From 5a062b19dc455bb98c08d2909c0bf8bc565e3763 Mon Sep 17 00:00:00 2001
From: Pepe Fagoaga
Date: Mon, 9 Mar 2026 12:14:19 +0000
Subject: [PATCH 001/108] chore: remove SaaS reference in dashboard (#10288)
---
dashboard/__main__.py | 2 +-
docs/user-guide/cli/tutorials/dashboard.mdx | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/dashboard/__main__.py b/dashboard/__main__.py
index 304e4afdf2..664ce3bfa5 100644
--- a/dashboard/__main__.py
+++ b/dashboard/__main__.py
@@ -21,7 +21,7 @@ print(
f"{Fore.GREEN}Loading all CSV files from the folder {folder_path_overview} ...\n{Style.RESET_ALL}"
)
cli.show_server_banner = lambda *x: click.echo(
- f"{Fore.YELLOW}NOTE:{Style.RESET_ALL} If you are using {Fore.GREEN}{Style.BRIGHT}Prowler SaaS{Style.RESET_ALL} with the S3 integration or that integration \nfrom {Fore.CYAN}{Style.BRIGHT}Prowler Open Source{Style.RESET_ALL} and you want to use your data from your S3 bucket,\nrun: `{orange_color}aws s3 cp s3:///output/csv ./output --recursive{Style.RESET_ALL}`\nand then run `prowler dashboard` again to load the new files."
+ f"{Fore.YELLOW}NOTE:{Style.RESET_ALL} If you are using {Fore.GREEN}{Style.BRIGHT}Prowler Cloud{Style.RESET_ALL} with the S3 integration or that integration \nfrom {Fore.CYAN}{Style.BRIGHT}Prowler CLI{Style.RESET_ALL} and you want to use your data from your S3 bucket,\nrun: `{orange_color}aws s3 cp s3:///output/csv ./output --recursive{Style.RESET_ALL}`\nand then run `prowler dashboard` again to load the new files."
)
# Initialize the app - incorporate css
diff --git a/docs/user-guide/cli/tutorials/dashboard.mdx b/docs/user-guide/cli/tutorials/dashboard.mdx
index 8c819743fe..0fe3c69fed 100644
--- a/docs/user-guide/cli/tutorials/dashboard.mdx
+++ b/docs/user-guide/cli/tutorials/dashboard.mdx
@@ -99,7 +99,7 @@ def get_table(data):
## S3 Integration
-If you are using Prowler SaaS with the S3 integration or that integration from Prowler Open Source and you want to use your data from your S3 bucket, you can run the following command in order to load the dashboard with the new files:
+If you are using Prowler Cloud with the S3 integration or that integration from Prowler CLI and you want to use your data from your S3 bucket, you can run the following command in order to load the dashboard with the new files:
```sh
aws s3 cp s3:///output/csv ./output --recursive
From 1e95b48c86f9aae5a220ad269f8003651546351c Mon Sep 17 00:00:00 2001
From: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com>
Date: Mon, 9 Mar 2026 13:36:31 +0100
Subject: [PATCH 002/108] fix(ui): rename error text token to
text-text-error-primary (#10285)
---
.../providers/radio-group-provider.tsx | 2 +-
.../forms/fields/wizard-input-field.tsx | 2 +-
.../forms/fields/wizard-textarea-field.tsx | 2 +-
...alibabacloud-via-credentials-type-form.tsx | 2 +-
...io-group-aws-via-credentials-type-form.tsx | 2 +-
...p-cloudflare-via-credentials-type-form.tsx | 2 +-
...io-group-gcp-via-credentials-type-form.tsx | 2 +-
...group-github-via-credentials-type-form.tsx | 2 +-
...o-group-m365-via-credentials-type-form.tsx | 2 +-
ui/components/ui/form/Form.test.tsx | 65 +++++++++++++++++++
ui/components/ui/form/Form.tsx | 7 +-
11 files changed, 79 insertions(+), 11 deletions(-)
create mode 100644 ui/components/ui/form/Form.test.tsx
diff --git a/ui/components/providers/radio-group-provider.tsx b/ui/components/providers/radio-group-provider.tsx
index b4bf22af4b..b071062e94 100644
--- a/ui/components/providers/radio-group-provider.tsx
+++ b/ui/components/providers/radio-group-provider.tsx
@@ -176,7 +176,7 @@ export const RadioGroupProvider: FC = ({
{errorMessage && (
-
+
{errorMessage}
)}
diff --git a/ui/components/providers/workflow/forms/fields/wizard-input-field.tsx b/ui/components/providers/workflow/forms/fields/wizard-input-field.tsx
index 88188b5487..b769bd1461 100644
--- a/ui/components/providers/workflow/forms/fields/wizard-input-field.tsx
+++ b/ui/components/providers/workflow/forms/fields/wizard-input-field.tsx
@@ -163,7 +163,7 @@ export const WizardInputField = ({
)}
-
+
);
}}
diff --git a/ui/components/providers/workflow/forms/fields/wizard-textarea-field.tsx b/ui/components/providers/workflow/forms/fields/wizard-textarea-field.tsx
index 05768b0e04..473de8d48d 100644
--- a/ui/components/providers/workflow/forms/fields/wizard-textarea-field.tsx
+++ b/ui/components/providers/workflow/forms/fields/wizard-textarea-field.tsx
@@ -87,7 +87,7 @@ export const WizardTextareaField = ({
{description}
)}
-
+
);
}}
diff --git a/ui/components/providers/workflow/forms/select-credentials-type/alibabacloud/radio-group-alibabacloud-via-credentials-type-form.tsx b/ui/components/providers/workflow/forms/select-credentials-type/alibabacloud/radio-group-alibabacloud-via-credentials-type-form.tsx
index 0359f07c86..1315a6b559 100644
--- a/ui/components/providers/workflow/forms/select-credentials-type/alibabacloud/radio-group-alibabacloud-via-credentials-type-form.tsx
+++ b/ui/components/providers/workflow/forms/select-credentials-type/alibabacloud/radio-group-alibabacloud-via-credentials-type-form.tsx
@@ -50,7 +50,7 @@ export const RadioGroupAlibabaCloudViaCredentialsTypeForm = <
{errorMessage && (
-
+
{errorMessage}
)}
diff --git a/ui/components/providers/workflow/forms/select-credentials-type/aws/radio-group-aws-via-credentials-type-form.tsx b/ui/components/providers/workflow/forms/select-credentials-type/aws/radio-group-aws-via-credentials-type-form.tsx
index b88208347a..294d68515b 100644
--- a/ui/components/providers/workflow/forms/select-credentials-type/aws/radio-group-aws-via-credentials-type-form.tsx
+++ b/ui/components/providers/workflow/forms/select-credentials-type/aws/radio-group-aws-via-credentials-type-form.tsx
@@ -43,7 +43,7 @@ export const RadioGroupAWSViaCredentialsTypeForm = ({
{errorMessage && (
-
+
{errorMessage}
)}
diff --git a/ui/components/providers/workflow/forms/select-credentials-type/cloudflare/radio-group-cloudflare-via-credentials-type-form.tsx b/ui/components/providers/workflow/forms/select-credentials-type/cloudflare/radio-group-cloudflare-via-credentials-type-form.tsx
index ba60dd6c25..67ea17267e 100644
--- a/ui/components/providers/workflow/forms/select-credentials-type/cloudflare/radio-group-cloudflare-via-credentials-type-form.tsx
+++ b/ui/components/providers/workflow/forms/select-credentials-type/cloudflare/radio-group-cloudflare-via-credentials-type-form.tsx
@@ -44,7 +44,7 @@ export const RadioGroupCloudflareViaCredentialsTypeForm = ({
{errorMessage && (
-
+
{errorMessage}
)}
diff --git a/ui/components/providers/workflow/forms/select-credentials-type/gcp/radio-group-gcp-via-credentials-type-form.tsx b/ui/components/providers/workflow/forms/select-credentials-type/gcp/radio-group-gcp-via-credentials-type-form.tsx
index 279d17a790..1f32d154a7 100644
--- a/ui/components/providers/workflow/forms/select-credentials-type/gcp/radio-group-gcp-via-credentials-type-form.tsx
+++ b/ui/components/providers/workflow/forms/select-credentials-type/gcp/radio-group-gcp-via-credentials-type-form.tsx
@@ -47,7 +47,7 @@ export const RadioGroupGCPViaCredentialsTypeForm = ({
{errorMessage && (
-
+
{errorMessage}
)}
diff --git a/ui/components/providers/workflow/forms/select-credentials-type/github/radio-group-github-via-credentials-type-form.tsx b/ui/components/providers/workflow/forms/select-credentials-type/github/radio-group-github-via-credentials-type-form.tsx
index f6964d98d0..fa2d5ab98f 100644
--- a/ui/components/providers/workflow/forms/select-credentials-type/github/radio-group-github-via-credentials-type-form.tsx
+++ b/ui/components/providers/workflow/forms/select-credentials-type/github/radio-group-github-via-credentials-type-form.tsx
@@ -54,7 +54,7 @@ export const RadioGroupGitHubViaCredentialsTypeForm = ({
{errorMessage && (
-
+
{errorMessage}
)}
diff --git a/ui/components/providers/workflow/forms/select-credentials-type/m365/radio-group-m365-via-credentials-type-form.tsx b/ui/components/providers/workflow/forms/select-credentials-type/m365/radio-group-m365-via-credentials-type-form.tsx
index dcbba07d2b..ed871352a7 100644
--- a/ui/components/providers/workflow/forms/select-credentials-type/m365/radio-group-m365-via-credentials-type-form.tsx
+++ b/ui/components/providers/workflow/forms/select-credentials-type/m365/radio-group-m365-via-credentials-type-form.tsx
@@ -44,7 +44,7 @@ export const RadioGroupM365ViaCredentialsTypeForm = ({
{errorMessage && (
-
+
{errorMessage}
)}
diff --git a/ui/components/ui/form/Form.test.tsx b/ui/components/ui/form/Form.test.tsx
new file mode 100644
index 0000000000..41f01750e5
--- /dev/null
+++ b/ui/components/ui/form/Form.test.tsx
@@ -0,0 +1,65 @@
+import { render, screen } from "@testing-library/react";
+import { useEffect } from "react";
+import { useForm } from "react-hook-form";
+import { describe, expect, it } from "vitest";
+
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "./Form";
+
+interface TestValues {
+ providerUid: string;
+}
+
+function TestFormWithError() {
+ const form = useForm({
+ defaultValues: {
+ providerUid: "",
+ },
+ });
+
+ useEffect(() => {
+ form.setError("providerUid", {
+ type: "manual",
+ message: "Provider ID is required",
+ });
+ }, [form]);
+
+ return (
+
+ );
+}
+
+describe("Form", () => {
+ it("should use the existing error text token for labels and messages", async () => {
+ // Given
+ render();
+
+ // When
+ const label = await screen.findByText("Provider UID");
+ const message = await screen.findByText("Provider ID is required");
+
+ // Then
+ expect(label).toHaveClass("text-text-error-primary");
+ expect(message).toHaveClass("text-text-error-primary");
+ });
+});
diff --git a/ui/components/ui/form/Form.tsx b/ui/components/ui/form/Form.tsx
index f4cc7d5403..011b0e0492 100644
--- a/ui/components/ui/form/Form.tsx
+++ b/ui/components/ui/form/Form.tsx
@@ -100,7 +100,10 @@ const FormLabel = React.forwardRef<
return (
@@ -164,7 +167,7 @@ const FormMessage = React.forwardRef<
ref={ref}
id={formMessageId}
className={cn(
- "text-text-error max-w-full text-xs font-medium",
+ "text-text-error-primary max-w-full text-xs font-medium",
className,
)}
{...props}
From 809142de35558c37929efaf76e7f0b60407b2062 Mon Sep 17 00:00:00 2001
From: Daniel Barranquero <74871504+danibarranqueroo@users.noreply.github.com>
Date: Mon, 9 Mar 2026 16:37:19 +0100
Subject: [PATCH 003/108] chore(alibaba): update all metadata files (#10289)
---
docs/developer-guide/checks.mdx | 1 +
prowler/CHANGELOG.md | 1 +
...ontrail_multi_region_enabled.metadata.json | 21 +++++++--------
...cket_not_publicly_accessible.metadata.json | 22 +++++++--------
...ernetes_cloudmonitor_enabled.metadata.json | 24 ++++++++---------
...ernetes_cluster_check_recent.metadata.json | 24 ++++++++---------
...ernetes_cluster_check_weekly.metadata.json | 24 ++++++++---------
...ubernetes_dashboard_disabled.metadata.json | 23 +++++++---------
...etes_eni_multiple_ip_enabled.metadata.json | 23 +++++++---------
...bernetes_log_service_enabled.metadata.json | 25 ++++++++---------
...netes_network_policy_enabled.metadata.json | 25 ++++++++---------
...etes_private_cluster_enabled.metadata.json | 25 ++++++++---------
.../cs_kubernetes_rbac_enabled.metadata.json | 26 ++++++++----------
.../ecs_attached_disk_encrypted.metadata.json | 20 +++++++-------
...ndpoint_protection_installed.metadata.json | 26 +++++++-----------
...ce_latest_os_patches_applied.metadata.json | 23 +++++++---------
...s_instance_no_legacy_network.metadata.json | 20 +++++++-------
...ygroup_restrict_rdp_internet.metadata.json | 21 +++++++--------
...ygroup_restrict_ssh_internet.metadata.json | 21 +++++++--------
...cs_unattached_disk_encrypted.metadata.json | 20 +++++++-------
.../oss_bucket_logging_enabled.metadata.json | 19 ++++++-------
...cket_not_publicly_accessible.metadata.json | 19 ++++++-------
...ket_secure_transport_enabled.metadata.json | 22 +++++++--------
.../ram_no_root_access_key.metadata.json | 19 ++++++-------
...am_password_policy_lowercase.metadata.json | 21 +++++++--------
...rd_policy_max_login_attempts.metadata.json | 21 +++++++--------
...word_policy_max_password_age.metadata.json | 23 +++++++---------
...ssword_policy_minimum_length.metadata.json | 21 +++++++--------
.../ram_password_policy_number.metadata.json | 21 +++++++--------
...cy_password_reuse_prevention.metadata.json | 21 +++++++--------
.../ram_password_policy_symbol.metadata.json | 21 +++++++--------
...am_password_policy_uppercase.metadata.json | 21 +++++++--------
...ached_only_to_group_or_roles.metadata.json | 19 ++++++-------
...no_administrative_privileges.metadata.json | 21 +++++++--------
...am_rotate_access_key_90_days.metadata.json | 19 ++++++-------
...m_user_console_access_unused.metadata.json | 21 +++++++--------
...r_mfa_enabled_console_access.metadata.json | 21 +++++++--------
...e_no_public_access_whitelist.metadata.json | 23 +++++++---------
...esql_log_connections_enabled.metadata.json | 21 +++++++--------
...l_log_disconnections_enabled.metadata.json | 21 +++++++--------
...tgresql_log_duration_enabled.metadata.json | 20 +++++++-------
...s_instance_sql_audit_enabled.metadata.json | 20 +++++++-------
...instance_sql_audit_retention.metadata.json | 20 +++++++-------
.../rds_instance_ssl_enabled.metadata.json | 21 +++++++--------
.../rds_instance_tde_enabled.metadata.json | 21 +++++++--------
.../rds_instance_tde_key_custom.metadata.json | 21 +++++++--------
...vanced_or_enterprise_edition.metadata.json | 27 +++++++------------
...r_all_assets_agent_installed.metadata.json | 24 +++++++----------
...tification_enabled_high_risk.metadata.json | 24 +++++++----------
...r_vulnerability_scan_enabled.metadata.json | 19 ++++++-------
...rewall_changes_alert_enabled.metadata.json | 19 ++++++-------
...ed_cmk_changes_alert_enabled.metadata.json | 21 +++++++--------
...ls_logstore_retention_period.metadata.json | 20 +++++++-------
...ation_failures_alert_enabled.metadata.json | 21 +++++++--------
...in_without_mfa_alert_enabled.metadata.json | 21 +++++++--------
...policy_changes_alert_enabled.metadata.json | 21 +++++++--------
...ission_changes_alert_enabled.metadata.json | 19 ++++++-------
...m_role_changes_alert_enabled.metadata.json | 19 ++++++-------
...ration_changes_alert_enabled.metadata.json | 19 ++++++-------
..._account_usage_alert_enabled.metadata.json | 21 +++++++--------
..._group_changes_alert_enabled.metadata.json | 21 +++++++--------
...ized_api_calls_alert_enabled.metadata.json | 21 +++++++--------
...ls_vpc_changes_alert_enabled.metadata.json | 19 ++++++-------
..._route_changes_alert_enabled.metadata.json | 21 +++++++--------
.../vpc_flow_logs_enabled.metadata.json | 21 +++++++--------
65 files changed, 581 insertions(+), 770 deletions(-)
diff --git a/docs/developer-guide/checks.mdx b/docs/developer-guide/checks.mdx
index 0c9bdf154e..da469678fa 100644
--- a/docs/developer-guide/checks.mdx
+++ b/docs/developer-guide/checks.mdx
@@ -315,6 +315,7 @@ The type of resource being audited. This field helps categorize and organize fin
- **Kubernetes**: Use types shown under `KIND` from `kubectl api-resources`.
- **Oracle Cloud Infrastructure**: Use types from [Oracle Cloud Infrastructure documentation](https://docs.public.oneportal.content.oci.oraclecloud.com/en-us/iaas/Content/Search/Tasks/queryingresources_topic-Listing_Supported_Resource_Types.htm).
- **OpenStack**: Use types from [OpenStack Heat resource types](https://docs.openstack.org/heat/latest/template_guide/openstack.html).
+- **Alibaba Cloud**: Use types from [Alibaba Cloud ROS resource types](https://www.alibabacloud.com/help/en/ros/developer-reference/list-of-resource-types-by-service).
- **Any other provider**: Use `NotDefined` due to lack of standardized resource types in their SDK or documentation.
#### ResourceGroup
diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md
index bf7809a6e9..fff5d21773 100644
--- a/prowler/CHANGELOG.md
+++ b/prowler/CHANGELOG.md
@@ -30,6 +30,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- Update Oracle Cloud Object Storage service metadata to new format [(#9379)](https://github.com/prowler-cloud/prowler/pull/9379)
- Update Oracle Cloud Events service metadata to new format [(#9373)](https://github.com/prowler-cloud/prowler/pull/9373)
- Update Oracle Cloud Identity service metadata to new format [(#9375)](https://github.com/prowler-cloud/prowler/pull/9375)
+- Update Alibaba Cloud services metadata to new format [(#10289)](https://github.com/prowler-cloud/prowler/pull/10289)
---
diff --git a/prowler/providers/alibabacloud/services/actiontrail/actiontrail_multi_region_enabled/actiontrail_multi_region_enabled.metadata.json b/prowler/providers/alibabacloud/services/actiontrail/actiontrail_multi_region_enabled/actiontrail_multi_region_enabled.metadata.json
index cb49461ce0..ed8ff162b0 100644
--- a/prowler/providers/alibabacloud/services/actiontrail/actiontrail_multi_region_enabled/actiontrail_multi_region_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/actiontrail/actiontrail_multi_region_enabled/actiontrail_multi_region_enabled.metadata.json
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "actiontrail_multi_region_enabled",
- "CheckTitle": "ActionTrail are configured to export copies of all Log entries",
- "CheckType": [
- "Unusual logon",
- "Cloud threat detection"
- ],
+ "CheckTitle": "ActionTrail is configured to export copies of all log entries across all regions",
+ "CheckType": [],
"ServiceName": "actiontrail",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:actiontrail::account-id:trail",
+ "ResourceIdTemplate": "",
"Severity": "critical",
- "ResourceType": "AlibabaCloudActionTrail",
+ "ResourceType": "ALIYUN::ACTIONTRAIL::Trail",
"ResourceGroup": "monitoring",
- "Description": "**ActionTrail** is a web service that records API calls for your account and delivers log files to you.\n\nThe recorded information includes the identity of the API caller, the time of the API call, the source IP address of the API caller, the request parameters, and the response elements returned by the Alibaba Cloud service. ActionTrail provides a history of API calls for an account, including API calls made via the Management Console, SDKs, and command line tools.",
- "Risk": "The API call history produced by ActionTrail enables **security analysis**, **resource change tracking**, and **compliance auditing**.\n\nEnsuring that a **multi-region trail** exists will detect unexpected activities occurring in otherwise unused regions. Global Service Logging should be enabled by default to capture events generated on Alibaba Cloud global services, ensuring the recording of management operations performed on all resources in an Alibaba Cloud account.",
+ "Description": "**Alibaba Cloud ActionTrail** is a service that records API calls made to your account and delivers log files containing the identity of the API caller, the time and source IP of the call, the request parameters, and the response elements returned by the service. Ensuring that a **multi-region trail** exists guarantees that management operations performed across all regions and global services are captured, enabling detection of unexpected activities in otherwise unused regions.",
+ "Risk": "Without a **multi-region trail** enabled, API calls made in regions outside the primary trail's scope will not be recorded. This creates blind spots in **security analysis**, **resource change tracking**, and **compliance auditing**, potentially allowing unauthorized or malicious activity to go undetected across your Alibaba Cloud account.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/28829.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ActionTrail/enable-multi-region-trails.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ActionTrail/enable-multi-region-trails.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun actiontrail CreateTrail --Name --OssBucketName --RoleName aliyunactiontraildefaultrole --SlsProjectArn --SlsWriteRoleArn --EventRW ",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **ActionTrail Console**\n2. Click on **Trails** in the left navigation pane\n3. Click **Add new trail**\n4. Enter a trail name in the `Trail name` box\n5. Set **Yes** for `Apply Trail to All Regions`\n6. Specify an OSS bucket name in the `OSS bucket` box\n7. Specify an SLS project name in the `SLS project` box\n8. Click **Create**",
"Terraform": "resource \"alicloud_actiontrail_trail\" \"example\" {\n trail_name = \"multi-region-trail\"\n trail_region = \"All\"\n sls_project_arn = \"acs:log:cn-hangzhou:123456789:project/actiontrail-project\"\n sls_write_role_arn = data.alicloud_ram_roles.actiontrail.roles.0.arn\n}"
},
"Recommendation": {
- "Text": "1. Log on to the **ActionTrail Console**\n2. Click on **Trails** in the left navigation pane\n3. Click **Add new trail**\n4. Enter a trail name in the `Trail name` box\n5. Set **Yes** for `Apply Trail to All Regions`\n6. Specify an OSS bucket name in the `OSS bucket` box\n7. Specify an SLS project name in the `SLS project` box\n8. Click **Create**",
+ "Text": "Enable a multi-region trail in ActionTrail to ensure all API calls across all regions are recorded and delivered to a centralized OSS bucket and SLS project for security analysis and compliance auditing.",
"Url": "https://hub.prowler.com/check/actiontrail_multi_region_enabled"
}
},
diff --git a/prowler/providers/alibabacloud/services/actiontrail/actiontrail_oss_bucket_not_publicly_accessible/actiontrail_oss_bucket_not_publicly_accessible.metadata.json b/prowler/providers/alibabacloud/services/actiontrail/actiontrail_oss_bucket_not_publicly_accessible/actiontrail_oss_bucket_not_publicly_accessible.metadata.json
index 7505a7404a..55daf22591 100644
--- a/prowler/providers/alibabacloud/services/actiontrail/actiontrail_oss_bucket_not_publicly_accessible/actiontrail_oss_bucket_not_publicly_accessible.metadata.json
+++ b/prowler/providers/alibabacloud/services/actiontrail/actiontrail_oss_bucket_not_publicly_accessible/actiontrail_oss_bucket_not_publicly_accessible.metadata.json
@@ -1,32 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "actiontrail_oss_bucket_not_publicly_accessible",
- "CheckTitle": "The OSS used to store ActionTrail logs is not publicly accessible",
- "CheckType": [
- "Sensitive file tampering"
- ],
+ "CheckTitle": "The OSS bucket used to store ActionTrail logs is not publicly accessible",
+ "CheckType": [],
"ServiceName": "actiontrail",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:oss::account-id:bucket-name",
+ "ResourceIdTemplate": "",
"Severity": "critical",
- "ResourceType": "AlibabaCloudOSSBucket",
+ "ResourceType": "ALIYUN::ACTIONTRAIL::Trail",
"ResourceGroup": "storage",
- "Description": "**ActionTrail** logs a record of every API call made in your Alibaba Cloud account. These log files are stored in an **OSS bucket**.\n\nIt is recommended that the **Access Control List (ACL)** of the OSS bucket, which ActionTrail logs to, prevents public access to the ActionTrail logs.",
- "Risk": "Allowing **public access** to ActionTrail log content may aid an adversary in identifying weaknesses in the affected account's use or configuration.\n\nExposed audit logs can reveal sensitive information about your infrastructure, API usage patterns, and security configurations.",
+ "Description": "**Alibaba Cloud ActionTrail** logs a record of every API call made in your account and stores these log files in an **OSS bucket**. It is recommended that the **Access Control List (ACL)** of the OSS bucket used by ActionTrail is set to `private` to prevent unauthorized public access to sensitive audit log data.",
+ "Risk": "Allowing **public access** to the OSS bucket containing ActionTrail logs may expose sensitive information about your infrastructure, API usage patterns, and security configurations. An adversary could use this information to identify weaknesses in the affected account, leading to potential **data breaches**, **privilege escalation**, and **compliance violations**.",
"RelatedUrl": "",
"AdditionalURLs": [
- "https://help.aliyun.com/document_detail/31954.html",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ActionTrail/trail-bucket-publicly-accessible.html"
+ "https://www.alibabacloud.com/help/doc-detail/31954.htm",
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ActionTrail/trail-bucket-publicly-accessible.html"
],
"Remediation": {
"Code": {
"CLI": "ossutil set-acl oss:// private -b",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **OSS Console**\n2. Right-click on the bucket and select **Basic Settings**\n3. In the Access Control List pane, click **Configure**\n4. The Bucket ACL tab shows three types of grants: `Private`, `Public Read`, `Public Read/Write`\n5. Ensure **Private** is set for the bucket\n6. Click **Save** to save the ACL",
"Terraform": "resource \"alicloud_oss_bucket_public_access_block\" \"actiontrail\" {\n bucket = alicloud_oss_bucket.actiontrail.bucket\n block_public_access = true\n}"
},
"Recommendation": {
- "Text": "1. Log on to the **OSS Console**\n2. Right-click on the bucket and select **Basic Settings**\n3. In the Access Control List pane, click **Configure**\n4. The Bucket ACL tab shows three types of grants: `Private`, `Public Read`, `Public Read/Write`\n5. Ensure **Private** is set for the bucket\n6. Click **Save** to save the ACL",
+ "Text": "Set the ACL of the OSS bucket used to store ActionTrail logs to private to prevent unauthorized public access to sensitive audit log data.",
"Url": "https://hub.prowler.com/check/actiontrail_oss_bucket_not_publicly_accessible"
}
},
diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cloudmonitor_enabled/cs_kubernetes_cloudmonitor_enabled.metadata.json b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cloudmonitor_enabled/cs_kubernetes_cloudmonitor_enabled.metadata.json
index 7685aa4f2e..6809db19ec 100644
--- a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cloudmonitor_enabled/cs_kubernetes_cloudmonitor_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cloudmonitor_enabled/cs_kubernetes_cloudmonitor_enabled.metadata.json
@@ -1,32 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "cs_kubernetes_cloudmonitor_enabled",
- "CheckTitle": "CloudMonitor is set to Enabled on Kubernetes Engine Clusters",
- "CheckType": [
- "Threat detection during container runtime"
- ],
+ "CheckTitle": "Kubernetes cluster has CloudMonitor enabled",
+ "CheckType": [],
"ServiceName": "cs",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudKubernetesCluster",
+ "ResourceType": "ALIYUN::CS::ManagedKubernetesCluster",
"ResourceGroup": "container",
- "Description": "The monitoring service in **Kubernetes Engine clusters** depends on the Alibaba Cloud **CloudMonitor** agent to access additional system resources and application services in virtual machine instances.\n\nThe monitor can access metrics about CPU utilization, disk traffic metrics, network traffic, and disk IO information, which help monitor signals and build operations in your Kubernetes Engine clusters.",
- "Risk": "Without **CloudMonitor** enabled, you lack visibility into system metrics and custom metrics. System metrics measure the cluster's infrastructure, such as CPU or memory usage.\n\nWith CloudMonitor, a monitor controller is created that periodically connects to each node and collects metrics about its Pods and containers, then sends the metrics to CloudMonitor server.",
+ "Description": "**Alibaba Cloud CloudMonitor** agent provides visibility into system metrics for **Kubernetes Engine clusters**, including CPU utilization, disk traffic, network traffic, and disk IO information. Without the CloudMonitor agent enabled, operators lack critical observability into node and pod health, making it difficult to detect performance degradation or anomalous resource consumption. Enabling CloudMonitor ensures that a monitor controller is created to periodically connect to each node, collect metrics about its Pods and containers, and send them to the **CloudMonitor** server for analysis and alerting.",
+ "Risk": "Without **CloudMonitor** enabled on Kubernetes Engine clusters, there is no automated collection of system-level metrics such as CPU, memory, disk, and network usage. This lack of visibility can delay detection of **resource exhaustion**, **node failures**, and **abnormal workload behavior**, increasing the risk of undetected **availability** and **performance** issues. In a security context, the absence of monitoring data impairs the ability to identify **denial-of-service conditions** or **cryptojacking** activities running on cluster nodes.",
"RelatedUrl": "",
"AdditionalURLs": [
- "https://help.aliyun.com/document_detail/125508.html",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/enable-cloud-monitor.html"
+ "https://www.alibabacloud.com/help/en/ack/",
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/enable-cloud-monitor.html"
],
"Remediation": {
"Code": {
- "CLI": "aliyun cs GET /clusters/[cluster_id]/nodepools to verify nodepools.kubernetes_config.cms_enabled is set to true for all node pools.",
+ "CLI": "aliyun cs GET /clusters//nodepools --header 'Content-Type=application/json' | jq '.nodepools[].kubernetes_config.cms_enabled'",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **ACK Console**.\n2. Select the target cluster and click its name to open the cluster detail page.\n3. Select **Nodes** on the left column and click the **Monitor** link on the Actions column of the selected node.\n4. Verify that OS Metrics data exists in the CloudMonitor page.\n5. To enable: Click **Create Kubernetes Cluster** and set `CloudMonitor Agent` to **Enabled** under creation options.",
"Terraform": ""
},
"Recommendation": {
- "Text": "1. Log on to the **ACK Console**\n2. Select the target cluster and click its name to open the cluster detail page\n3. Select **Nodes** on the left column and click the **Monitor** link on the Actions column of the selected node\n4. Verify that OS Metrics data exists in the CloudMonitor page\n5. To enable: Click **Create Kubernetes Cluster** and set `CloudMonitor Agent` to **Enabled** under creation options",
+ "Text": "Enable the **CloudMonitor** agent during cluster creation by setting `CloudMonitor Agent` to **Enabled**. For existing clusters, verify that `cms_enabled` is set to `true` for all node pools.",
"Url": "https://hub.prowler.com/check/cs_kubernetes_cloudmonitor_enabled"
}
},
diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_recent/cs_kubernetes_cluster_check_recent.metadata.json b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_recent/cs_kubernetes_cluster_check_recent.metadata.json
index ed87a8c2fb..948ca7539f 100644
--- a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_recent/cs_kubernetes_cluster_check_recent.metadata.json
+++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_recent/cs_kubernetes_cluster_check_recent.metadata.json
@@ -1,32 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "cs_kubernetes_cluster_check_recent",
- "CheckTitle": "Cluster Check triggered within configured period for Kubernetes Clusters",
- "CheckType": [
- "Threat detection during container runtime"
- ],
+ "CheckTitle": "Kubernetes cluster health check has been triggered within the configured period",
+ "CheckType": [],
"ServiceName": "cs",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudKubernetesCluster",
+ "ResourceType": "ALIYUN::CS::ManagedKubernetesCluster",
"ResourceGroup": "container",
- "Description": "**Kubernetes Engine's cluster check** feature helps you verify the system nodes and components healthy status.\n\nWhen you trigger the checking, the process validates the health state of each node in your cluster and also the cluster configuration (`kubelet`, `docker daemon`, `kernel`, and network `iptables` configuration). If there are consecutive health check failures, the diagnose reports to admin for further repair.",
- "Risk": "Kubernetes Engine uses the node's health status to determine if a node needs to be repaired. A cluster health check includes: cloud resource healthy status including **VPC/VSwitch**, **SLB**, and every **ECS node** status in the cluster; the `kubelet`, `docker daemon`, `kernel`, `iptables` configurations on every node.\n\nWithout regular cluster checks, potential issues may go undetected and could lead to **cluster instability** or **security vulnerabilities**.",
+ "Description": "**Alibaba Cloud Kubernetes Engine** provides a cluster health check feature that validates the health state of each node and verifies the cluster configuration, including `kubelet`, `docker daemon`, `kernel`, and network `iptables` settings. Running these checks regularly ensures that cloud resources such as **VPC/VSwitch**, **SLB**, and every **ECS node** are functioning correctly. If consecutive health check failures are detected, diagnostic reports are generated for administrators to take corrective action.",
+ "Risk": "Without regular cluster health checks within the configured period, potential issues such as **node failures**, **misconfigured network rules**, or **degraded system components** may go undetected. This increases the risk of **cluster instability**, **service outages**, and **security vulnerabilities** that could be exploited by attackers. Delayed detection of unhealthy nodes or misconfigured components can also impact the **integrity** and **availability** of workloads running on the cluster.",
"RelatedUrl": "",
"AdditionalURLs": [
- "https://help.aliyun.com/document_detail/114882.html",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/cluster-check.html"
+ "https://www.alibabacloud.com/help/en/ack/",
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/cluster-check.html"
],
"Remediation": {
"Code": {
- "CLI": "aliyun cs GET /clusters/[cluster_id]/checks to verify cluster checks are being run regularly. Trigger a check if needed.",
+ "CLI": "aliyun cs GET /clusters//checks --header 'Content-Type=application/json'",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **ACK Console**.\n2. Select the target cluster and open the **More** pop-menu for advanced options.\n3. Select **Global Check** and click the **Start** button to trigger the checking.\n4. Verify the checking time and details in Global Check.\n5. It is recommended to trigger cluster checks at least once within the configured period.",
"Terraform": ""
},
"Recommendation": {
- "Text": "1. Log on to the **ACK Console**\n2. Select the target cluster and open the **More** pop-menu for advanced options\n3. Select **Global Check** and click the **Start** button to trigger the checking\n4. Verify the checking time and details in Global Check\n5. It is recommended to trigger cluster checks at least once within the configured period (default: weekly)",
+ "Text": "Trigger a cluster health check regularly within the configured period to ensure all nodes and system components are healthy. Use the **Global Check** feature in the ACK Console or the `aliyun cs` CLI to verify and trigger checks.",
"Url": "https://hub.prowler.com/check/cs_kubernetes_cluster_check_recent"
}
},
diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_weekly/cs_kubernetes_cluster_check_weekly.metadata.json b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_weekly/cs_kubernetes_cluster_check_weekly.metadata.json
index db61e88fe2..9e7bb1af6a 100644
--- a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_weekly/cs_kubernetes_cluster_check_weekly.metadata.json
+++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_weekly/cs_kubernetes_cluster_check_weekly.metadata.json
@@ -1,32 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "cs_kubernetes_cluster_check_weekly",
- "CheckTitle": "Cluster Check triggered at least once per week for Kubernetes Clusters",
- "CheckType": [
- "Threat detection during container runtime"
- ],
+ "CheckTitle": "Kubernetes cluster health check has been triggered at least once per week",
+ "CheckType": [],
"ServiceName": "cs",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudKubernetesCluster",
+ "ResourceType": "ALIYUN::CS::ManagedKubernetesCluster",
"ResourceGroup": "container",
- "Description": "**Kubernetes Engine's cluster check** feature helps you verify the system nodes and components healthy status.\n\nWhen you trigger the checking, the process validates the health state of each node in your cluster and also the cluster configuration (`kubelet`, `docker daemon`, `kernel`, and network `iptables` configuration). If there are consecutive health check failures, the diagnose reports to admin for further repair.",
- "Risk": "Kubernetes Engine uses the node's health status to determine if a node needs to be repaired. A cluster health check includes: cloud resource healthy status including **VPC/VSwitch**, **SLB**, and every **ECS node** status in the cluster; the `kubelet`, `docker daemon`, `kernel`, `iptables` configurations on every node.\n\nWithout regular cluster checks, potential issues may go undetected and could lead to **cluster instability** or **security vulnerabilities**.",
+ "Description": "**Alibaba Cloud Kubernetes Engine** provides a cluster health check feature that validates the health state of each node and verifies the cluster configuration, including `kubelet`, `docker daemon`, `kernel`, and network `iptables` settings. Running these checks at least once per week ensures that cloud resources such as **VPC/VSwitch**, **SLB**, and every **ECS node** are functioning correctly. If consecutive health check failures are detected, diagnostic reports are generated for administrators to take corrective action.",
+ "Risk": "Without weekly cluster health checks, potential issues such as **node failures**, **misconfigured network rules**, or **degraded system components** may go undetected for extended periods. This increases the risk of **cluster instability**, **service outages**, and **security vulnerabilities** that could be exploited by attackers. Delayed detection of unhealthy nodes or misconfigured components can also impact the **integrity** and **availability** of workloads running on the cluster.",
"RelatedUrl": "",
"AdditionalURLs": [
- "https://help.aliyun.com/document_detail/114882.html",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/cluster-check.html"
+ "https://www.alibabacloud.com/help/en/ack/",
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/cluster-check.html"
],
"Remediation": {
"Code": {
- "CLI": "aliyun cs GET /clusters/[cluster_id]/checks to verify cluster checks are being run regularly. Trigger a check if needed.",
+ "CLI": "aliyun cs GET /clusters//checks --header 'Content-Type=application/json'",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **ACK Console**.\n2. Select the target cluster and open the **More** pop-menu for advanced options.\n3. Select **Global Check** and click the **Start** button to trigger the checking.\n4. Verify the checking time and details in Global Check.\n5. Trigger cluster checks at least once per week.",
"Terraform": ""
},
"Recommendation": {
- "Text": "1. Log on to the **ACK Console**\n2. Select the target cluster and open the **More** pop-menu for advanced options\n3. Select **Global Check** and click the **Start** button to trigger the checking\n4. Verify the checking time and details in Global Check\n5. It is recommended to trigger cluster checks at least once per week",
+ "Text": "Trigger a cluster health check at least once per week to ensure all nodes and system components are healthy. Use the **Global Check** feature in the ACK Console or the `aliyun cs` CLI to verify and trigger checks.",
"Url": "https://hub.prowler.com/check/cs_kubernetes_cluster_check_weekly"
}
},
diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_dashboard_disabled/cs_kubernetes_dashboard_disabled.metadata.json b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_dashboard_disabled/cs_kubernetes_dashboard_disabled.metadata.json
index 4d0b3ed936..162f75c79f 100644
--- a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_dashboard_disabled/cs_kubernetes_dashboard_disabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_dashboard_disabled/cs_kubernetes_dashboard_disabled.metadata.json
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "cs_kubernetes_dashboard_disabled",
- "CheckTitle": "Kubernetes web UI / Dashboard is not enabled",
- "CheckType": [
- "Threat detection during container runtime",
- "Unusual logon"
- ],
+ "CheckTitle": "Kubernetes web UI (Dashboard) is disabled on Kubernetes Engine clusters",
+ "CheckType": [],
"ServiceName": "cs",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}",
+ "ResourceIdTemplate": "",
"Severity": "high",
- "ResourceType": "AlibabaCloudKubernetesCluster",
+ "ResourceType": "ALIYUN::CS::ManagedKubernetesCluster",
"ResourceGroup": "container",
- "Description": "**Dashboard** is a web-based Kubernetes user interface that can be used to deploy containerized applications to a Kubernetes cluster, troubleshoot your containerized application, and manage the cluster itself.\n\nYou should disable the **Kubernetes Web UI (Dashboard)** when running on Kubernetes Engine. The Dashboard is backed by a highly privileged Kubernetes Service Account. It is recommended to use the **ACK User Console** instead to avoid privilege escalation via a compromised dashboard.",
- "Risk": "The **Kubernetes Dashboard** is backed by a highly privileged Service Account. If the Dashboard is compromised, it could allow an attacker to gain **full control** over the cluster and potentially **escalate privileges**.\n\nAttackers who gain access to the Dashboard can deploy malicious workloads, exfiltrate secrets, and compromise the entire cluster.",
+ "Description": "**Alibaba Cloud Kubernetes Engine** clusters should not have the **Kubernetes Dashboard** (web UI) enabled. The Dashboard is backed by a highly privileged Kubernetes Service Account that can perform administrative operations across the cluster. It is recommended to use the **ACK Console** instead, which provides fine-grained access control through RAM policies and RBAC integration, avoiding the risk of privilege escalation through a compromised dashboard.",
+ "Risk": "The **Kubernetes Dashboard** is backed by a highly privileged Service Account that grants broad access to cluster resources. If the Dashboard is compromised through a vulnerability or unauthorized access, an attacker could gain **full control** over the cluster, deploy malicious workloads, exfiltrate **secrets**, and **escalate privileges**. This directly impacts the **confidentiality**, **integrity**, and **availability** of all workloads and data within the cluster.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/86494.html",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/disable-kubernetes-dashboard.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/disable-kubernetes-dashboard.html"
],
"Remediation": {
"Code": {
- "CLI": "Use kubectl to delete the dashboard deployment: kubectl delete deployment kubernetes-dashboard -n kube-system",
+ "CLI": "kubectl delete deployment kubernetes-dashboard -n kube-system",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **ACK Console**.\n2. Select the target cluster and select the `kube-system` namespace in the Namespace pop-menu.\n3. Input `dashboard` in the deploy filter bar.\n4. Make sure there is no result after the filter.\n5. If dashboard exists, delete the deployment by selecting **Delete** in the More pop-menu.",
"Terraform": ""
},
"Recommendation": {
- "Text": "1. Log on to the **ACK Console**\n2. Select the target cluster and select the `kube-system` namespace in the Namespace pop-menu\n3. Input `dashboard` in the deploy filter bar\n4. Make sure there is no result after the filter\n5. If dashboard exists, delete the deployment by selecting **Delete** in the More pop-menu",
+ "Text": "Delete the Kubernetes Dashboard deployment from the `kube-system` namespace using `kubectl` or the ACK Console. Use the **ACK Console** for cluster management instead of the Kubernetes Dashboard.",
"Url": "https://hub.prowler.com/check/cs_kubernetes_dashboard_disabled"
}
},
diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_eni_multiple_ip_enabled/cs_kubernetes_eni_multiple_ip_enabled.metadata.json b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_eni_multiple_ip_enabled/cs_kubernetes_eni_multiple_ip_enabled.metadata.json
index 29392fa152..d0c4e8cc92 100644
--- a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_eni_multiple_ip_enabled/cs_kubernetes_eni_multiple_ip_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_eni_multiple_ip_enabled/cs_kubernetes_eni_multiple_ip_enabled.metadata.json
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "cs_kubernetes_eni_multiple_ip_enabled",
- "CheckTitle": "ENI multiple IP mode support for Kubernetes Cluster",
- "CheckType": [
- "Threat detection during container runtime",
- "Suspicious network connection"
- ],
+ "CheckTitle": "Kubernetes cluster has ENI multiple IP mode enabled",
+ "CheckType": [],
"ServiceName": "cs",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudKubernetesCluster",
+ "ResourceType": "ALIYUN::CS::ManagedKubernetesCluster",
"ResourceGroup": "container",
- "Description": "Alibaba Cloud **ENI (Elastic Network Interface)** supports assigning ranges of internal IP addresses as aliases to a single virtual machine's ENI network interfaces.\n\nWith **ENI multiple IP mode**, Kubernetes Engine clusters can allocate IP addresses from a CIDR block known to **Terway** network plugin. This makes your cluster more scalable and allows better interaction with other Alibaba Cloud products.",
- "Risk": "Without **ENI multiple IP mode** (provided by Terway), pods share the node's network interface in a less scalable way.\n\nUsing ENI multiple IPs allows pod IPs to be reserved within the network ahead of time, preventing conflict with other compute resources, and allows firewall controls for Pods to be applied separately from their nodes.",
+ "Description": "**Alibaba Cloud Elastic Network Interface (ENI)** supports assigning multiple IP addresses to a single virtual machine's network interface. With **ENI multiple IP mode** provided by the **Terway** network plugin, Kubernetes Engine clusters can allocate pod IP addresses from the VPC CIDR block, enabling better scalability and native integration with other Alibaba Cloud services. This mode allows pods to have their own security group associations, providing granular network-level access control independently from their host nodes.",
+ "Risk": "Without **ENI multiple IP mode** (provided by the **Terway** network plugin), pods share the node's network interface in a less scalable manner and cannot have independent security group associations. This limits the ability to apply **granular firewall controls** at the pod level, increasing the risk of **lateral movement** if a pod is compromised. The inability to isolate pod networking from node networking weakens **network segmentation** and reduces the overall **security posture** of the cluster.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/en/ack/ack-managed-and-ack-dedicated/user-guide/associate-multiple-security-groups-with-an-eni",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/enable-multi-ip-mode.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/enable-multi-ip-mode.html"
],
"Remediation": {
"Code": {
- "CLI": "Terway network plugin must be selected during cluster creation to support ENI multiple IP mode.",
+ "CLI": "aliyun cs GET /clusters/ --header 'Content-Type=application/json' | jq '.parameters.Network'",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. When creating a new cluster in the **ACK Console**, select **Terway** in the `Network Plugin` option to enable ENI multiple IP mode support.\n2. Note that existing clusters using **Flannel** cannot be migrated to **Terway**.",
"Terraform": ""
},
"Recommendation": {
- "Text": "When creating a new cluster, select **Terway** in the `Network Plugin` option to enable ENI multiple IP mode support.\n\n**Note:** Existing clusters using Flannel cannot be migrated to Terway.",
+ "Text": "Select the **Terway** network plugin during cluster creation to enable ENI multiple IP mode. Existing clusters using **Flannel** cannot be migrated to Terway and must be recreated.",
"Url": "https://hub.prowler.com/check/cs_kubernetes_eni_multiple_ip_enabled"
}
},
diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_log_service_enabled/cs_kubernetes_log_service_enabled.metadata.json b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_log_service_enabled/cs_kubernetes_log_service_enabled.metadata.json
index 22405dd674..b0d683ca31 100644
--- a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_log_service_enabled/cs_kubernetes_log_service_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_log_service_enabled/cs_kubernetes_log_service_enabled.metadata.json
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "cs_kubernetes_log_service_enabled",
- "CheckTitle": "Log Service is set to Enabled on Kubernetes Engine Clusters",
- "CheckType": [
- "Threat detection during container runtime"
- ],
+ "CheckTitle": "Kubernetes cluster has Log Service enabled",
+ "CheckType": [],
"ServiceName": "cs",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}",
+ "ResourceIdTemplate": "",
"Severity": "high",
- "ResourceType": "AlibabaCloudKubernetesCluster",
+ "ResourceType": "ALIYUN::CS::ManagedKubernetesCluster",
"ResourceGroup": "container",
- "Description": "**Log Service** is a complete real-time data logging service on Alibaba Cloud supporting collection, shipping, search, storage, and analysis for logs.\n\nLog Service can automatically collect, process, and store your container and audit logs in a dedicated, persistent datastore. Container logs are collected from your containers, audit logs from the `kube-apiserver` or deployed ingress, and events about cluster activity such as the deletion of Pods or Secrets.",
- "Risk": "Without **Log Service** enabled, you lose visibility into container and system logs. The per-node logging agent collects: `kube-apiserver` audit logs, ingress visiting logs, and standard output/error logs from containerized processes.\n\nLack of logging makes **incident investigation**, **compliance auditing**, and **security monitoring** significantly more difficult.",
+ "Description": "**Alibaba Cloud Log Service** is a real-time data logging service that supports collection, shipping, search, storage, and analysis for container and audit logs in **Kubernetes Engine clusters**. When enabled, Log Service automatically collects `kube-apiserver` audit logs, ingress visiting logs, and standard output/error logs from containerized processes. These logs are stored in a dedicated, persistent datastore and are essential for operational visibility, security monitoring, and compliance auditing.",
+ "Risk": "Without **Log Service** enabled, there is no centralized collection of container logs, audit trails, or cluster events. This severely impairs the ability to perform **incident investigation**, **compliance auditing**, and **security monitoring**. Attackers could operate undetected within the cluster, as there would be no audit trail of API server calls, pod lifecycle events, or container output. The lack of logging directly impacts the **confidentiality** and **integrity** of the cluster by removing a critical detection and forensic capability.",
"RelatedUrl": "",
"AdditionalURLs": [
- "https://help.aliyun.com/document_detail/91406.html",
- "https://help.aliyun.com/document_detail/86532.html",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/enable-log-service.html"
+ "https://www.alibabacloud.com/help/en/ack/",
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/enable-log-service.html"
],
"Remediation": {
"Code": {
- "CLI": "aliyun cs GET /clusters/[cluster_id] to verify AuditProjectName is set. When creating a new cluster, set Enable Log Service to Enabled.",
+ "CLI": "aliyun cs GET /clusters/ --header 'Content-Type=application/json' | jq '.meta_data' | jq -r 'fromjson | .AuditProjectName'",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **ACK Console**.\n2. Select the target cluster and click its name to open the cluster detail page.\n3. Select **Cluster Auditing** on the left column and check if the audit page is shown.\n4. To enable: When creating a new cluster, set `Enable Log Service` to **Enabled**.",
"Terraform": ""
},
"Recommendation": {
- "Text": "1. Log on to the **ACK Console**\n2. Select the target cluster and click its name to open the cluster detail page\n3. Select **Cluster Auditing** on the left column and check if the audit page is shown\n4. To enable: When creating a new cluster, set `Enable Log Service` to **Enabled**",
+ "Text": "Enable **Log Service** during cluster creation by setting `Enable Log Service` to **Enabled**. For existing clusters, verify that `AuditProjectName` is configured in the cluster metadata.",
"Url": "https://hub.prowler.com/check/cs_kubernetes_log_service_enabled"
}
},
diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_network_policy_enabled/cs_kubernetes_network_policy_enabled.metadata.json b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_network_policy_enabled/cs_kubernetes_network_policy_enabled.metadata.json
index e7fddd4694..830d62e716 100644
--- a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_network_policy_enabled/cs_kubernetes_network_policy_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_network_policy_enabled/cs_kubernetes_network_policy_enabled.metadata.json
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "cs_kubernetes_network_policy_enabled",
- "CheckTitle": "Network policy is enabled on Kubernetes Engine Clusters",
- "CheckType": [
- "Threat detection during container runtime",
- "Suspicious network connection"
- ],
+ "CheckTitle": "Kubernetes cluster has Network policy enabled",
+ "CheckType": [],
"ServiceName": "cs",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudKubernetesCluster",
+ "ResourceType": "ALIYUN::CS::ManagedKubernetesCluster",
"ResourceGroup": "container",
- "Description": "A **Network Policy** is a specification of how groups of pods are allowed to communicate with each other and other network endpoints.\n\n`NetworkPolicy` resources use labels to select pods and define rules which specify what traffic is allowed. By default, pods are non-isolated and accept traffic from any source. Pods become isolated by having a NetworkPolicy that selects them.",
- "Risk": "Without **Network Policies**, all pods in a Kubernetes cluster can communicate with each other freely. This open communication model allows an attacker who compromises a single pod to potentially move **laterally** within the cluster and access sensitive services or data.\n\nNetwork Policies are essential for implementing **defense in depth** and **least privilege** networking.",
+ "Description": "**Alibaba Cloud Kubernetes Engine** clusters should have **Network Policy** support enabled through the **Terway** network plugin. A `NetworkPolicy` is a Kubernetes specification that defines how groups of pods are allowed to communicate with each other and other network endpoints using label-based selection rules. By default, pods are non-isolated and accept traffic from any source; applying NetworkPolicy resources restricts traffic to only explicitly allowed connections, enforcing the principle of least privilege at the network level.",
+ "Risk": "Without **Network Policies**, all pods in a Kubernetes cluster can communicate with each other freely, creating an unrestricted flat network. This allows an attacker who compromises a single pod to move **laterally** within the cluster, accessing sensitive services, databases, and secrets without restriction. The absence of network segmentation undermines **defense in depth** and increases the blast radius of any compromise, directly impacting the **confidentiality** and **integrity** of workloads.",
"RelatedUrl": "",
"AdditionalURLs": [
- "https://help.aliyun.com/document_detail/97621.html",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/enable-network-policy-support.html"
+ "https://www.alibabacloud.com/help/en/ack/",
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/enable-network-policy-support.html"
],
"Remediation": {
"Code": {
- "CLI": "Network Policy support (Terway) must be selected during cluster creation.",
+ "CLI": "aliyun cs GET /clusters/ --header 'Content-Type=application/json' | jq '.parameters.Network'",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. When creating a new cluster in the **ACK Console**, select **Terway** in the `Network Plugin` option to enable Network Policy support.\n2. Note that existing clusters using **Flannel** cannot be migrated to **Terway**.",
"Terraform": ""
},
"Recommendation": {
- "Text": "Only the **Terway** network plugin supports the Network Policy feature. When creating a new cluster, select **Terway** in the `Network Plugin` option.\n\n**Note:** Existing clusters using Flannel cannot be migrated to Terway.",
+ "Text": "Select the **Terway** network plugin during cluster creation to enable Network Policy support. Existing clusters using **Flannel** cannot be migrated to Terway and must be recreated.",
"Url": "https://hub.prowler.com/check/cs_kubernetes_network_policy_enabled"
}
},
diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_private_cluster_enabled/cs_kubernetes_private_cluster_enabled.metadata.json b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_private_cluster_enabled/cs_kubernetes_private_cluster_enabled.metadata.json
index 4ea2cf3893..92db7daa7a 100644
--- a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_private_cluster_enabled/cs_kubernetes_private_cluster_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_private_cluster_enabled/cs_kubernetes_private_cluster_enabled.metadata.json
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "cs_kubernetes_private_cluster_enabled",
- "CheckTitle": "Kubernetes Cluster is created with Private cluster enabled",
- "CheckType": [
- "Threat detection during container runtime",
- "Unusual logon"
- ],
+ "CheckTitle": "Kubernetes cluster is created with private cluster enabled",
+ "CheckType": [],
"ServiceName": "cs",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudKubernetesCluster",
+ "ResourceType": "ALIYUN::CS::ManagedKubernetesCluster",
"ResourceGroup": "container",
- "Description": "A **private cluster** is a cluster that makes your master inaccessible from the public internet.\n\nIn a private cluster, nodes do not have public IP addresses, so your workloads run in an environment that is isolated from the internet. Nodes and masters communicate with each other privately using **VPC peering**.",
- "Risk": "Exposing the **API server endpoint** to the public internet increases the attack surface of your cluster. Attackers can attempt to probe for vulnerabilities, perform **brute force attacks**, or exploit misconfigurations if the API server is publicly accessible.\n\nUsing a private cluster significantly reduces network security risks.",
+ "Description": "**Alibaba Cloud Kubernetes Engine** clusters should be configured as **private clusters** to ensure that the API server endpoint is not accessible from the public internet. In a private cluster, nodes do not have public IP addresses and all communication between nodes and the master occurs privately through **VPC peering**. This significantly reduces the attack surface by eliminating direct internet exposure of the cluster's control plane and worker nodes.",
+ "Risk": "Exposing the **API server endpoint** to the public internet increases the attack surface of the cluster, allowing attackers to probe for vulnerabilities, perform **brute force attacks** against authentication, or exploit misconfigurations. A publicly accessible API server can be targeted by automated scanning tools and botnets, increasing the risk of unauthorized access. This directly impacts the **confidentiality** and **integrity** of the cluster by potentially allowing attackers to execute commands, deploy malicious workloads, or exfiltrate sensitive data.",
"RelatedUrl": "",
"AdditionalURLs": [
- "https://help.aliyun.com/document_detail/100380.html",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/private-cluster.html"
+ "https://www.alibabacloud.com/help/en/ack/",
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/private-cluster.html"
],
"Remediation": {
"Code": {
- "CLI": "Public access settings cannot be easily changed for existing clusters. Ensure Public Access is disabled during creation.",
+ "CLI": "aliyun cs GET /clusters/ --header 'Content-Type=application/json' | jq '.external_loadbalancer_id'",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **ACK Console**.\n2. Select the target cluster name and go to the cluster detail page.\n3. Check if there is no `API Server Public Network Endpoint` under Cluster Information.\n4. When creating a new cluster, make sure **Public Access** is not enabled.",
"Terraform": ""
},
"Recommendation": {
- "Text": "1. Log on to the **ACK Console**\n2. Select the target cluster name and go to the cluster detail page\n3. Check if there is no `API Server Public Network Endpoint` under Cluster Information\n4. When creating a new cluster, make sure **Public Access** is not enabled",
+ "Text": "Disable **Public Access** during cluster creation to ensure the API server is not exposed to the public internet. For existing clusters, remove the public endpoint if one was configured.",
"Url": "https://hub.prowler.com/check/cs_kubernetes_private_cluster_enabled"
}
},
diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_rbac_enabled/cs_kubernetes_rbac_enabled.metadata.json b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_rbac_enabled/cs_kubernetes_rbac_enabled.metadata.json
index bd4fa293ce..c329225cb7 100644
--- a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_rbac_enabled/cs_kubernetes_rbac_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_rbac_enabled/cs_kubernetes_rbac_enabled.metadata.json
@@ -1,34 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "cs_kubernetes_rbac_enabled",
- "CheckTitle": "Role-based access control (RBAC) authorization is Enabled on Kubernetes Engine Clusters",
- "CheckType": [
- "Threat detection during container runtime",
- "Abnormal account"
- ],
+ "CheckTitle": "Kubernetes cluster has RBAC authorization enabled",
+ "CheckType": [],
"ServiceName": "cs",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}",
+ "ResourceIdTemplate": "",
"Severity": "high",
- "ResourceType": "AlibabaCloudKubernetesCluster",
+ "ResourceType": "ALIYUN::CS::ManagedKubernetesCluster",
"ResourceGroup": "container",
- "Description": "In Kubernetes, authorizers interact by granting a permission if any authorizer grants the permission. The legacy authorizer in Kubernetes Engine grants broad, statically defined permissions.\n\nTo ensure that **RBAC** limits permissions correctly, you must disable the legacy authorizer. RBAC has significant security advantages, helps ensure that users only have access to specific cluster resources within their own namespace, and is now stable in Kubernetes.",
- "Risk": "In Kubernetes, **RBAC** is used to grant permissions to resources at the cluster and namespace level. RBAC allows you to define roles with rules containing a set of permissions.\n\nWithout RBAC, legacy authorization mechanisms like **ABAC** grant **overly broad permissions**, increasing the risk of unauthorized access and privilege escalation.",
+ "Description": "**Alibaba Cloud Kubernetes Engine** clusters should have **Role-Based Access Control (RBAC)** enabled to enforce fine-grained authorization for cluster resources. RBAC allows administrators to define roles with specific permissions at both the cluster and namespace level, ensuring that users and service accounts only have access to the resources they need. The legacy **ABAC** (Attribute-Based Access Control) authorizer grants broad, statically defined permissions and should be disabled in favor of RBAC for improved security.",
+ "Risk": "Without **RBAC** enabled, Kubernetes clusters may rely on legacy authorization mechanisms such as **ABAC**, which grant **overly broad permissions** that cannot be scoped to specific namespaces or resource types. This increases the risk of **unauthorized access** and **privilege escalation**, where a compromised service account or user could gain access to sensitive resources across the entire cluster. The lack of granular access control directly impacts the **confidentiality** and **integrity** of workloads and secrets stored in the cluster.",
"RelatedUrl": "",
"AdditionalURLs": [
- "https://help.aliyun.com/document_detail/87656.html",
- "https://help.aliyun.com/document_detail/119596.html",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/enable-rbac-authorization.html"
+ "https://www.alibabacloud.com/help/en/ack/",
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/enable-rbac-authorization.html"
],
"Remediation": {
"Code": {
- "CLI": "RBAC is enabled by default on new ACK clusters. Verify cluster authorization configuration.",
+ "CLI": "aliyun cs GET /clusters/ --header 'Content-Type=application/json' | jq '.parameters.KubernetesVersion'",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **ACK Console**.\n2. Navigate to **Clusters** -> **Authorizations** page.\n3. Select the target RAM sub-account and configure the RBAC roles on specific clusters or namespaces.\n4. Ensure **RBAC** is enabled and legacy ABAC authorization is disabled.",
"Terraform": ""
},
"Recommendation": {
- "Text": "1. Log on to the **ACK Console**\n2. Navigate to **Clusters** -> **Authorizations** page\n3. Select the target RAM sub-account and configure the RBAC roles on specific clusters or namespaces\n4. Ensure **RBAC** is enabled and legacy ABAC authorization is disabled",
+ "Text": "Ensure **RBAC** is enabled on all Kubernetes Engine clusters and that legacy **ABAC** authorization is disabled. Configure RBAC roles and bindings through the ACK Console Authorizations page to enforce least-privilege access.",
"Url": "https://hub.prowler.com/check/cs_kubernetes_rbac_enabled"
}
},
diff --git a/prowler/providers/alibabacloud/services/ecs/ecs_attached_disk_encrypted/ecs_attached_disk_encrypted.metadata.json b/prowler/providers/alibabacloud/services/ecs/ecs_attached_disk_encrypted/ecs_attached_disk_encrypted.metadata.json
index b6d14c101b..2a9f51c0b9 100644
--- a/prowler/providers/alibabacloud/services/ecs/ecs_attached_disk_encrypted/ecs_attached_disk_encrypted.metadata.json
+++ b/prowler/providers/alibabacloud/services/ecs/ecs_attached_disk_encrypted/ecs_attached_disk_encrypted.metadata.json
@@ -1,32 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "ecs_attached_disk_encrypted",
- "CheckTitle": "Virtual Machines disk are encrypted",
- "CheckType": [
- "Sensitive file tampering"
- ],
+ "CheckTitle": "ECS attached disk is encrypted",
+ "CheckType": [],
"ServiceName": "ecs",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:ecs:region:account-id:disk/{disk-id}",
+ "ResourceIdTemplate": "",
"Severity": "high",
- "ResourceType": "AlibabaCloudECSDisk",
+ "ResourceType": "ALIYUN::ECS::Disk",
"ResourceGroup": "storage",
- "Description": "**ECS cloud disk encryption** protects your data at rest. The cloud disk data encryption feature automatically encrypts data when data is transferred from ECS instances to disks, and decrypts data when read from disks.\n\nEnsure that disks are encrypted when they are created with the creation of VM instances.",
- "Risk": "**Unencrypted disks** attached to ECS instances pose a security risk as they may contain sensitive data that could be accessed if the disk is compromised or accessed by unauthorized parties.\n\nData at rest without encryption is vulnerable to **unauthorized access** if storage media is lost, stolen, or improperly decommissioned.",
+ "Description": "**Alibaba Cloud ECS cloud disk encryption** protects data at rest by automatically encrypting data when it is transferred from ECS instances to disks and decrypting it when read. Ensuring that all attached disks are encrypted helps prevent unauthorized access to sensitive data stored on the disk. This check verifies that **disk encryption** is enabled on all ECS disks attached to instances, using **KMS** (Key Management Service) for key management.",
+ "Risk": "**Unencrypted disks** attached to ECS instances pose a significant security risk, as sensitive data could be exposed if the disk is compromised, improperly decommissioned, or accessed by unauthorized parties. Data at rest without encryption is vulnerable to **unauthorized access**, impacting **confidentiality** and potentially leading to **data breaches** or **regulatory non-compliance**.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/59643.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ECS/encrypt-vm-instance-disks.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ECS/encrypt-vm-instance-disks.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ecs CreateDisk --DiskName --Size --Encrypted true --KmsKeyId ",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **ECS Console** > **Instances & Images** > **Images**.\n2. Select the **Custom Image** tab and select the target image.\n3. Click **Copy Image** and check the **Encrypt** box.\n4. Select a key and click **OK**.\n5. For data disks, go to **Instances** > **Create Instance**, in the Storage section click **Add Disk**, select **Disk Encryption**, and choose a key.\n\n**Note:** You cannot directly convert unencrypted disks to encrypted disks.",
"Terraform": "resource \"alicloud_ecs_disk\" \"encrypted\" {\n zone_id = \"cn-hangzhou-a\"\n disk_name = \"encrypted-disk\"\n category = \"cloud_efficiency\"\n size = 20\n encrypted = true\n kms_key_id = alicloud_kms_key.example.id\n}"
},
"Recommendation": {
- "Text": "**Encrypt a system disk when copying an image:**\n1. Log on to the **ECS Console** > **Instances & Images** > **Images**\n2. Select the **Custom Image** tab and select target image\n3. Click **Copy Image** and check the **Encrypt** box\n4. Select a key and click **OK**\n\n**Encrypt a data disk when creating an instance:**\n1. Log on to the **ECS Console** > **Instances & Images** > **Instances** > **Create Instance**\n2. In the Storage section, click **Add Disk**\n3. Select **Disk Encryption** and choose a key\n\n**Note:** You cannot directly convert unencrypted disks to encrypted disks.",
+ "Text": "Enable encryption on all ECS disks to protect data at rest. Use KMS-managed keys for encryption. Note that existing unencrypted disks cannot be directly converted; data must be migrated to new encrypted disks.",
"Url": "https://hub.prowler.com/check/ecs_attached_disk_encrypted"
}
},
diff --git a/prowler/providers/alibabacloud/services/ecs/ecs_instance_endpoint_protection_installed/ecs_instance_endpoint_protection_installed.metadata.json b/prowler/providers/alibabacloud/services/ecs/ecs_instance_endpoint_protection_installed/ecs_instance_endpoint_protection_installed.metadata.json
index d2ad113899..d018b97768 100644
--- a/prowler/providers/alibabacloud/services/ecs/ecs_instance_endpoint_protection_installed/ecs_instance_endpoint_protection_installed.metadata.json
+++ b/prowler/providers/alibabacloud/services/ecs/ecs_instance_endpoint_protection_installed/ecs_instance_endpoint_protection_installed.metadata.json
@@ -1,35 +1,29 @@
{
"Provider": "alibabacloud",
"CheckID": "ecs_instance_endpoint_protection_installed",
- "CheckTitle": "The endpoint protection for all Virtual Machines is installed",
- "CheckType": [
- "Suspicious process",
- "Webshell",
- "Unusual logon",
- "Sensitive file tampering",
- "Malicious software"
- ],
+ "CheckTitle": "ECS instance has endpoint protection installed",
+ "CheckType": [],
"ServiceName": "ecs",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:ecs:region:account-id:instance/{instance-id}",
+ "ResourceIdTemplate": "",
"Severity": "high",
- "ResourceType": "AlibabaCloudECSInstance",
+ "ResourceType": "ALIYUN::ECS::Instance",
"ResourceGroup": "compute",
- "Description": "Installing **endpoint protection systems** (like **Security Center** for Alibaba Cloud) provides real-time protection capability that helps identify and remove viruses, spyware, and other malicious software.\n\nConfigurable alerts notify when known malicious software attempts to install itself or run on ECS instances.",
- "Risk": "ECS instances without **endpoint protection** are vulnerable to **malware**, **viruses**, and other security threats.\n\nEndpoint protection provides real-time monitoring and protection capabilities essential for detecting and preventing security incidents.",
+ "Description": "**Alibaba Cloud Security Center** provides endpoint protection for ECS instances, offering real-time detection and removal of viruses, spyware, and other malicious software. This check verifies that the **Security Center agent** is installed and active on all ECS instances, ensuring configurable alerts notify administrators when known malicious software attempts to install itself or execute on the instance.",
+ "Risk": "ECS instances without **endpoint protection** are vulnerable to **malware**, **viruses**, **webshells**, and other security threats that can compromise **confidentiality**, **integrity**, and **availability**. Without real-time monitoring, security incidents may go undetected, allowing attackers to maintain persistent access and exfiltrate sensitive data.",
"RelatedUrl": "",
"AdditionalURLs": [
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ECS/enable-endpoint-protection.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ECS/enable-endpoint-protection.html"
],
"Remediation": {
"Code": {
- "CLI": "Logon to Security Center Console > Select Settings > Click Agent > Select virtual machines without Security Center agent > Click Install",
+ "CLI": "aliyun sas InstallBackupClient --Uuid ",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **Security Center Console**.\n2. Select **Settings**.\n3. Click **Agent**.\n4. On the Agent tab, select the virtual machines without Security Center agent installed.\n5. Click **Install**.",
"Terraform": ""
},
"Recommendation": {
- "Text": "1. Log on to the **Security Center Console**\n2. Select **Settings**\n3. Click **Agent**\n4. On the Agent tab, select the virtual machines without Security Center agent installed\n5. Click **Install**",
+ "Text": "Install the Alibaba Cloud **Security Center** agent on all ECS instances to enable real-time endpoint protection, malware detection, and vulnerability scanning.",
"Url": "https://hub.prowler.com/check/ecs_instance_endpoint_protection_installed"
}
},
diff --git a/prowler/providers/alibabacloud/services/ecs/ecs_instance_latest_os_patches_applied/ecs_instance_latest_os_patches_applied.metadata.json b/prowler/providers/alibabacloud/services/ecs/ecs_instance_latest_os_patches_applied/ecs_instance_latest_os_patches_applied.metadata.json
index 21222b9058..d667848b18 100644
--- a/prowler/providers/alibabacloud/services/ecs/ecs_instance_latest_os_patches_applied/ecs_instance_latest_os_patches_applied.metadata.json
+++ b/prowler/providers/alibabacloud/services/ecs/ecs_instance_latest_os_patches_applied/ecs_instance_latest_os_patches_applied.metadata.json
@@ -1,32 +1,29 @@
{
"Provider": "alibabacloud",
"CheckID": "ecs_instance_latest_os_patches_applied",
- "CheckTitle": "The latest OS Patches for all Virtual Machines are applied",
- "CheckType": [
- "Malicious software",
- "Web application threat detection"
- ],
+ "CheckTitle": "ECS instance has latest OS patches applied",
+ "CheckType": [],
"ServiceName": "ecs",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:ecs:region:account-id:instance/{instance-id}",
+ "ResourceIdTemplate": "",
"Severity": "high",
- "ResourceType": "AlibabaCloudECSInstance",
+ "ResourceType": "ALIYUN::ECS::Instance",
"ResourceGroup": "compute",
- "Description": "Windows and Linux virtual machines should be kept updated to address specific bugs or flaws, improve OS or application's general stability, and fix **security vulnerabilities**.\n\nThe Alibaba Cloud **Security Center** checks for the latest updates in Linux and Windows systems.",
- "Risk": "**Unpatched systems** are vulnerable to known security exploits and may be compromised by attackers.\n\nKeeping systems updated with the latest patches is critical for maintaining security and preventing **exploitation of known vulnerabilities**.",
+ "Description": "**Alibaba Cloud Security Center** checks for the latest updates in Linux and Windows systems running on ECS instances. Keeping virtual machines updated with the latest OS patches addresses specific bugs, improves general stability, and fixes **security vulnerabilities**. This check verifies that all known vulnerabilities detected by Security Center have been patched on each ECS instance.",
+ "Risk": "**Unpatched systems** are vulnerable to known security exploits and can be compromised by attackers leveraging publicly disclosed vulnerabilities. Failure to apply patches in a timely manner increases the risk of **unauthorized access**, **malware infection**, and **data breaches**, impacting the **confidentiality**, **integrity**, and **availability** of the system.",
"RelatedUrl": "",
"AdditionalURLs": [
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ECS/apply-latest-os-patches.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ECS/apply-latest-os-patches.html"
],
"Remediation": {
"Code": {
- "CLI": "Logon to Security Center Console > Select Vulnerabilities > Apply all patches for vulnerabilities",
+ "CLI": "aliyun sas FixCheckWarnings --CheckIds ",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **Security Center Console**.\n2. Select **Vulnerabilities** in the left-side navigation pane.\n3. Review all detected vulnerabilities.\n4. Apply all available patches for the reported vulnerabilities.\n5. Verify that vulnerabilities are resolved after patching.",
"Terraform": ""
},
"Recommendation": {
- "Text": "1. Log on to the **Security Center Console**\n2. Select **Vulnerabilities**\n3. Ensure all vulnerabilities are fixed\n4. Apply all patches for vulnerabilities",
+ "Text": "Regularly review and apply OS patches on all ECS instances using the **Alibaba Cloud Security Center** vulnerability management feature to maintain a strong security posture.",
"Url": "https://hub.prowler.com/check/ecs_instance_latest_os_patches_applied"
}
},
diff --git a/prowler/providers/alibabacloud/services/ecs/ecs_instance_no_legacy_network/ecs_instance_no_legacy_network.metadata.json b/prowler/providers/alibabacloud/services/ecs/ecs_instance_no_legacy_network/ecs_instance_no_legacy_network.metadata.json
index 1d3eee3847..5da71e02cf 100644
--- a/prowler/providers/alibabacloud/services/ecs/ecs_instance_no_legacy_network/ecs_instance_no_legacy_network.metadata.json
+++ b/prowler/providers/alibabacloud/services/ecs/ecs_instance_no_legacy_network/ecs_instance_no_legacy_network.metadata.json
@@ -1,32 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "ecs_instance_no_legacy_network",
- "CheckTitle": "Legacy networks does not exist",
- "CheckType": [
- "Suspicious network connection"
- ],
+ "CheckTitle": "ECS instance does not use legacy network",
+ "CheckType": [],
"ServiceName": "ecs",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:ecs:region:account-id:instance/{instance-id}",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudECSInstance",
+ "ResourceType": "ALIYUN::ECS::Instance",
"ResourceGroup": "compute",
- "Description": "In order to prevent use of **legacy networks**, ECS instances should not have a legacy network configured.\n\nLegacy networks have a single network IPv4 prefix range and a single gateway IP address for the whole network. With legacy networks, you cannot create subnetworks or switch from legacy to auto or custom subnet networks.",
- "Risk": "**Legacy networks** can have an impact on high network traffic ECS instances and are subject to a **single point of failure**.\n\nThey also lack the security isolation and network segmentation capabilities provided by **VPCs**.",
+ "Description": "**Alibaba Cloud ECS instances** should use **VPC (Virtual Private Cloud)** networks instead of legacy classic networks. Legacy networks have a single IPv4 prefix range and a single gateway IP address for the whole network, preventing the creation of subnetworks or migration to auto/custom subnet networks. This check verifies that no ECS instances are configured with a legacy network type.",
+ "Risk": "**Legacy networks** lack the security isolation and network segmentation capabilities provided by **VPCs**, creating a **single point of failure** for high-traffic instances. Without proper network segmentation, lateral movement by attackers becomes easier, impacting **confidentiality** and **integrity** of workloads sharing the same flat network.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/87190.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-VPC/legacy-network-usage.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-VPC/legacy-network-usage.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ecs CreateInstance --InstanceName --ImageId --InstanceType --VSwitchId ",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **ECS Console**.\n2. In the left-side navigation pane, choose **Instance & Image** > **Instances**.\n3. Click **Create Instance**.\n4. Specify the basic instance information and click **Next: Networking**.\n5. Select **VPC** as the Network Type and choose an appropriate VSwitch.",
"Terraform": ""
},
"Recommendation": {
- "Text": "1. Log on to the **ECS Console**\n2. In the left-side navigation pane, choose **Instance & Image** > **Instances**\n3. Click **Create Instance**\n4. Specify the basic instance information required and click **Next: Networking**\n5. Select the Network Type of **VPC**",
+ "Text": "Migrate all ECS instances from legacy classic networks to **VPC** networks. Create new instances within a VPC and migrate workloads from legacy network instances.",
"Url": "https://hub.prowler.com/check/ecs_instance_no_legacy_network"
}
},
diff --git a/prowler/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_rdp_internet/ecs_securitygroup_restrict_rdp_internet.metadata.json b/prowler/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_rdp_internet/ecs_securitygroup_restrict_rdp_internet.metadata.json
index cf61b5255e..855d63279f 100644
--- a/prowler/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_rdp_internet/ecs_securitygroup_restrict_rdp_internet.metadata.json
+++ b/prowler/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_rdp_internet/ecs_securitygroup_restrict_rdp_internet.metadata.json
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "ecs_securitygroup_restrict_rdp_internet",
- "CheckTitle": "RDP access is restricted from the internet",
- "CheckType": [
- "Unusual logon",
- "Suspicious network connection"
- ],
+ "CheckTitle": "Security group restricts RDP access from the internet",
+ "CheckType": [],
"ServiceName": "ecs",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:ecs:region:account-id:security-group/{security-group-id}",
+ "ResourceIdTemplate": "",
"Severity": "high",
- "ResourceType": "AlibabaCloudECSSecurityGroup",
+ "ResourceType": "ALIYUN::ECS::SecurityGroup",
"ResourceGroup": "network",
- "Description": "**Security groups** provide stateful filtering of ingress/egress network traffic to Alibaba Cloud resources.\n\nIt is recommended that no security group allows unrestricted ingress access to port **3389 (RDP)**.",
- "Risk": "Removing unfettered connectivity to remote console services, such as **RDP**, reduces a server's exposure to risk.\n\nUnrestricted RDP access from the internet (`0.0.0.0/0`) exposes systems to **brute force attacks**, **credential stuffing**, and **exploitation of RDP vulnerabilities**.",
+ "Description": "**Alibaba Cloud ECS security groups** provide stateful filtering of ingress and egress network traffic to cloud resources. This check verifies that no security group allows unrestricted ingress access to port **3389** (RDP) from the internet (`0.0.0.0/0` or `::/0`). Restricting RDP access to trusted IP addresses significantly reduces the attack surface of ECS instances.",
+ "Risk": "Unrestricted **RDP access** from the internet (`0.0.0.0/0`) exposes systems to **brute force attacks**, **credential stuffing**, and **exploitation of RDP vulnerabilities** such as BlueKeep. This can lead to **unauthorized access**, **data exfiltration**, and full system compromise, impacting **confidentiality**, **integrity**, and **availability**.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/25387.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ECS/unrestricted-rdp-access.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ECS/unrestricted-rdp-access.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ecs RevokeSecurityGroup --SecurityGroupId --IpProtocol tcp --PortRange 3389/3389 --SourceCidrIp 0.0.0.0/0",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **ECS Console**.\n2. In the left-side navigation pane, choose **Network & Security** > **Security Groups**.\n3. Find the target security group and click **Add Rules**.\n4. Locate the rule allowing port `3389` from `0.0.0.0/0`.\n5. Modify the Source IP range to a specific trusted IP or CIDR block.\n6. Click **Save**.",
"Terraform": "resource \"alicloud_security_group_rule\" \"deny_rdp_internet\" {\n type = \"ingress\"\n ip_protocol = \"tcp\"\n port_range = \"3389/3389\"\n security_group_id = alicloud_security_group.example.id\n cidr_ip = \"10.0.0.0/8\" # Restrict to internal network\n policy = \"accept\"\n}"
},
"Recommendation": {
- "Text": "1. Log on to the **ECS Console**\n2. In the left-side navigation pane, choose **Network & Security** > **Security Groups**\n3. Find the Security Group you want to modify\n4. Modify Source IP range to specific IP instead of `0.0.0.0/0`\n5. Click **Save**",
+ "Text": "Restrict RDP (port **3389**) access in security groups to only trusted IP addresses or CIDR blocks. Remove any rules allowing access from `0.0.0.0/0` or `::/0`.",
"Url": "https://hub.prowler.com/check/ecs_securitygroup_restrict_rdp_internet"
}
},
diff --git a/prowler/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_ssh_internet/ecs_securitygroup_restrict_ssh_internet.metadata.json b/prowler/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_ssh_internet/ecs_securitygroup_restrict_ssh_internet.metadata.json
index 136687ed7f..19c2b5e7f3 100644
--- a/prowler/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_ssh_internet/ecs_securitygroup_restrict_ssh_internet.metadata.json
+++ b/prowler/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_ssh_internet/ecs_securitygroup_restrict_ssh_internet.metadata.json
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "ecs_securitygroup_restrict_ssh_internet",
- "CheckTitle": "SSH access is restricted from the internet",
- "CheckType": [
- "Unusual logon",
- "Suspicious network connection"
- ],
+ "CheckTitle": "Security group restricts SSH access from the internet",
+ "CheckType": [],
"ServiceName": "ecs",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:ecs:region:account-id:security-group/{security-group-id}",
+ "ResourceIdTemplate": "",
"Severity": "high",
- "ResourceType": "AlibabaCloudECSSecurityGroup",
+ "ResourceType": "ALIYUN::ECS::SecurityGroup",
"ResourceGroup": "network",
- "Description": "**Security groups** provide stateful filtering of ingress/egress network traffic to Alibaba Cloud resources.\n\nIt is recommended that no security group allows unrestricted ingress access to port **22 (SSH)**.",
- "Risk": "Removing unfettered connectivity to remote console services, such as **SSH**, reduces a server's exposure to risk.\n\nUnrestricted SSH access from the internet (`0.0.0.0/0`) exposes systems to **brute force attacks**, **credential stuffing**, and **exploitation of SSH vulnerabilities**.",
+ "Description": "**Alibaba Cloud ECS security groups** provide stateful filtering of ingress and egress network traffic to cloud resources. This check verifies that no security group allows unrestricted ingress access to port **22** (SSH) from the internet (`0.0.0.0/0` or `::/0`). Restricting SSH access to trusted IP addresses significantly reduces the attack surface of ECS instances.",
+ "Risk": "Unrestricted **SSH access** from the internet (`0.0.0.0/0`) exposes systems to **brute force attacks**, **credential stuffing**, and **exploitation of SSH vulnerabilities**. This can lead to **unauthorized access**, **data exfiltration**, and full system compromise, impacting **confidentiality**, **integrity**, and **availability**.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/25387.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ECS/unrestricted-ssh-access.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ECS/unrestricted-ssh-access.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ecs RevokeSecurityGroup --SecurityGroupId --IpProtocol tcp --PortRange 22/22 --SourceCidrIp 0.0.0.0/0",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **ECS Console**.\n2. In the left-side navigation pane, choose **Network & Security** > **Security Groups**.\n3. Find the target security group and click **Add Rules**.\n4. Locate the rule allowing port `22` from `0.0.0.0/0`.\n5. Modify the Source IP range to a specific trusted IP or CIDR block.\n6. Click **Save**.",
"Terraform": "resource \"alicloud_security_group_rule\" \"deny_ssh_internet\" {\n type = \"ingress\"\n ip_protocol = \"tcp\"\n port_range = \"22/22\"\n security_group_id = alicloud_security_group.example.id\n cidr_ip = \"10.0.0.0/8\" # Restrict to internal network\n policy = \"accept\"\n}"
},
"Recommendation": {
- "Text": "1. Log on to the **ECS Console**\n2. In the left-side navigation pane, choose **Network & Security** > **Security Groups**\n3. Find the Security Group you want to modify\n4. Modify Source IP range to specific IP instead of `0.0.0.0/0`\n5. Click **Save**",
+ "Text": "Restrict SSH (port **22**) access in security groups to only trusted IP addresses or CIDR blocks. Remove any rules allowing access from `0.0.0.0/0` or `::/0`.",
"Url": "https://hub.prowler.com/check/ecs_securitygroup_restrict_ssh_internet"
}
},
diff --git a/prowler/providers/alibabacloud/services/ecs/ecs_unattached_disk_encrypted/ecs_unattached_disk_encrypted.metadata.json b/prowler/providers/alibabacloud/services/ecs/ecs_unattached_disk_encrypted/ecs_unattached_disk_encrypted.metadata.json
index ed2daedbde..0808a219f0 100644
--- a/prowler/providers/alibabacloud/services/ecs/ecs_unattached_disk_encrypted/ecs_unattached_disk_encrypted.metadata.json
+++ b/prowler/providers/alibabacloud/services/ecs/ecs_unattached_disk_encrypted/ecs_unattached_disk_encrypted.metadata.json
@@ -1,32 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "ecs_unattached_disk_encrypted",
- "CheckTitle": "Unattached disks are encrypted",
- "CheckType": [
- "Sensitive file tampering"
- ],
+ "CheckTitle": "ECS unattached disk is encrypted",
+ "CheckType": [],
"ServiceName": "ecs",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:ecs:region:account-id:disk/{disk-id}",
+ "ResourceIdTemplate": "",
"Severity": "high",
- "ResourceType": "AlibabaCloudECSDisk",
+ "ResourceType": "ALIYUN::ECS::Disk",
"ResourceGroup": "storage",
- "Description": "**Cloud disk encryption** protects your data at rest. The cloud disk data encryption feature automatically encrypts data when data is transferred from ECS instances to disks, and decrypts data when read from disks.",
- "Risk": "**Unencrypted unattached disks** pose a security risk as they may contain sensitive data that could be accessed if the disk is compromised or accessed by unauthorized parties.\n\nUnattached disks are especially vulnerable as they may be forgotten or not monitored, increasing the risk of **unauthorized access**.",
+ "Description": "**Alibaba Cloud ECS cloud disk encryption** protects data at rest by automatically encrypting data when it is transferred from ECS instances to disks and decrypting it when read. This check verifies that unattached (detached) disks have encryption enabled, since unattached disks may still contain sensitive data from previous workloads and are especially vulnerable if not properly managed.",
+ "Risk": "**Unencrypted unattached disks** pose a significant security risk as they may contain sensitive data that could be accessed if the disk is compromised or accessed by unauthorized parties. Unattached disks are especially vulnerable as they may be overlooked in security monitoring, increasing the risk of **unauthorized access** and **data breaches** that impact **confidentiality**.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/59643.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ECS/encrypt-unattached-disks.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ECS/encrypt-unattached-disks.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ecs CreateDisk --DiskName --Size --Encrypted true --KmsKeyId ",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **ECS Console**.\n2. In the left-side navigation pane, choose **Storage & Snapshots** > **Disk**.\n3. In the upper-right corner of the Disks page, click **Create Disk**.\n4. In the Disk section, check the **Disk Encryption** box and select a key from the drop-down list.\n\n**Note:** After a data disk is created, you can only encrypt it by manually copying data from the unencrypted disk to a new encrypted disk.",
"Terraform": "resource \"alicloud_ecs_disk\" \"encrypted\" {\n zone_id = \"cn-hangzhou-a\"\n disk_name = \"encrypted-disk\"\n category = \"cloud_efficiency\"\n size = 20\n encrypted = true\n kms_key_id = alicloud_kms_key.example.id\n}"
},
"Recommendation": {
- "Text": "1. Log on to the **ECS Console**\n2. In the left-side navigation pane, choose **Storage & Snapshots** > **Disk**\n3. In the upper-right corner of the Disks page, click **Create Disk**\n4. In the Disk section, check the **Disk Encryption** box and select a key from the drop-down list\n\n**Note:** After a data disk is created, you can only encrypt the data disk by manually copying data from the unencrypted disk to a new encrypted disk.",
+ "Text": "Ensure all unattached ECS disks are encrypted. Create new encrypted disks and migrate data from unencrypted disks, then delete the unencrypted originals.",
"Url": "https://hub.prowler.com/check/ecs_unattached_disk_encrypted"
}
},
diff --git a/prowler/providers/alibabacloud/services/oss/oss_bucket_logging_enabled/oss_bucket_logging_enabled.metadata.json b/prowler/providers/alibabacloud/services/oss/oss_bucket_logging_enabled/oss_bucket_logging_enabled.metadata.json
index 23fce0f862..934eee4dbf 100644
--- a/prowler/providers/alibabacloud/services/oss/oss_bucket_logging_enabled/oss_bucket_logging_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/oss/oss_bucket_logging_enabled/oss_bucket_logging_enabled.metadata.json
@@ -2,32 +2,29 @@
"Provider": "alibabacloud",
"CheckID": "oss_bucket_logging_enabled",
"CheckTitle": "Logging is enabled for OSS buckets",
- "CheckType": [
- "Sensitive file tampering",
- "Cloud threat detection"
- ],
+ "CheckType": [],
"ServiceName": "oss",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:oss::account-id:bucket-name",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudOSSBucket",
+ "ResourceType": "ALIYUN::OSS::Bucket",
"ResourceGroup": "storage",
- "Description": "**OSS Bucket Access Logging** generates a log that contains access records for each request made to your OSS bucket.\n\nAn access log record contains details about the request, such as the request type, the resources specified in the request, and the time and date the request was processed. It is recommended that bucket access logging be enabled on OSS buckets.",
- "Risk": "By enabling **OSS bucket logging** on target OSS buckets, it is possible to capture all events which may affect objects within target buckets.\n\nConfiguring logs to be placed in a separate bucket allows access to log information useful in **security** and **incident response** workflows.",
+ "Description": "**Alibaba Cloud OSS Bucket Access Logging** generates a log record for each request made to your OSS bucket, containing details such as the request type, the resources specified, and the time and date the request was processed. Enabling bucket access logging on all OSS buckets ensures that access patterns are recorded and available for security analysis and incident response workflows.",
+ "Risk": "Without **OSS bucket logging** enabled, access events affecting objects within target buckets are not captured. This limits the ability to perform **security analysis**, **incident response**, and **forensic investigations**, as there is no record of who accessed or modified stored data.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/31900.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-OSS/enable-bucket-access-logging.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-OSS/enable-bucket-access-logging.html"
],
"Remediation": {
"Code": {
"CLI": "ossutil logging --method put oss:// --target-bucket --target-prefix ",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **OSS Console**\n2. In the bucket-list pane, click on a target OSS bucket\n3. Under **Log**, click **Configure**\n4. Click the **Enabled** checkbox\n5. Select `Target Bucket` from the list\n6. Enter a `Target Prefix`\n7. Click **Save**",
"Terraform": "resource \"alicloud_oss_bucket_logging\" \"example\" {\n bucket = alicloud_oss_bucket.example.bucket\n target_bucket = alicloud_oss_bucket.log_bucket.bucket\n target_prefix = \"log/\"\n}"
},
"Recommendation": {
- "Text": "1. Log on to the **OSS Console**\n2. In the bucket-list pane, click on a target OSS bucket\n3. Under **Log**, click **Configure**\n4. Click the **Enabled** checkbox\n5. Select `Target Bucket` from the list\n6. Enter a `Target Prefix`\n7. Click **Save**",
+ "Text": "Enable access logging on all OSS buckets and configure logs to be stored in a separate dedicated bucket for security analysis and compliance auditing.",
"Url": "https://hub.prowler.com/check/oss_bucket_logging_enabled"
}
},
diff --git a/prowler/providers/alibabacloud/services/oss/oss_bucket_not_publicly_accessible/oss_bucket_not_publicly_accessible.metadata.json b/prowler/providers/alibabacloud/services/oss/oss_bucket_not_publicly_accessible/oss_bucket_not_publicly_accessible.metadata.json
index 8ba7ef19db..748e14b666 100644
--- a/prowler/providers/alibabacloud/services/oss/oss_bucket_not_publicly_accessible/oss_bucket_not_publicly_accessible.metadata.json
+++ b/prowler/providers/alibabacloud/services/oss/oss_bucket_not_publicly_accessible/oss_bucket_not_publicly_accessible.metadata.json
@@ -2,32 +2,29 @@
"Provider": "alibabacloud",
"CheckID": "oss_bucket_not_publicly_accessible",
"CheckTitle": "OSS bucket is not anonymously or publicly accessible",
- "CheckType": [
- "Sensitive file tampering",
- "Cloud threat detection"
- ],
+ "CheckType": [],
"ServiceName": "oss",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:oss::account-id:bucket-name",
+ "ResourceIdTemplate": "",
"Severity": "critical",
- "ResourceType": "AlibabaCloudOSSBucket",
+ "ResourceType": "ALIYUN::OSS::Bucket",
"ResourceGroup": "storage",
- "Description": "A bucket is a container used to store objects in **Object Storage Service (OSS)**. All objects in OSS are stored in buckets.\n\nIt is recommended that the access policy on OSS buckets does not allow **anonymous** and/or **public access**.",
- "Risk": "Allowing **anonymous** and/or **public access** grants permissions to anyone to access bucket content. Such access might not be desired if you are storing any sensitive data.\n\nPublic buckets can lead to **data breaches**, **unauthorized data access**, and **compliance violations**.",
+ "Description": "**Alibaba Cloud Object Storage Service (OSS)** buckets store objects that may contain sensitive data. It is recommended that the access policy on OSS buckets does not allow **anonymous** or **public access**, ensuring that only authorized identities can interact with bucket contents. The bucket ACL should be set to `private` to prevent unintended data exposure.",
+ "Risk": "Allowing **anonymous** or **public access** to OSS buckets grants permissions to anyone on the internet to read or modify bucket content. This can lead to **data breaches**, **unauthorized data exfiltration**, **data tampering**, and **compliance violations**, particularly when buckets contain sensitive or regulated information.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/31896.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-OSS/publicly-accessible-oss-bucket.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-OSS/publicly-accessible-oss-bucket.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun oss PutBucketAcl --bucket --acl private",
"NativeIaC": "",
- "Other": "",
+ "Other": "**Set Bucket ACL to Private:**\n1. Log on to the **OSS Console**\n2. In the bucket-list pane, click on a target OSS bucket\n3. Click on **Basic Setting** in the top middle of the console\n4. Under ACL section, click on **Configure**\n5. Click **Private** and click **Save**\n\n**For Bucket Policy:**\n1. Click **Bucket**, and then click the name of the target bucket\n2. Click the **Files** tab and click **Authorize**\n3. In the Authorize dialog, choose `Anonymous Accounts (*)` for Accounts and choose `None` for Authorized Operation\n4. Click **OK**",
"Terraform": "resource \"alicloud_oss_bucket_public_access_block\" \"example\" {\n bucket = alicloud_oss_bucket.example.bucket\n block_public_access = true\n}"
},
"Recommendation": {
- "Text": "**Set Bucket ACL to Private:**\n1. Log on to the **OSS Console**\n2. In the bucket-list pane, click on a target OSS bucket\n3. Click on **Basic Setting** in the top middle of the console\n4. Under ACL section, click on **Configure**\n5. Click **Private** and click **Save**\n\n**For Bucket Policy:**\n1. Click **Bucket**, and then click the name of the target bucket\n2. Click the **Files** tab and click **Authorize**\n3. In the Authorize dialog, choose `Anonymous Accounts (*)` for Accounts and choose `None` for Authorized Operation\n4. Click **OK**",
+ "Text": "Set the OSS bucket ACL to private and configure bucket policies to deny anonymous or public access, ensuring only authorized identities can access stored objects.",
"Url": "https://hub.prowler.com/check/oss_bucket_not_publicly_accessible"
}
},
diff --git a/prowler/providers/alibabacloud/services/oss/oss_bucket_secure_transport_enabled/oss_bucket_secure_transport_enabled.metadata.json b/prowler/providers/alibabacloud/services/oss/oss_bucket_secure_transport_enabled/oss_bucket_secure_transport_enabled.metadata.json
index d5b0a59bf9..b112b84d43 100644
--- a/prowler/providers/alibabacloud/services/oss/oss_bucket_secure_transport_enabled/oss_bucket_secure_transport_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/oss/oss_bucket_secure_transport_enabled/oss_bucket_secure_transport_enabled.metadata.json
@@ -1,32 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "oss_bucket_secure_transport_enabled",
- "CheckTitle": "Secure transfer required is set to Enabled",
- "CheckType": [
- "Sensitive file tampering"
- ],
+ "CheckTitle": "Secure transfer required is enabled for OSS buckets",
+ "CheckType": [],
"ServiceName": "oss",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:oss::account-id:bucket-name",
+ "ResourceIdTemplate": "",
"Severity": "high",
- "ResourceType": "AlibabaCloudOSSBucket",
+ "ResourceType": "ALIYUN::OSS::Bucket",
"ResourceGroup": "storage",
- "Description": "Enable **data encryption in transit**. The secure transfer enhances the security of OSS buckets by only allowing requests to the storage account via a secure connection.\n\nFor example, when calling REST APIs to access storage accounts, the connection must use **HTTPS**. Any requests using HTTP will be rejected.",
- "Risk": "Without **secure transfer enforcement**, OSS buckets may accept HTTP requests, which are not encrypted in transit.\n\nThis exposes data to potential **interception** and **man-in-the-middle attacks**, compromising data confidentiality and integrity.",
+ "Description": "**Alibaba Cloud OSS** buckets should enforce **secure transfer** by requiring all requests to use HTTPS. A bucket policy that denies requests with `acs:SecureTransport` set to `false` ensures that data in transit is encrypted, rejecting any unencrypted HTTP connections to the storage endpoint.",
+ "Risk": "Without **secure transfer enforcement**, OSS buckets accept HTTP requests that transmit data in plaintext. This exposes stored data to potential **interception**, **man-in-the-middle attacks**, and **eavesdropping**, compromising data **confidentiality** and **integrity** during transit.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/85111.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-OSS/enable-secure-transfer.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-OSS/enable-secure-transfer.html"
],
"Remediation": {
"Code": {
- "CLI": "",
+ "CLI": "aliyun oss PutBucketPolicy --bucket --policy '{\"Version\":\"1\",\"Statement\":[{\"Effect\":\"Deny\",\"Principal\":[\"*\"],\"Action\":[\"oss:*\"],\"Resource\":[\"acs:oss:*:*:\",\"acs:oss:*:*:/*\"],\"Condition\":{\"Bool\":{\"acs:SecureTransport\":\"false\"}}}]}'",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **OSS Console**\n2. In the bucket-list pane, click on a target OSS bucket\n3. Click on **Files** in the top middle of the console\n4. Click on **Authorize**\n5. Configure: `Whole Bucket`, `*`, `None` (Authorized Operation) and `http` (Conditions: Access Method) to deny HTTP access\n6. Click **Save**",
"Terraform": "resource \"alicloud_oss_bucket\" \"example\" {\n bucket = \"example-bucket\"\n \n policy = jsonencode({\n \"Version\": \"1\",\n \"Statement\": [{\n \"Effect\": \"Deny\",\n \"Principal\": [\"*\"],\n \"Action\": [\"oss:*\"],\n \"Resource\": [\"acs:oss:*:*:example-bucket\", \"acs:oss:*:*:example-bucket/*\"],\n \"Condition\": {\n \"Bool\": {\n \"acs:SecureTransport\": \"false\"\n }\n }\n }]\n })\n}"
},
"Recommendation": {
- "Text": "1. Log on to the **OSS Console**\n2. In the bucket-list pane, click on a target OSS bucket\n3. Click on **Files** in the top middle of the console\n4. Click on **Authorize**\n5. Configure: `Whole Bucket`, `*`, `None` (Authorized Operation) and `http` (Conditions: Access Method) to deny HTTP access\n6. Click **Save**",
+ "Text": "Enforce secure transfer on OSS buckets by applying a bucket policy that denies all requests not using HTTPS, ensuring data in transit is always encrypted.",
"Url": "https://hub.prowler.com/check/oss_bucket_secure_transport_enabled"
}
},
diff --git a/prowler/providers/alibabacloud/services/ram/ram_no_root_access_key/ram_no_root_access_key.metadata.json b/prowler/providers/alibabacloud/services/ram/ram_no_root_access_key/ram_no_root_access_key.metadata.json
index be7ec536fd..d0ce95a9a1 100644
--- a/prowler/providers/alibabacloud/services/ram/ram_no_root_access_key/ram_no_root_access_key.metadata.json
+++ b/prowler/providers/alibabacloud/services/ram/ram_no_root_access_key/ram_no_root_access_key.metadata.json
@@ -2,32 +2,29 @@
"Provider": "alibabacloud",
"CheckID": "ram_no_root_access_key",
"CheckTitle": "No root account access key exists",
- "CheckType": [
- "Unusual logon",
- "Cloud threat detection"
- ],
+ "CheckType": [],
"ServiceName": "ram",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:ram::account-id:root",
+ "ResourceIdTemplate": "",
"Severity": "critical",
- "ResourceType": "AlibabaCloudRAMAccessKey",
+ "ResourceType": "ALIYUN::RAM::User",
"ResourceGroup": "IAM",
- "Description": "Ensure no **root account access key** exists. Access keys provide programmatic access to a given Alibaba Cloud account.\n\nIt is recommended that all access keys associated with the root account be removed.",
- "Risk": "The **root account** is the most privileged user in an Alibaba Cloud account. Access Keys provide programmatic access to a given Alibaba Cloud account.\n\nRemoving access keys associated with the root account limits vectors by which the account can be compromised and encourages the creation and use of **role-based accounts** that are least privileged.",
+ "Description": "**Alibaba Cloud RAM** access keys provide programmatic access to a given account. The **root account** is the most privileged user in an Alibaba Cloud account and should not have access keys associated with it. It is recommended that all access keys associated with the root account be removed to limit vectors by which the account can be compromised and encourage the creation of **role-based accounts** that follow the principle of least privilege.",
+ "Risk": "The **root account** has unrestricted access to all resources and services within the Alibaba Cloud account. If access keys for the root account are compromised, an attacker gains **full administrative control** over the entire account, including the ability to create, modify, or delete any resource. This poses a critical risk to the **confidentiality**, **integrity**, and **availability** of all cloud resources and data.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/102600.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/remove-root-access-keys.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/remove-root-access-keys.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ram DeleteAccessKey --UserAccessKeyId ",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **RAM Console** by using your Alibaba Cloud account (root account).\n2. Move the pointer over the account icon in the upper-right corner and click **AccessKey**.\n3. Click **Continue to manage AccessKey**.\n4. On the Security Management page, find the target access keys and click **Delete** to delete the target access keys permanently.",
"Terraform": ""
},
"Recommendation": {
- "Text": "1. Log on to the **RAM Console** by using your Alibaba Cloud account (root account)\n2. Move the pointer over the account icon in the upper-right corner and click **AccessKey**\n3. Click **Continue to manage AccessKey**\n4. On the Security Management page, find the target access keys and click **Delete** to delete the target access keys permanently",
+ "Text": "Remove all access keys associated with the root account to reduce the attack surface and encourage the use of role-based accounts with least privilege.",
"Url": "https://hub.prowler.com/check/ram_no_root_access_key"
}
},
diff --git a/prowler/providers/alibabacloud/services/ram/ram_password_policy_lowercase/ram_password_policy_lowercase.metadata.json b/prowler/providers/alibabacloud/services/ram/ram_password_policy_lowercase/ram_password_policy_lowercase.metadata.json
index f5189bc2c8..5cfd7d06ae 100644
--- a/prowler/providers/alibabacloud/services/ram/ram_password_policy_lowercase/ram_password_policy_lowercase.metadata.json
+++ b/prowler/providers/alibabacloud/services/ram/ram_password_policy_lowercase/ram_password_policy_lowercase.metadata.json
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "ram_password_policy_lowercase",
- "CheckTitle": "RAM password policy requires at least one lowercase letter",
- "CheckType": [
- "Unusual logon",
- "Abnormal account"
- ],
+ "CheckTitle": "RAM password policy has lowercase letter requirement",
+ "CheckType": [],
"ServiceName": "ram",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:ram::account-id:password-policy",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudRAMPasswordPolicy",
+ "ResourceType": "ALIYUN::RAM::SecurityPreference",
"ResourceGroup": "IAM",
- "Description": "**RAM password policies** can be used to ensure password complexity.\n\nIt is recommended that the password policy require at least one **lowercase letter**.",
- "Risk": "Enhancing complexity of a password policy increases account resiliency against **brute force logon attempts**.\n\nWeak passwords without character variety are more susceptible to dictionary attacks and automated password cracking tools.",
+ "Description": "**Alibaba Cloud RAM** password policies can be used to enforce password complexity requirements. It is recommended that the password policy require at least one **lowercase letter** to increase the character diversity of passwords. This enhances account resiliency against **brute force logon attempts** and dictionary attacks.",
+ "Risk": "Without requiring **lowercase letters** in the password policy, users may create passwords with limited character diversity. Weak passwords without sufficient character variety are more susceptible to **dictionary attacks** and automated password cracking tools, potentially compromising the **confidentiality** of user accounts and the resources they have access to.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/116413.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/lowercase-letter-password-policy.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/lowercase-letter-password-policy.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ram SetPasswordPolicy --RequireLowercaseCharacters true",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **RAM Console**.\n2. Choose **Settings**.\n3. In the Password section, click **Modify**.\n4. In the Charset section, select **Lower case**.\n5. Click **OK**.",
"Terraform": "resource \"alicloud_ram_password_policy\" \"example\" {\n require_lowercase_characters = true\n}"
},
"Recommendation": {
- "Text": "1. Log on to the **RAM Console**\n2. Choose **Settings**\n3. In the Password section, click **Modify**\n4. In the Charset section, select **Lower case**\n5. Click **OK**",
+ "Text": "Configure the RAM password policy to require at least one lowercase letter to improve password complexity.",
"Url": "https://hub.prowler.com/check/ram_password_policy_lowercase"
}
},
diff --git a/prowler/providers/alibabacloud/services/ram/ram_password_policy_max_login_attempts/ram_password_policy_max_login_attempts.metadata.json b/prowler/providers/alibabacloud/services/ram/ram_password_policy_max_login_attempts/ram_password_policy_max_login_attempts.metadata.json
index f2228326e7..9e4f4298b3 100644
--- a/prowler/providers/alibabacloud/services/ram/ram_password_policy_max_login_attempts/ram_password_policy_max_login_attempts.metadata.json
+++ b/prowler/providers/alibabacloud/services/ram/ram_password_policy_max_login_attempts/ram_password_policy_max_login_attempts.metadata.json
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "ram_password_policy_max_login_attempts",
- "CheckTitle": "RAM password policy temporarily blocks logon after 5 incorrect logon attempts within an hour",
- "CheckType": [
- "Unusual logon",
- "Abnormal account"
- ],
+ "CheckTitle": "RAM password policy temporarily blocks logon after 5 incorrect attempts within an hour",
+ "CheckType": [],
"ServiceName": "ram",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:ram::account-id:password-policy",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudRAMPasswordPolicy",
+ "ResourceType": "ALIYUN::RAM::SecurityPreference",
"ResourceGroup": "IAM",
- "Description": "**RAM password policies** can temporarily block logon after several incorrect logon attempts within an hour.\n\nIt is recommended that the password policy is set to temporarily block logon after **5 incorrect logon attempts** within an hour.",
- "Risk": "Temporarily blocking logon for incorrect password input increases account resiliency against **brute force logon attempts**.\n\nThis control helps prevent automated password guessing attacks from succeeding.",
+ "Description": "**Alibaba Cloud RAM** password policies can temporarily block logon after several incorrect logon attempts within an hour. It is recommended that the password policy is set to temporarily block logon after **5 incorrect logon attempts** within an hour to protect accounts against automated **brute force logon attempts** and credential stuffing attacks.",
+ "Risk": "Without an account lockout policy, attackers can make unlimited **brute force logon attempts** against RAM user accounts without any throttling. This significantly increases the risk of password compromise, potentially leading to unauthorized access to cloud resources and a breach of **confidentiality** and **integrity** of the account's data and services.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/116413.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/max-login-attempts-password-policy.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/max-login-attempts-password-policy.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ram SetPasswordPolicy --MaxLoginAttemps 5",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **RAM Console**.\n2. Choose **Settings**.\n3. In the Password section, click **Modify**.\n4. In the `Max Attempts` field, check the box next to **Enable** and enter `5`.\n5. Click **OK**.",
"Terraform": "resource \"alicloud_ram_password_policy\" \"example\" {\n max_login_attemps = 5\n}"
},
"Recommendation": {
- "Text": "1. Log on to the **RAM Console**\n2. Choose **Settings**\n3. In the Password section, click **Modify**\n4. In the `Max Attempts` field, check the box next to **Enable** and enter `5`\n5. Click **OK**",
+ "Text": "Configure the RAM password policy to temporarily block logon after 5 incorrect attempts within an hour.",
"Url": "https://hub.prowler.com/check/ram_password_policy_max_login_attempts"
}
},
diff --git a/prowler/providers/alibabacloud/services/ram/ram_password_policy_max_password_age/ram_password_policy_max_password_age.metadata.json b/prowler/providers/alibabacloud/services/ram/ram_password_policy_max_password_age/ram_password_policy_max_password_age.metadata.json
index a4138c2362..88e9292d37 100644
--- a/prowler/providers/alibabacloud/services/ram/ram_password_policy_max_password_age/ram_password_policy_max_password_age.metadata.json
+++ b/prowler/providers/alibabacloud/services/ram/ram_password_policy_max_password_age/ram_password_policy_max_password_age.metadata.json
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "ram_password_policy_max_password_age",
- "CheckTitle": "RAM password policy expires passwords in 365 days or greater",
- "CheckType": [
- "Unusual logon",
- "Abnormal account"
- ],
+ "CheckTitle": "RAM password policy expires passwords within 365 days or less",
+ "CheckType": [],
"ServiceName": "ram",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:ram::account-id:password-policy",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudRAMPasswordPolicy",
+ "ResourceType": "ALIYUN::RAM::SecurityPreference",
"ResourceGroup": "IAM",
- "Description": "**RAM password policies** can require passwords to be expired after a given number of days.\n\nIt is recommended that the password policy expire passwords after **365 days** or greater.",
- "Risk": "Too frequent password changes are more harmful than beneficial. They offer no containment benefits and enforce bad habits, since they encourage users to choose variants of older passwords.\n\nThe CIS now recommends an **annual password reset** as a balanced approach.",
+ "Description": "**Alibaba Cloud RAM** password policies can require passwords to be expired after a given number of days. It is recommended that the password policy expire passwords after **365 days** or less to ensure periodic credential rotation. The CIS benchmark recommends an **annual password reset** as a balanced approach that avoids forcing overly frequent changes while still ensuring compromised credentials have a limited lifespan.",
+ "Risk": "Without a maximum password age policy, compromised passwords can remain valid **indefinitely**, giving attackers persistent access to cloud resources. While overly frequent password changes can encourage users to choose weak variants, a reasonable maximum age of **365 days** ensures that any compromised credentials are eventually invalidated, reducing the window of opportunity for unauthorized access and protecting the **confidentiality** and **integrity** of account data.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/116413.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/require-password-expiration-policy.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/require-password-expiration-policy.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ram SetPasswordPolicy --MaxPasswordAge 365",
"NativeIaC": "",
- "Other": "",
- "Terraform": "resource \"alicloud_ram_password_policy\" \"example\" {\n max_password_age = 90\n}"
+ "Other": "1. Log on to the **RAM Console**.\n2. Choose **Settings**.\n3. In the Password section, click **Modify**.\n4. Check the box under `Max Age`, enter `365` or a smaller number.\n5. Click **OK**.",
+ "Terraform": "resource \"alicloud_ram_password_policy\" \"example\" {\n max_password_age = 365\n}"
},
"Recommendation": {
- "Text": "1. Log on to the **RAM Console**\n2. Choose **Settings**\n3. In the Password section, click **Modify**\n4. Check the box under `Max Age`, enter `365` or a greater number up to `1095`\n5. Click **OK**",
+ "Text": "Configure the RAM password policy to expire passwords within 365 days or less.",
"Url": "https://hub.prowler.com/check/ram_password_policy_max_password_age"
}
},
diff --git a/prowler/providers/alibabacloud/services/ram/ram_password_policy_minimum_length/ram_password_policy_minimum_length.metadata.json b/prowler/providers/alibabacloud/services/ram/ram_password_policy_minimum_length/ram_password_policy_minimum_length.metadata.json
index 4c0c45c406..fd1be8c195 100644
--- a/prowler/providers/alibabacloud/services/ram/ram_password_policy_minimum_length/ram_password_policy_minimum_length.metadata.json
+++ b/prowler/providers/alibabacloud/services/ram/ram_password_policy_minimum_length/ram_password_policy_minimum_length.metadata.json
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "ram_password_policy_minimum_length",
- "CheckTitle": "RAM password policy requires minimum length of 14 or greater",
- "CheckType": [
- "Unusual logon",
- "Abnormal account"
- ],
+ "CheckTitle": "RAM password policy requires a minimum length of 14 or greater",
+ "CheckType": [],
"ServiceName": "ram",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:ram::account-id:password-policy",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudRAMPasswordPolicy",
+ "ResourceType": "ALIYUN::RAM::SecurityPreference",
"ResourceGroup": "IAM",
- "Description": "**RAM password policies** can be used to ensure password complexity.\n\nIt is recommended that the password policy require a minimum of **14 or greater characters** for any password.",
- "Risk": "Enhancing complexity of a password policy increases account resiliency against **brute force logon attempts**.\n\nLonger passwords provide exponentially more security against automated password cracking.",
+ "Description": "**Alibaba Cloud RAM** password policies can be used to enforce password complexity requirements. It is recommended that the password policy require a minimum of **14 or greater characters** for any password. Longer passwords provide exponentially more security against automated password cracking, as the keyspace increases dramatically with each additional character.",
+ "Risk": "Allowing short passwords significantly reduces the effort required for **brute force attacks** to succeed. Passwords shorter than **14 characters** can be cracked in a fraction of the time compared to longer passwords, potentially compromising the **confidentiality** of user accounts. This can lead to unauthorized access to cloud resources and sensitive data, affecting the overall **integrity** and **availability** of the environment.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/116413.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/require-14-characters-password-policy.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/require-14-characters-password-policy.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ram SetPasswordPolicy --MinimumPasswordLength 14",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **RAM Console**.\n2. Choose **Settings**.\n3. In the Password section, click **Modify**.\n4. In the Length section, enter `14` or a greater number.\n5. Click **OK**.",
"Terraform": "resource \"alicloud_ram_password_policy\" \"example\" {\n minimum_password_length = 14\n}"
},
"Recommendation": {
- "Text": "1. Log on to the **RAM Console**\n2. Choose **Settings**\n3. In the Password section, click **Modify**\n4. In the Length section, enter `14` or a greater number\n5. Click **OK**",
+ "Text": "Configure the RAM password policy to require a minimum password length of 14 characters or greater.",
"Url": "https://hub.prowler.com/check/ram_password_policy_minimum_length"
}
},
diff --git a/prowler/providers/alibabacloud/services/ram/ram_password_policy_number/ram_password_policy_number.metadata.json b/prowler/providers/alibabacloud/services/ram/ram_password_policy_number/ram_password_policy_number.metadata.json
index 29aba59a21..9b107baa91 100644
--- a/prowler/providers/alibabacloud/services/ram/ram_password_policy_number/ram_password_policy_number.metadata.json
+++ b/prowler/providers/alibabacloud/services/ram/ram_password_policy_number/ram_password_policy_number.metadata.json
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "ram_password_policy_number",
- "CheckTitle": "RAM password policy require at least one number",
- "CheckType": [
- "Unusual logon",
- "Abnormal account"
- ],
+ "CheckTitle": "RAM password policy requires at least one number",
+ "CheckType": [],
"ServiceName": "ram",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:ram::account-id:password-policy",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudRAMPasswordPolicy",
+ "ResourceType": "ALIYUN::RAM::SecurityPreference",
"ResourceGroup": "IAM",
- "Description": "**RAM password policies** can be used to ensure password complexity.\n\nIt is recommended that the password policy require at least one **number**.",
- "Risk": "Enhancing complexity of a password policy increases account resiliency against **brute force logon attempts**.\n\nWeak passwords without numeric characters are more susceptible to dictionary attacks.",
+ "Description": "**Alibaba Cloud RAM** password policies can be used to enforce password complexity requirements. It is recommended that the password policy require at least one **numeric character** to increase the character diversity of passwords. This enhances account resiliency against **brute force logon attempts** and dictionary attacks by expanding the keyspace.",
+ "Risk": "Without requiring **numeric characters** in the password policy, users may create passwords composed only of alphabetic characters. Such passwords are more susceptible to **dictionary attacks** and automated cracking tools, potentially compromising the **confidentiality** of user accounts and the cloud resources they protect.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/116413.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/require-number-password-policy.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/require-number-password-policy.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ram SetPasswordPolicy --RequireNumbers true",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **RAM Console**.\n2. Choose **Settings**.\n3. In the Password section, click **Modify**.\n4. In the Charset section, select **Number**.\n5. Click **OK**.",
"Terraform": "resource \"alicloud_ram_password_policy\" \"example\" {\n require_numbers = true\n}"
},
"Recommendation": {
- "Text": "1. Log on to the **RAM Console**\n2. Choose **Settings**\n3. In the Password section, click **Modify**\n4. In the Charset section, select **Number**\n5. Click **OK**",
+ "Text": "Configure the RAM password policy to require at least one numeric character to improve password complexity.",
"Url": "https://hub.prowler.com/check/ram_password_policy_number"
}
},
diff --git a/prowler/providers/alibabacloud/services/ram/ram_password_policy_password_reuse_prevention/ram_password_policy_password_reuse_prevention.metadata.json b/prowler/providers/alibabacloud/services/ram/ram_password_policy_password_reuse_prevention/ram_password_policy_password_reuse_prevention.metadata.json
index 761807fb00..43b3f7ae5d 100644
--- a/prowler/providers/alibabacloud/services/ram/ram_password_policy_password_reuse_prevention/ram_password_policy_password_reuse_prevention.metadata.json
+++ b/prowler/providers/alibabacloud/services/ram/ram_password_policy_password_reuse_prevention/ram_password_policy_password_reuse_prevention.metadata.json
@@ -2,32 +2,29 @@
"Provider": "alibabacloud",
"CheckID": "ram_password_policy_password_reuse_prevention",
"CheckTitle": "RAM password policy prevents password reuse",
- "CheckType": [
- "Unusual logon",
- "Abnormal account"
- ],
+ "CheckType": [],
"ServiceName": "ram",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:ram::account-id:password-policy",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudRAMPasswordPolicy",
+ "ResourceType": "ALIYUN::RAM::SecurityPreference",
"ResourceGroup": "IAM",
- "Description": "It is recommended that the **password policy** prevent the reuse of passwords.\n\nThis ensures users cannot cycle back to previously compromised passwords.",
- "Risk": "Preventing **password reuse** increases account resiliency against brute force logon attempts.\n\nIf a password is compromised and later reused, attackers with knowledge of old credentials can regain access.",
+ "Description": "**Alibaba Cloud RAM** password policies can be configured to prevent the reuse of previously used passwords. It is recommended that the password policy prevent the reuse of passwords to ensure users cannot cycle back to previously compromised credentials. This increases account resiliency against **brute force logon attempts** and reduces the risk of credential reuse attacks.",
+ "Risk": "Without **password reuse prevention**, users may cycle back to previously compromised passwords. If a password was compromised in the past and is later reused, attackers with knowledge of old credentials can regain access to the account, threatening the **confidentiality** and **integrity** of cloud resources. This significantly weakens the overall security posture of the Alibaba Cloud environment.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/116413.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/prevent-password-reuse-password-policy.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/prevent-password-reuse-password-policy.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ram SetPasswordPolicy --PasswordReusePrevention 5",
"NativeIaC": "",
- "Other": "",
- "Terraform": "resource \"alicloud_ram_password_policy\" \"example\" {\n password_reuse_prevention = 24\n}"
+ "Other": "1. Log on to the **RAM Console**.\n2. Choose **Settings**.\n3. In the Password section, click **Modify**.\n4. In the `Do Not repeat History` section field, enter `5`.\n5. Click **OK**.",
+ "Terraform": "resource \"alicloud_ram_password_policy\" \"example\" {\n password_reuse_prevention = 5\n}"
},
"Recommendation": {
- "Text": "1. Log on to the **RAM Console**\n2. Choose **Settings**\n3. In the Password section, click **Modify**\n4. In the `Do Not repeat History` section field, enter `5`\n5. Click **OK**",
+ "Text": "Configure the RAM password policy to prevent the reuse of at least the last 5 passwords.",
"Url": "https://hub.prowler.com/check/ram_password_policy_password_reuse_prevention"
}
},
diff --git a/prowler/providers/alibabacloud/services/ram/ram_password_policy_symbol/ram_password_policy_symbol.metadata.json b/prowler/providers/alibabacloud/services/ram/ram_password_policy_symbol/ram_password_policy_symbol.metadata.json
index 7c369a89c2..46fc154d33 100644
--- a/prowler/providers/alibabacloud/services/ram/ram_password_policy_symbol/ram_password_policy_symbol.metadata.json
+++ b/prowler/providers/alibabacloud/services/ram/ram_password_policy_symbol/ram_password_policy_symbol.metadata.json
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "ram_password_policy_symbol",
- "CheckTitle": "RAM password policy require at least one symbol",
- "CheckType": [
- "Unusual logon",
- "Abnormal account"
- ],
+ "CheckTitle": "RAM password policy requires at least one symbol",
+ "CheckType": [],
"ServiceName": "ram",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:ram::account-id:password-policy",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudRAMPasswordPolicy",
+ "ResourceType": "ALIYUN::RAM::SecurityPreference",
"ResourceGroup": "IAM",
- "Description": "**RAM password policies** can be used to ensure password complexity.\n\nIt is recommended that the password policy require at least one **symbol**.",
- "Risk": "Enhancing complexity of a password policy increases account resiliency against **brute force logon attempts**.\n\nSpecial characters significantly increase the keyspace that attackers must search.",
+ "Description": "**Alibaba Cloud RAM** password policies can be used to enforce password complexity requirements. It is recommended that the password policy require at least one **special character (symbol)** to increase the character diversity of passwords. Special characters significantly increase the keyspace that attackers must search, enhancing account resiliency against **brute force logon attempts**.",
+ "Risk": "Without requiring **symbols** in the password policy, users may create passwords composed only of alphanumeric characters. Such passwords have a reduced keyspace and are more susceptible to **brute force attacks** and automated password cracking tools, potentially compromising the **confidentiality** of user accounts and the cloud resources they protect.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/116413.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/require-symbol-password-policy.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/require-symbol-password-policy.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ram SetPasswordPolicy --RequireSymbols true",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **RAM Console**.\n2. Choose **Settings**.\n3. In the Password section, click **Modify**.\n4. In the Charset section, select **Symbol**.\n5. Click **OK**.",
"Terraform": "resource \"alicloud_ram_password_policy\" \"example\" {\n require_symbols = true\n}"
},
"Recommendation": {
- "Text": "1. Log on to the **RAM Console**\n2. Choose **Settings**\n3. In the Password section, click **Modify**\n4. In the Charset section, select **Symbol**\n5. Click **OK**",
+ "Text": "Configure the RAM password policy to require at least one symbol to improve password complexity.",
"Url": "https://hub.prowler.com/check/ram_password_policy_symbol"
}
},
diff --git a/prowler/providers/alibabacloud/services/ram/ram_password_policy_uppercase/ram_password_policy_uppercase.metadata.json b/prowler/providers/alibabacloud/services/ram/ram_password_policy_uppercase/ram_password_policy_uppercase.metadata.json
index 28f115bd3e..41e5ec4845 100644
--- a/prowler/providers/alibabacloud/services/ram/ram_password_policy_uppercase/ram_password_policy_uppercase.metadata.json
+++ b/prowler/providers/alibabacloud/services/ram/ram_password_policy_uppercase/ram_password_policy_uppercase.metadata.json
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "ram_password_policy_uppercase",
- "CheckTitle": "RAM password policy requires at least one uppercase letter",
- "CheckType": [
- "Unusual logon",
- "Abnormal account"
- ],
+ "CheckTitle": "RAM password policy has uppercase letter requirement",
+ "CheckType": [],
"ServiceName": "ram",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:ram::account-id:password-policy",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudRAMPasswordPolicy",
+ "ResourceType": "ALIYUN::RAM::SecurityPreference",
"ResourceGroup": "IAM",
- "Description": "**RAM password policies** can be used to ensure password complexity.\n\nIt is recommended that the password policy require at least one **uppercase letter**.",
- "Risk": "Enhancing complexity of a password policy increases account resiliency against **brute force logon attempts**.\n\nWeak passwords without case variety are more susceptible to dictionary attacks.",
+ "Description": "**Alibaba Cloud RAM** password policies can be used to enforce password complexity requirements. It is recommended that the password policy require at least one **uppercase letter** to increase the character diversity of passwords. This enhances account resiliency against **brute force logon attempts** and dictionary attacks by requiring mixed-case passwords.",
+ "Risk": "Without requiring **uppercase letters** in the password policy, users may create passwords with limited case diversity. Weak passwords without case variety are more susceptible to **dictionary attacks** and automated password cracking tools, potentially compromising the **confidentiality** of user accounts and the resources they have access to.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/116413.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/uppercase-letter-password-policy.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/uppercase-letter-password-policy.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ram SetPasswordPolicy --RequireUppercaseCharacters true",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **RAM Console**.\n2. Choose **Settings**.\n3. In the Password section, click **Modify**.\n4. In the Charset section, select **Upper case**.\n5. Click **OK**.",
"Terraform": "resource \"alicloud_ram_password_policy\" \"example\" {\n require_uppercase_characters = true\n}"
},
"Recommendation": {
- "Text": "1. Log on to the **RAM Console**\n2. Choose **Settings**\n3. In the Password section, click **Modify**\n4. In the Charset section, select **Upper case**\n5. Click **OK**",
+ "Text": "Configure the RAM password policy to require at least one uppercase letter to improve password complexity.",
"Url": "https://hub.prowler.com/check/ram_password_policy_uppercase"
}
},
diff --git a/prowler/providers/alibabacloud/services/ram/ram_policy_attached_only_to_group_or_roles/ram_policy_attached_only_to_group_or_roles.metadata.json b/prowler/providers/alibabacloud/services/ram/ram_policy_attached_only_to_group_or_roles/ram_policy_attached_only_to_group_or_roles.metadata.json
index 0dfa1782bd..5c895dc0ca 100644
--- a/prowler/providers/alibabacloud/services/ram/ram_policy_attached_only_to_group_or_roles/ram_policy_attached_only_to_group_or_roles.metadata.json
+++ b/prowler/providers/alibabacloud/services/ram/ram_policy_attached_only_to_group_or_roles/ram_policy_attached_only_to_group_or_roles.metadata.json
@@ -2,32 +2,29 @@
"Provider": "alibabacloud",
"CheckID": "ram_policy_attached_only_to_group_or_roles",
"CheckTitle": "RAM policies are attached only to groups or roles",
- "CheckType": [
- "Abnormal account",
- "Cloud threat detection"
- ],
+ "CheckType": [],
"ServiceName": "ram",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:ram::account-id:user/{user-name}",
+ "ResourceIdTemplate": "",
"Severity": "low",
- "ResourceType": "AlibabaCloudRAMUser",
+ "ResourceType": "ALIYUN::RAM::ManagedPolicy",
"ResourceGroup": "IAM",
- "Description": "By default, **RAM users**, groups, and roles have no access to Alibaba Cloud resources. RAM policies are the means by which privileges are granted to users, groups, or roles.\n\nIt is recommended that RAM policies be applied directly to **groups and roles** but not users.",
- "Risk": "Assigning privileges at the **group or role level** reduces the complexity of access management as the number of users grows.\n\nReducing access management complexity may in turn reduce opportunity for a principal to inadvertently receive or retain **excessive privileges**.",
+ "Description": "**Alibaba Cloud RAM** users, groups, and roles have no access to resources by default. RAM policies are the means by which privileges are granted to users, groups, or roles. It is recommended that RAM policies be applied directly to **groups and roles** but not to individual users. This simplifies access management and reduces the likelihood of granting unintended permissions as the number of users grows.",
+ "Risk": "Assigning privileges directly to individual users instead of **groups or roles** increases the complexity of access management. As the number of users grows, this complexity can lead to principals inadvertently receiving or retaining **excessive privileges**, threatening the **confidentiality** and **integrity** of cloud resources. It also makes auditing and compliance reviews significantly more difficult.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/116820.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/receive-permissions-via-ram-groups-only.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/receive-permissions-via-ram-groups-only.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ram DetachPolicyFromUser --PolicyName --PolicyType --UserName ",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Create **RAM user groups** and assign policies to those groups.\n2. Add users to the appropriate groups.\n3. Detach any policies directly attached to users using the RAM Console or CLI.",
"Terraform": ""
},
"Recommendation": {
- "Text": "1. Create **RAM user groups** and assign policies to those groups\n2. Add users to the appropriate groups\n3. Detach any policies directly attached to users using the RAM Console or CLI",
+ "Text": "Detach policies from individual RAM users and attach them to groups or roles instead to simplify access management.",
"Url": "https://hub.prowler.com/check/ram_policy_attached_only_to_group_or_roles"
}
},
diff --git a/prowler/providers/alibabacloud/services/ram/ram_policy_no_administrative_privileges/ram_policy_no_administrative_privileges.metadata.json b/prowler/providers/alibabacloud/services/ram/ram_policy_no_administrative_privileges/ram_policy_no_administrative_privileges.metadata.json
index 3d2e085fc9..27fbeff47c 100644
--- a/prowler/providers/alibabacloud/services/ram/ram_policy_no_administrative_privileges/ram_policy_no_administrative_privileges.metadata.json
+++ b/prowler/providers/alibabacloud/services/ram/ram_policy_no_administrative_privileges/ram_policy_no_administrative_privileges.metadata.json
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "ram_policy_no_administrative_privileges",
- "CheckTitle": "RAM policies that allow full \"*:*\" administrative privileges are not created",
- "CheckType": [
- "Abnormal account",
- "Cloud threat detection"
- ],
+ "CheckTitle": "RAM policies do not allow full administrative privileges",
+ "CheckType": [],
"ServiceName": "ram",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:ram::account-id:policy/{policy-name}",
+ "ResourceIdTemplate": "",
"Severity": "critical",
- "ResourceType": "AlibabaCloudRAMPolicy",
+ "ResourceType": "ALIYUN::RAM::ManagedPolicy",
"ResourceGroup": "IAM",
- "Description": "**RAM policies** represent permissions that can be granted to users, groups, or roles. It is recommended to grant **least privilege**—that is, granting only the permissions required to perform tasks.\n\nDetermine what users need to do and then create policies with permissions that only fit those tasks, instead of allowing full administrative privileges.",
- "Risk": "It is more secure to start with a minimum set of permissions and grant additional permissions as necessary. Providing **full administrative privileges** exposes your resources to potentially unwanted actions.\n\nRAM policies with `\"Effect\": \"Allow\"`, `\"Action\": \"*\"`, and `\"Resource\": \"*\"` should be prohibited.",
+ "Description": "**Alibaba Cloud RAM** policies represent permissions that can be granted to users, groups, or roles. It is recommended to follow the principle of **least privilege** by granting only the permissions required to perform specific tasks. RAM policies with `\"Effect\": \"Allow\"`, `\"Action\": \"*\"`, and `\"Resource\": \"*\"` should be avoided as they grant full administrative access to all resources and services.",
+ "Risk": "RAM policies granting **full administrative privileges** (`*:*`) expose all cloud resources to potentially unwanted actions. If such a policy is attached to a compromised user, group, or role, an attacker gains unrestricted access to create, modify, or delete any resource, severely impacting the **confidentiality**, **integrity**, and **availability** of the entire Alibaba Cloud environment.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/93733.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/policies-with-full-administrative-privileges.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/policies-with-full-administrative-privileges.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ram DetachPolicyFromUser --PolicyName --PolicyType Custom --UserName ",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **RAM Console**.\n2. Choose **Permissions** > **Policies**.\n3. From the Policy Type drop-down list, select **Custom Policy**.\n4. In the Policy Name column, click the name of the target policy.\n5. In the Policy Document section, edit the policy to remove the statement with full administrative privileges, or remove the policy from any RAM users, user groups, or roles that have this policy attached.",
"Terraform": ""
},
"Recommendation": {
- "Text": "1. Log on to the **RAM Console**\n2. Choose **Permissions** > **Policies**\n3. From the Policy Type drop-down list, select **Custom Policy**\n4. In the Policy Name column, click the name of the target policy\n5. In the Policy Document section, edit the policy to remove the statement with full administrative privileges, or remove the policy from any RAM users, user groups, or roles that have this policy attached",
+ "Text": "Remove or modify RAM policies that grant full administrative privileges and replace them with least-privilege policies.",
"Url": "https://hub.prowler.com/check/ram_policy_no_administrative_privileges"
}
},
diff --git a/prowler/providers/alibabacloud/services/ram/ram_rotate_access_key_90_days/ram_rotate_access_key_90_days.metadata.json b/prowler/providers/alibabacloud/services/ram/ram_rotate_access_key_90_days/ram_rotate_access_key_90_days.metadata.json
index c5f4bf24ce..da8d81b20f 100644
--- a/prowler/providers/alibabacloud/services/ram/ram_rotate_access_key_90_days/ram_rotate_access_key_90_days.metadata.json
+++ b/prowler/providers/alibabacloud/services/ram/ram_rotate_access_key_90_days/ram_rotate_access_key_90_days.metadata.json
@@ -2,32 +2,29 @@
"Provider": "alibabacloud",
"CheckID": "ram_rotate_access_key_90_days",
"CheckTitle": "Access keys are rotated every 90 days or less",
- "CheckType": [
- "Unusual logon",
- "Cloud threat detection"
- ],
+ "CheckType": [],
"ServiceName": "ram",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:ram::account-id:user/{user-name}/accesskey/{access-key-id}",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudRAMAccessKey",
+ "ResourceType": "ALIYUN::RAM::User",
"ResourceGroup": "IAM",
- "Description": "An **access key** consists of an access key ID and a secret, which are used to sign programmatic requests that you make to Alibaba Cloud.\n\nRAM users need their own access keys to make programmatic calls from SDKs, CLIs, or direct API calls. It is recommended that all access keys be **regularly rotated**.",
- "Risk": "Access keys might be compromised by leaving them in code, configuration files, on-premise and cloud storages, and then stolen by attackers.\n\n**Rotating access keys** reduces the window of opportunity for a compromised access key to be used.",
+ "Description": "**Alibaba Cloud RAM** access keys consist of an access key ID and a secret, which are used to sign programmatic requests. RAM users need their own access keys to make programmatic calls from SDKs, CLIs, or direct API calls. It is recommended that all access keys be **regularly rotated** every 90 days or less to reduce the window of opportunity for compromised keys to be used.",
+ "Risk": "Access keys might be compromised by being left in code, configuration files, or cloud storage and then stolen by attackers. Without regular **access key rotation**, a compromised key can remain valid indefinitely, allowing persistent unauthorized access. This threatens the **confidentiality**, **integrity**, and **availability** of all resources accessible via those credentials.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/116401.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/access-keys-rotation.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/access-keys-rotation.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ram CreateAccessKey --UserName && aliyun ram UpdateAccessKey --UserAccessKeyId --Status Inactive --UserName && aliyun ram DeleteAccessKey --UserAccessKeyId --UserName ",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Create a new **AccessKey pair** for rotation.\n2. Update all applications and systems to use the new AccessKey pair.\n3. **Disable** the original AccessKey pair.\n4. Confirm that your applications and systems are working.\n5. **Delete** the original AccessKey pair.",
"Terraform": ""
},
"Recommendation": {
- "Text": "1. Create a new **AccessKey pair** for rotation\n2. Update all applications and systems to use the new AccessKey pair\n3. **Disable** the original AccessKey pair\n4. Confirm that your applications and systems are working\n5. **Delete** the original AccessKey pair",
+ "Text": "Rotate all RAM user access keys every 90 days or less to limit the impact of compromised credentials.",
"Url": "https://hub.prowler.com/check/ram_rotate_access_key_90_days"
}
},
diff --git a/prowler/providers/alibabacloud/services/ram/ram_user_console_access_unused/ram_user_console_access_unused.metadata.json b/prowler/providers/alibabacloud/services/ram/ram_user_console_access_unused/ram_user_console_access_unused.metadata.json
index 1f6b644a46..3d920c351e 100644
--- a/prowler/providers/alibabacloud/services/ram/ram_user_console_access_unused/ram_user_console_access_unused.metadata.json
+++ b/prowler/providers/alibabacloud/services/ram/ram_user_console_access_unused/ram_user_console_access_unused.metadata.json
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "ram_user_console_access_unused",
- "CheckTitle": "Users not logged on for 90 days or longer are disabled for console logon",
- "CheckType": [
- "Unusual logon",
- "Abnormal account"
- ],
+ "CheckTitle": "RAM user not logged on for 90 days or longer has console logon disabled",
+ "CheckType": [],
"ServiceName": "ram",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:ram::account-id:user/{user-name}",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudRAMUser",
+ "ResourceType": "ALIYUN::RAM::User",
"ResourceGroup": "IAM",
- "Description": "Alibaba Cloud **RAM users** can log on to the Alibaba Cloud console by using their username and password.\n\nIf a user has not logged on for **90 days or longer**, it is recommended to disable the console access of the user.",
- "Risk": "Disabling users from having unnecessary logon privileges will reduce the opportunity that an **abandoned user** or a user with **compromised password** to be exploited.\n\nInactive accounts are common targets for attackers attempting account takeover.",
+ "Description": "**Alibaba Cloud RAM** users can log on to the console by using their username and password. If a user has not logged on for **90 days or longer**, it is recommended to disable the console access of the user. Disabling unused console access reduces the attack surface by removing unnecessary logon capabilities from potentially abandoned or dormant accounts.",
+ "Risk": "Inactive accounts with console access enabled are common targets for attackers attempting **account takeover**. An abandoned user account or one with a **compromised password** that has not been used in over 90 days may go unmonitored, allowing unauthorized access to go undetected. This poses a significant risk to the **confidentiality** and **integrity** of cloud resources accessible through the compromised account.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/116820.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/inactive-ram-user.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/inactive-ram-user.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ram DeleteLoginProfile --UserName ",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **RAM Console**.\n2. Choose **Identities** > **Users**.\n3. In the User Logon Name/Display Name column, click the username of the target RAM user.\n4. In the Console Logon Management section, click **Modify Logon Settings**.\n5. In the Console Password Logon section, select **Disabled**.\n6. Click **OK**.",
"Terraform": ""
},
"Recommendation": {
- "Text": "1. Log on to the **RAM Console**\n2. Choose **Identities** > **Users**\n3. In the User Logon Name/Display Name column, click the username of the target RAM user\n4. In the Console Logon Management section, click **Modify Logon Settings**\n5. In the Console Password Logon section, select **Disabled**\n6. Click **OK**",
+ "Text": "Disable console access for RAM users that have not logged on for 90 days or longer to reduce the attack surface.",
"Url": "https://hub.prowler.com/check/ram_user_console_access_unused"
}
},
diff --git a/prowler/providers/alibabacloud/services/ram/ram_user_mfa_enabled_console_access/ram_user_mfa_enabled_console_access.metadata.json b/prowler/providers/alibabacloud/services/ram/ram_user_mfa_enabled_console_access/ram_user_mfa_enabled_console_access.metadata.json
index 11d8177294..92b537df54 100644
--- a/prowler/providers/alibabacloud/services/ram/ram_user_mfa_enabled_console_access/ram_user_mfa_enabled_console_access.metadata.json
+++ b/prowler/providers/alibabacloud/services/ram/ram_user_mfa_enabled_console_access/ram_user_mfa_enabled_console_access.metadata.json
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "ram_user_mfa_enabled_console_access",
- "CheckTitle": "Multi-factor authentication is enabled for all RAM users that have a console password",
- "CheckType": [
- "Unusual logon",
- "Abnormal account"
- ],
+ "CheckTitle": "Multi-factor authentication is enabled for all RAM users with console access",
+ "CheckType": [],
"ServiceName": "ram",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:ram::account-id:user/{user-name}",
+ "ResourceIdTemplate": "",
"Severity": "high",
- "ResourceType": "AlibabaCloudRAMUser",
+ "ResourceType": "ALIYUN::RAM::User",
"ResourceGroup": "IAM",
- "Description": "**Multi-Factor Authentication (MFA)** adds an extra layer of protection on top of a username and password.\n\nWith MFA enabled, when a user logs on to Alibaba Cloud, they will be prompted for their username and password followed by an authentication code from their virtual MFA device. It is recommended that MFA be enabled for all users that have a console password.",
- "Risk": "**MFA** requires users to verify their identities by entering two authentication factors. When MFA is enabled, an attacker faces at least two different authentication mechanisms.\n\nThe additional security makes it significantly harder for an attacker to gain access even if passwords are compromised.",
+ "Description": "**Alibaba Cloud RAM** supports **Multi-Factor Authentication (MFA)**, which adds an extra layer of protection on top of a username and password. With MFA enabled, when a user logs on to the console, they are prompted for their username and password followed by an authentication code from their virtual MFA device. It is recommended that MFA be enabled for all RAM users that have a console password to significantly strengthen account security.",
+ "Risk": "Without **MFA** enabled, RAM user accounts rely solely on passwords for authentication. If a password is compromised through phishing, credential stuffing, or other attacks, an attacker gains full access to the account. Enabling MFA requires an additional authentication factor, making it significantly harder for attackers to gain unauthorized access even with compromised credentials, thereby protecting the **confidentiality**, **integrity**, and **availability** of cloud resources.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/119555.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/ram-user-multi-factor-authentication-enabled.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/ram-user-multi-factor-authentication-enabled.html"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **RAM Console**.\n2. For each user with console access, go to the user's details.\n3. In the **Console Logon Management** section, click **Modify Logon Settings**.\n4. For `Enable MFA`, select **Required**.\n5. Click **OK** to save the settings.",
"Terraform": ""
},
"Recommendation": {
- "Text": "1. Log on to the **RAM Console**\n2. For each user with console access, go to the user's details\n3. In the **Console Logon Management** section, click **Modify Logon Settings**\n4. For `Enable MFA`, select **Required**\n5. Click **OK** to save the settings",
+ "Text": "Enable MFA for all RAM users with console access to add an extra layer of authentication security.",
"Url": "https://hub.prowler.com/check/ram_user_mfa_enabled_console_access"
}
},
diff --git a/prowler/providers/alibabacloud/services/rds/rds_instance_no_public_access_whitelist/rds_instance_no_public_access_whitelist.metadata.json b/prowler/providers/alibabacloud/services/rds/rds_instance_no_public_access_whitelist/rds_instance_no_public_access_whitelist.metadata.json
index ac073851a7..02305aa74b 100644
--- a/prowler/providers/alibabacloud/services/rds/rds_instance_no_public_access_whitelist/rds_instance_no_public_access_whitelist.metadata.json
+++ b/prowler/providers/alibabacloud/services/rds/rds_instance_no_public_access_whitelist/rds_instance_no_public_access_whitelist.metadata.json
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "rds_instance_no_public_access_whitelist",
- "CheckTitle": "RDS Instances are not open to the world",
- "CheckType": [
- "Intrusion into applications",
- "Suspicious network connection"
- ],
+ "CheckTitle": "RDS instance does not allow public access in the IP whitelist",
+ "CheckType": [],
"ServiceName": "rds",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:rds:region:account-id:dbinstance/{dbinstance-id}",
+ "ResourceIdTemplate": "",
"Severity": "critical",
- "ResourceType": "AlibabaCloudRDSDBInstance",
+ "ResourceType": "ALIYUN::RDS::DBInstance",
"ResourceGroup": "database",
- "Description": "Database Server should accept connections only from trusted **Network(s)/IP(s)** and restrict access from the world.\n\nTo minimize attack surface on a Database server Instance, only trusted/known and required IPs should be whitelisted. Authorized network should not have IPs/networks configured to `0.0.0.0` or `/0` which would allow access from anywhere in the world.",
- "Risk": "Allowing **public access** (`0.0.0.0/0`) to the database significantly increases the risk of **brute-force attacks**, **unauthorized access**, and **data exfiltration**.\n\nDatabases exposed to the internet are prime targets for attackers.",
+ "Description": "**Alibaba Cloud RDS instances** should only accept connections from trusted networks and IP addresses. This check verifies that the IP whitelist does not contain entries such as `0.0.0.0/0` or `0.0.0.0` that would allow access from anywhere on the internet. Only specific, trusted IP addresses should be whitelisted to minimize the attack surface of the database server.",
+ "Risk": "Allowing **public access** (`0.0.0.0/0`) to the database significantly increases the risk of **brute-force attacks**, **unauthorized access**, and **data exfiltration**. Databases exposed to the internet are prime targets for attackers, and a successful breach can compromise **confidentiality**, **integrity**, and **availability** of all stored data.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/26198.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RDS/disable-network-public-access.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RDS/disable-network-public-access.html"
],
"Remediation": {
"Code": {
- "CLI": "aliyun rds ModifySecurityIps --DBInstanceId --SecurityIps ",
+ "CLI": "aliyun rds ModifySecurityIps --DBInstanceId --SecurityIps ",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **RDS Console**.\n2. Select the target RDS instance.\n3. Go to **Data Security** > **Whitelist Settings** tab.\n4. Remove any `0.0.0.0` or `0.0.0.0/0` entries.\n5. Add only the specific IP addresses that need to access the instance.\n6. Click **OK** to save changes.",
"Terraform": ""
},
"Recommendation": {
- "Text": "1. Log on to the **RDS Console**\n2. Go to **Data Security** > **Whitelist Settings** tab\n3. Remove any `0.0.0.0` or `/0` entries\n4. Only add the IP addresses that need to access the instance",
+ "Text": "Restrict the RDS IP whitelist to only trusted IP addresses. Remove any entries that allow unrestricted access such as `0.0.0.0/0`.",
"Url": "https://hub.prowler.com/check/rds_instance_no_public_access_whitelist"
}
},
diff --git a/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_connections_enabled/rds_instance_postgresql_log_connections_enabled.metadata.json b/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_connections_enabled/rds_instance_postgresql_log_connections_enabled.metadata.json
index ee59a7cd50..e81fd5a60f 100644
--- a/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_connections_enabled/rds_instance_postgresql_log_connections_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_connections_enabled/rds_instance_postgresql_log_connections_enabled.metadata.json
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "rds_instance_postgresql_log_connections_enabled",
- "CheckTitle": "Parameter log_connections is set to ON for PostgreSQL Database",
- "CheckType": [
- "Intrusion into applications",
- "Unusual logon"
- ],
+ "CheckTitle": "RDS PostgreSQL instance has log_connections parameter enabled",
+ "CheckType": [],
"ServiceName": "rds",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:rds:region:account-id:dbinstance/{dbinstance-id}",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudRDSDBInstance",
+ "ResourceType": "ALIYUN::RDS::DBInstance",
"ResourceGroup": "database",
- "Description": "Enable `log_connections` on **PostgreSQL Servers**. Enabling `log_connections` helps PostgreSQL Database log attempted connections to the server, as well as successful completion of client authentication.\n\nLog data can be used to identify, troubleshoot, and repair configuration errors and suboptimal performance.",
- "Risk": "Without **connection logging**, unauthorized access attempts might go unnoticed, and troubleshooting connection issues becomes more difficult.\n\nThis data is essential for **security monitoring** and **incident investigation**.",
+ "Description": "**Alibaba Cloud RDS PostgreSQL instances** should have the `log_connections` parameter set to `on`. Enabling this parameter logs each attempted connection to the server, including successful client authentication. This log data is essential for identifying, troubleshooting, and repairing configuration errors, detecting unauthorized access attempts, and supporting **security auditing**.",
+ "Risk": "Without **connection logging** enabled, unauthorized access attempts to the database may go unnoticed, making it difficult to detect **brute-force attacks** or **credential compromise**. This gap in visibility impacts the ability to perform **security monitoring**, **incident investigation**, and **forensic analysis**, reducing overall **confidentiality** assurance.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/96751.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RDS/enable-log-connections-for-postgresql.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RDS/enable-log-connections-for-postgresql.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun rds ModifyParameter --DBInstanceId --Parameters \"{\\\"log_connections\\\":\\\"on\\\"}\"",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **RDS Console**.\n2. Select the region and target PostgreSQL instance.\n3. In the left-side navigation pane, select **Parameters**.\n4. Find the `log_connections` parameter and set it to `on`.\n5. Click **Apply Changes**.",
"Terraform": ""
},
"Recommendation": {
- "Text": "1. Log on to the **RDS Console**\n2. Select the region and target instance\n3. In the left-side navigation pane, select **Parameters**\n4. Find the `log_connections` parameter and set it to `on`\n5. Click **Apply Changes**",
+ "Text": "Enable the `log_connections` parameter on all PostgreSQL RDS instances to log connection attempts for security monitoring and troubleshooting.",
"Url": "https://hub.prowler.com/check/rds_instance_postgresql_log_connections_enabled"
}
},
diff --git a/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_disconnections_enabled/rds_instance_postgresql_log_disconnections_enabled.metadata.json b/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_disconnections_enabled/rds_instance_postgresql_log_disconnections_enabled.metadata.json
index 2255790916..7c358b77df 100644
--- a/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_disconnections_enabled/rds_instance_postgresql_log_disconnections_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_disconnections_enabled/rds_instance_postgresql_log_disconnections_enabled.metadata.json
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "rds_instance_postgresql_log_disconnections_enabled",
- "CheckTitle": "Server parameter log_disconnections is set to ON for PostgreSQL Database Server",
- "CheckType": [
- "Intrusion into applications",
- "Unusual logon"
- ],
+ "CheckTitle": "RDS PostgreSQL instance has log_disconnections parameter enabled",
+ "CheckType": [],
"ServiceName": "rds",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:rds:region:account-id:dbinstance/{dbinstance-id}",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudRDSDBInstance",
+ "ResourceType": "ALIYUN::RDS::DBInstance",
"ResourceGroup": "database",
- "Description": "Enable `log_disconnections` on **PostgreSQL Servers**. Enabling `log_disconnections` helps PostgreSQL Database log session terminations of the server, as well as duration of the session.\n\nLog data can be used to identify, troubleshoot, and repair configuration errors and suboptimal performance.",
- "Risk": "Without **disconnection logging**, it's harder to track session durations and identify abnormal disconnection patterns that might indicate **attacks** or **stability issues**.",
+ "Description": "**Alibaba Cloud RDS PostgreSQL instances** should have the `log_disconnections` parameter set to `on`. Enabling this parameter logs session terminations and the duration of each session. This data is valuable for identifying abnormal disconnection patterns, troubleshooting performance issues, and supporting **security auditing** and **incident investigation**.",
+ "Risk": "Without **disconnection logging**, it is harder to track session durations and identify abnormal disconnection patterns that might indicate **attacks**, **session hijacking**, or **stability issues**. This reduces visibility into database activity, impacting **security monitoring** and the ability to perform effective **forensic analysis**.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/96751.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RDS/enable-log-disconnections-for-postgresql.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RDS/enable-log-disconnections-for-postgresql.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun rds ModifyParameter --DBInstanceId --Parameters \"{\\\"log_disconnections\\\":\\\"on\\\"}\"",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **RDS Console**.\n2. Select the region and target PostgreSQL instance.\n3. In the left-side navigation pane, select **Parameters**.\n4. Find the `log_disconnections` parameter and set it to `on`.\n5. Click **Apply Changes**.",
"Terraform": ""
},
"Recommendation": {
- "Text": "1. Log on to the **RDS Console**\n2. Select the region and target instance\n3. In the left-side navigation pane, select **Parameters**\n4. Find the `log_disconnections` parameter and set it to `on`\n5. Click **Apply Changes**",
+ "Text": "Enable the `log_disconnections` parameter on all PostgreSQL RDS instances to log session terminations for security monitoring and troubleshooting.",
"Url": "https://hub.prowler.com/check/rds_instance_postgresql_log_disconnections_enabled"
}
},
diff --git a/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_duration_enabled/rds_instance_postgresql_log_duration_enabled.metadata.json b/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_duration_enabled/rds_instance_postgresql_log_duration_enabled.metadata.json
index dc5a2d9d3a..a0c04e5228 100644
--- a/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_duration_enabled/rds_instance_postgresql_log_duration_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_duration_enabled/rds_instance_postgresql_log_duration_enabled.metadata.json
@@ -1,32 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "rds_instance_postgresql_log_duration_enabled",
- "CheckTitle": "Server parameter log_duration is set to ON for PostgreSQL Database Server",
- "CheckType": [
- "Intrusion into applications"
- ],
+ "CheckTitle": "RDS PostgreSQL instance has log_duration parameter enabled",
+ "CheckType": [],
"ServiceName": "rds",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:rds:region:account-id:dbinstance/{dbinstance-id}",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudRDSDBInstance",
+ "ResourceType": "ALIYUN::RDS::DBInstance",
"ResourceGroup": "database",
- "Description": "Enable `log_duration` on **PostgreSQL Servers**. Enabling `log_duration` helps PostgreSQL Database log the duration of each completed SQL statement which in turn generates query and error logs.\n\nQuery and error logs can be used to identify, troubleshoot, and repair configuration errors and sub-optimal performance.",
- "Risk": "Without **duration logging**, it's difficult to identify **slow queries**, **performance bottlenecks**, and potential **DoS attempts**.\n\nThis information is critical for database performance tuning and security monitoring.",
+ "Description": "**Alibaba Cloud RDS PostgreSQL instances** should have the `log_duration` parameter set to `on`. Enabling this parameter logs the duration of each completed SQL statement, generating query and error logs that can be used to identify **slow queries**, troubleshoot performance issues, and detect potential **denial-of-service** patterns targeting the database.",
+ "Risk": "Without **duration logging**, it is difficult to identify **slow queries**, **performance bottlenecks**, and potential **DoS attempts** against the database. This lack of visibility impacts the ability to optimize database performance and detect **malicious activity** such as resource exhaustion attacks, reducing overall **availability** assurance.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/96751.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RDS/enable-log-duration-for-postgresql.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RDS/enable-log-duration-for-postgresql.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun rds ModifyParameter --DBInstanceId --Parameters \"{\\\"log_duration\\\":\\\"on\\\"}\"",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **RDS Console**.\n2. Select the region and target PostgreSQL instance.\n3. In the left-side navigation pane, select **Parameters**.\n4. Find the `log_duration` parameter and set it to `on`.\n5. Click **Apply Changes**.",
"Terraform": ""
},
"Recommendation": {
- "Text": "1. Log on to the **RDS Console**\n2. Select the region and target instance\n3. In the left-side navigation pane, select **Parameters**\n4. Find the `log_duration` parameter and set it to `on`\n5. Click **Apply Changes**",
+ "Text": "Enable the `log_duration` parameter on all PostgreSQL RDS instances to log SQL statement durations for performance monitoring and security analysis.",
"Url": "https://hub.prowler.com/check/rds_instance_postgresql_log_duration_enabled"
}
},
diff --git a/prowler/providers/alibabacloud/services/rds/rds_instance_sql_audit_enabled/rds_instance_sql_audit_enabled.metadata.json b/prowler/providers/alibabacloud/services/rds/rds_instance_sql_audit_enabled/rds_instance_sql_audit_enabled.metadata.json
index 2506245412..76ed06d5b5 100644
--- a/prowler/providers/alibabacloud/services/rds/rds_instance_sql_audit_enabled/rds_instance_sql_audit_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/rds/rds_instance_sql_audit_enabled/rds_instance_sql_audit_enabled.metadata.json
@@ -1,32 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "rds_instance_sql_audit_enabled",
- "CheckTitle": "Auditing is set to On for applicable database instances",
- "CheckType": [
- "Intrusion into applications"
- ],
+ "CheckTitle": "RDS instance has SQL auditing enabled",
+ "CheckType": [],
"ServiceName": "rds",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:rds:region:account-id:dbinstance/{dbinstance-id}",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudRDSDBInstance",
+ "ResourceType": "ALIYUN::RDS::DBInstance",
"ResourceGroup": "database",
- "Description": "Enable **SQL auditing** on all RDS instances (except SQL Server 2012/2016/2017 and MariaDB TX). Auditing tracks database events and writes them to an audit log.\n\nIt helps to maintain **regulatory compliance**, understand database activity, and gain insight into discrepancies and anomalies that could indicate business concerns or suspected security violations.",
- "Risk": "Without **SQL auditing**, it's difficult to detect **unauthorized access**, **data breaches**, or **malicious activity** within the database.\n\nIt also hinders **forensic investigations** and compliance reporting.",
+ "Description": "**Alibaba Cloud RDS instances** should have **SQL auditing** (SQL Explorer) enabled to track database events and write them to an audit log. SQL auditing helps maintain **regulatory compliance**, understand database activity, and gain insight into discrepancies and anomalies that could indicate business concerns or suspected security violations. This applies to all RDS engine types except SQL Server 2012/2016/2017 and MariaDB TX.",
+ "Risk": "Without **SQL auditing**, it is difficult to detect **unauthorized access**, **data breaches**, or **malicious activity** within the database. The absence of audit logs hinders **forensic investigations**, compliance reporting, and the ability to identify **data exfiltration** or **privilege escalation** attempts, impacting **confidentiality** and **integrity**.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/96123.html",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RDS/enable-audit-logs.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RDS/enable-audit-logs.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun rds ModifySQLCollectorPolicy --DBInstanceId --SQLCollectorStatus Enable --StoragePeriod ",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **RDS Console**.\n2. Select the target RDS instance.\n3. In the left-side navigation pane, select **SQL Explorer**.\n4. Click **Activate Now**.\n5. Specify the SQL log storage duration.\n6. Click **Activate**.",
"Terraform": ""
},
"Recommendation": {
- "Text": "1. Log on to the **RDS Console**\n2. In the left-side navigation pane, select **SQL Explorer**\n3. Click **Activate Now**\n4. Specify the SQL log storage duration\n5. Click **Activate**",
+ "Text": "Enable **SQL auditing** (SQL Explorer) on all applicable RDS instances to track database events and maintain audit logs for security monitoring and compliance.",
"Url": "https://hub.prowler.com/check/rds_instance_sql_audit_enabled"
}
},
diff --git a/prowler/providers/alibabacloud/services/rds/rds_instance_sql_audit_retention/rds_instance_sql_audit_retention.metadata.json b/prowler/providers/alibabacloud/services/rds/rds_instance_sql_audit_retention/rds_instance_sql_audit_retention.metadata.json
index 17300c26e5..2402bf7146 100644
--- a/prowler/providers/alibabacloud/services/rds/rds_instance_sql_audit_retention/rds_instance_sql_audit_retention.metadata.json
+++ b/prowler/providers/alibabacloud/services/rds/rds_instance_sql_audit_retention/rds_instance_sql_audit_retention.metadata.json
@@ -1,32 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "rds_instance_sql_audit_retention",
- "CheckTitle": "Auditing Retention is greater than the configured period",
- "CheckType": [
- "Intrusion into applications"
- ],
+ "CheckTitle": "RDS instance SQL audit retention period meets the configured minimum",
+ "CheckType": [],
"ServiceName": "rds",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:rds:region:account-id:dbinstance/{dbinstance-id}",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudRDSDBInstance",
+ "ResourceType": "ALIYUN::RDS::DBInstance",
"ResourceGroup": "database",
- "Description": "Database **SQL Audit Retention** should be configured to be greater than or equal to the configured period (default: **6 months / 180 days**).\n\nAudit Logs can be used to check for anomalies and give insight into suspected breaches or misuse of information and access.",
- "Risk": "**Short retention periods** for audit logs can result in the loss of critical forensic data needed for **incident investigation** and **compliance auditing**.\n\nMany regulations require minimum retention periods for audit data.",
+ "Description": "**Alibaba Cloud RDS instances** with SQL auditing enabled should have a retention period configured to be greater than or equal to the required minimum (default: **6 months / 180 days**). Audit logs are essential for checking anomalies, understanding database activity, and gaining insight into suspected breaches or misuse of information and access.",
+ "Risk": "**Short retention periods** for audit logs can result in the loss of critical forensic data needed for **incident investigation**, **compliance auditing**, and **regulatory reporting**. Many regulations and security frameworks require minimum retention periods for audit data, and failing to meet them can result in **non-compliance** penalties.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/96123.html",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RDS/configure-log-retention-period.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RDS/configure-log-retention-period.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun rds ModifySQLCollectorPolicy --DBInstanceId --SQLCollectorStatus Enable --StoragePeriod 180",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **RDS Console**.\n2. Select the target RDS instance.\n3. In the left-side navigation pane, select **SQL Explorer**.\n4. Click **Service Setting**.\n5. Enable `Activate SQL Explorer` if not already active.\n6. Set the storage duration to `6 months` or longer.\n7. Click **OK** to save changes.",
"Terraform": ""
},
"Recommendation": {
- "Text": "1. Log on to the **RDS Console**\n2. Select **SQL Explorer**\n3. Click **Service Setting**\n4. Enable `Activate SQL Explorer`\n5. Set the storage duration to `6 months` or longer",
+ "Text": "Configure the SQL audit retention period to at least **180 days** (6 months) on all RDS instances to ensure adequate audit log availability for compliance and forensic purposes.",
"Url": "https://hub.prowler.com/check/rds_instance_sql_audit_retention"
}
},
diff --git a/prowler/providers/alibabacloud/services/rds/rds_instance_ssl_enabled/rds_instance_ssl_enabled.metadata.json b/prowler/providers/alibabacloud/services/rds/rds_instance_ssl_enabled/rds_instance_ssl_enabled.metadata.json
index c00ac879ec..aa1ff3224f 100644
--- a/prowler/providers/alibabacloud/services/rds/rds_instance_ssl_enabled/rds_instance_ssl_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/rds/rds_instance_ssl_enabled/rds_instance_ssl_enabled.metadata.json
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "rds_instance_ssl_enabled",
- "CheckTitle": "RDS instance requires all incoming connections to use SSL",
- "CheckType": [
- "Sensitive file tampering",
- "Intrusion into applications"
- ],
+ "CheckTitle": "RDS instance has SSL encryption enabled",
+ "CheckType": [],
"ServiceName": "rds",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:rds:region:account-id:dbinstance/{dbinstance-id}",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudRDSDBInstance",
+ "ResourceType": "ALIYUN::RDS::DBInstance",
"ResourceGroup": "database",
- "Description": "It is recommended to enforce all incoming connections to SQL database instances to use **SSL**.\n\nSQL database connections if successfully intercepted (MITM) can reveal sensitive data like credentials, database queries, and query outputs. For security, it is recommended to always use SSL encryption when connecting to your instance.",
- "Risk": "If **SSL is not enabled**, data in transit (including credentials and query results) can be intercepted by attackers performing **Man-in-the-Middle (MITM) attacks**.\n\nThis compromises data confidentiality and integrity.",
+ "Description": "**Alibaba Cloud RDS instances** should enforce **SSL encryption** for all incoming connections. SSL protects data in transit between the application and the database, preventing interception of sensitive data such as credentials, database queries, and query outputs. This check verifies that SSL encryption is enabled on the RDS instance.",
+ "Risk": "If **SSL is not enabled**, data in transit including credentials and query results can be intercepted by attackers performing **Man-in-the-Middle (MITM) attacks**. This compromises data **confidentiality** and **integrity**, potentially leading to **credential theft**, **data exfiltration**, and unauthorized manipulation of database communications.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/32474.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RDS/enable-encryption-in-transit.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RDS/enable-encryption-in-transit.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun rds ModifyDBInstanceSSL --DBInstanceId --SSLEnabled 1",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **RDS Console**.\n2. Select the region and target instance.\n3. In the left-side navigation pane, click **Data Security**.\n4. Click the **SSL Encryption** tab.\n5. Click the switch next to **Disabled** to enable SSL encryption.\n6. Download the SSL CA certificate for client configuration.",
"Terraform": "resource \"alicloud_db_instance\" \"example\" {\n engine = \"MySQL\"\n engine_version = \"8.0\"\n instance_type = \"rds.mysql.s1.small\"\n instance_storage = 20\n ssl_action = \"Open\"\n}"
},
"Recommendation": {
- "Text": "1. Log on to the **RDS Console**\n2. Select the region and target instance\n3. In the left-side navigation pane, click **Data Security**\n4. Click the **SSL Encryption** tab\n5. Click the switch next to **Disabled** in the SSL Encryption parameter to enable it",
+ "Text": "Enable **SSL encryption** on all RDS instances to protect data in transit and prevent Man-in-the-Middle attacks.",
"Url": "https://hub.prowler.com/check/rds_instance_ssl_enabled"
}
},
diff --git a/prowler/providers/alibabacloud/services/rds/rds_instance_tde_enabled/rds_instance_tde_enabled.metadata.json b/prowler/providers/alibabacloud/services/rds/rds_instance_tde_enabled/rds_instance_tde_enabled.metadata.json
index da57605bfe..0b033bdbb4 100644
--- a/prowler/providers/alibabacloud/services/rds/rds_instance_tde_enabled/rds_instance_tde_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/rds/rds_instance_tde_enabled/rds_instance_tde_enabled.metadata.json
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "rds_instance_tde_enabled",
- "CheckTitle": "TDE is set to Enabled on for applicable database instance",
- "CheckType": [
- "Sensitive file tampering",
- "Intrusion into applications"
- ],
+ "CheckTitle": "RDS instance has Transparent Data Encryption enabled",
+ "CheckType": [],
"ServiceName": "rds",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:rds:region:account-id:dbinstance/{dbinstance-id}",
+ "ResourceIdTemplate": "",
"Severity": "high",
- "ResourceType": "AlibabaCloudRDSDBInstance",
+ "ResourceType": "ALIYUN::RDS::DBInstance",
"ResourceGroup": "database",
- "Description": "Enable **Transparent Data Encryption (TDE)** on every RDS instance. RDS Database TDE helps protect against the threat of malicious activity by performing real-time encryption and decryption of the database, associated backups, and log files at rest.\n\nNo changes to the application are required.",
- "Risk": "**Data at rest** that is not encrypted is vulnerable to unauthorized access if the underlying storage media or backups are compromised.\n\nTDE protects against physical theft and unauthorized access to storage systems.",
+ "Description": "**Alibaba Cloud RDS instances** should have **Transparent Data Encryption (TDE)** enabled. TDE performs real-time encryption and decryption of the database, associated backups, and log files at rest, without requiring changes to the application. This check verifies that TDE is enabled to protect sensitive data stored in the RDS instance from unauthorized physical access.",
+ "Risk": "**Data at rest** that is not encrypted is vulnerable to unauthorized access if the underlying storage media or backups are compromised, stolen, or improperly decommissioned. Without TDE, attackers with physical or administrative access to the storage layer can read sensitive data directly, impacting **confidentiality** and potentially leading to **data breaches**.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/33510.html",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RDS/enable-sql-database-tde.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RDS/enable-sql-database-tde.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun rds ModifyDBInstanceTDE --DBInstanceId --TDEStatus Enabled",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **RDS Console**.\n2. Select the target RDS instance.\n3. Go to **Data Security** > **TDE** tab.\n4. Find TDE Status and click the switch next to **Disabled**.\n5. Choose automatically generated key or custom key.\n6. Click **Confirm**.",
"Terraform": "resource \"alicloud_db_instance\" \"example\" {\n engine = \"MySQL\"\n engine_version = \"8.0\"\n instance_type = \"rds.mysql.s1.small\"\n instance_storage = 20\n tde_status = \"Enabled\"\n}"
},
"Recommendation": {
- "Text": "1. Log on to the **RDS Console**\n2. Go to **Data Security** > **TDE** tab\n3. Find TDE Status and click the switch next to **Disabled**\n4. Choose automatically generated key or custom key\n5. Click **Confirm**",
+ "Text": "Enable **Transparent Data Encryption (TDE)** on all applicable RDS instances to protect data at rest from unauthorized physical access.",
"Url": "https://hub.prowler.com/check/rds_instance_tde_enabled"
}
},
diff --git a/prowler/providers/alibabacloud/services/rds/rds_instance_tde_key_custom/rds_instance_tde_key_custom.metadata.json b/prowler/providers/alibabacloud/services/rds/rds_instance_tde_key_custom/rds_instance_tde_key_custom.metadata.json
index 5b911a5ace..24fe497d8d 100644
--- a/prowler/providers/alibabacloud/services/rds/rds_instance_tde_key_custom/rds_instance_tde_key_custom.metadata.json
+++ b/prowler/providers/alibabacloud/services/rds/rds_instance_tde_key_custom/rds_instance_tde_key_custom.metadata.json
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "rds_instance_tde_key_custom",
- "CheckTitle": "RDS instance TDE protector is encrypted with BYOK (Use your own key)",
- "CheckType": [
- "Sensitive file tampering",
- "Intrusion into applications"
- ],
+ "CheckTitle": "RDS instance TDE uses a customer-managed key (BYOK)",
+ "CheckType": [],
"ServiceName": "rds",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:rds:region:account-id:dbinstance/{dbinstance-id}",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudRDSDBInstance",
+ "ResourceType": "ALIYUN::RDS::DBInstance",
"ResourceGroup": "database",
- "Description": "**TDE with BYOK** support provides increased transparency and control, increased security with an HSM-backed KMS service, and promotion of separation of duties.\n\nBased on business needs or criticality of data, it is recommended that the TDE protector is encrypted by a key that is managed by the data owner (**BYOK**).",
- "Risk": "Using **service-managed keys** means the cloud provider manages the encryption keys. **BYOK (Bring Your Own Key)** gives you full control over the key lifecycle and permissions.\n\nThis ensures that even the cloud provider cannot access your data without your explicit permission.",
+ "Description": "**Alibaba Cloud RDS instances** with TDE enabled should use a **customer-managed key (BYOK)** rather than a service-managed key. BYOK provides increased transparency and control over the encryption key lifecycle, enhanced security through an HSM-backed **KMS** service, and promotes separation of duties between the data owner and the cloud provider.",
+ "Risk": "Using **service-managed keys** means the cloud provider manages the encryption keys, limiting the data owner's control over key access and rotation. Without **BYOK (Bring Your Own Key)**, the cloud provider retains the ability to access encrypted data, reducing **confidentiality** assurance and making it harder to enforce **separation of duties** and **key lifecycle management** policies.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/96121.html",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RDS/enable-tde-with-cmk.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RDS/enable-tde-with-cmk.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun rds ModifyDBInstanceTDE --DBInstanceId --TDEStatus Enabled --EncryptionKey ",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **RDS Console**.\n2. Select the target RDS instance.\n3. Go to **Data Security** > **TDE** tab.\n4. Click the switch next to **Disabled** (or modify existing TDE configuration).\n5. In the displayed dialog box, choose **custom key** and select your KMS key.\n6. Click **Confirm**.",
"Terraform": "resource \"alicloud_db_instance\" \"example\" {\n engine = \"MySQL\"\n engine_version = \"8.0\"\n instance_type = \"rds.mysql.s1.small\"\n instance_storage = 20\n tde_status = \"Enabled\"\n encryption_key = alicloud_kms_key.example.id\n}"
},
"Recommendation": {
- "Text": "1. Log on to the **RDS Console**\n2. Go to **Data Security** > **TDE** tab\n3. Click the switch next to **Disabled**\n4. In the displayed dialog box, choose **custom key**\n5. Click **Confirm**",
+ "Text": "Configure TDE on RDS instances to use a **customer-managed key (BYOK)** from KMS for full control over the encryption key lifecycle and enhanced security.",
"Url": "https://hub.prowler.com/check/rds_instance_tde_key_custom"
}
},
diff --git a/prowler/providers/alibabacloud/services/securitycenter/securitycenter_advanced_or_enterprise_edition/securitycenter_advanced_or_enterprise_edition.metadata.json b/prowler/providers/alibabacloud/services/securitycenter/securitycenter_advanced_or_enterprise_edition/securitycenter_advanced_or_enterprise_edition.metadata.json
index ff90197827..429663bc2a 100644
--- a/prowler/providers/alibabacloud/services/securitycenter/securitycenter_advanced_or_enterprise_edition/securitycenter_advanced_or_enterprise_edition.metadata.json
+++ b/prowler/providers/alibabacloud/services/securitycenter/securitycenter_advanced_or_enterprise_edition/securitycenter_advanced_or_enterprise_edition.metadata.json
@@ -1,37 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "securitycenter_advanced_or_enterprise_edition",
- "CheckTitle": "Security Center is Advanced or Enterprise Edition",
- "CheckType": [
- "Suspicious process",
- "Webshell",
- "Unusual logon",
- "Sensitive file tampering",
- "Malicious software",
- "Precision defense"
- ],
+ "CheckTitle": "Security Center is using Advanced or Enterprise Edition",
+ "CheckType": [],
"ServiceName": "securitycenter",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:sas::account-id:security-center",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudSecurityCenter",
+ "ResourceType": "ALIYUN::SAS::Instance",
"ResourceGroup": "security",
- "Description": "The **Advanced or Enterprise Edition** enables threat detection for network and endpoints, providing **malware detection**, **webshell detection**, and **anomaly detection** in Security Center.",
- "Risk": "Using **Basic or Free Edition** of Security Center may not provide comprehensive protection against cloud threats.\n\n**Advanced or Enterprise Edition** allows for full protection to defend against cloud threats.",
+ "Description": "**Alibaba Cloud Security Center** should be running the **Advanced** or **Enterprise Edition** to enable comprehensive threat detection capabilities for network and endpoints. These editions provide **malware detection**, **webshell detection**, **anomaly detection**, and **precision defense** features that are not available in the Basic or Free editions.",
+ "Risk": "Using the **Basic or Free Edition** of Security Center limits threat detection capabilities to basic vulnerability scanning only. Without the **Advanced or Enterprise Edition**, critical protections such as **malware detection**, **intrusion prevention**, **webshell detection**, and **anomalous behavior analysis** are unavailable, leaving workloads exposed to sophisticated cloud threats.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/product/28498.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-SecurityCenter/security-center-plan.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-SecurityCenter/security-center-plan.html"
],
"Remediation": {
"Code": {
- "CLI": "Logon to Security Center Console > Select Overview > Click Upgrade > Select Advanced or Enterprise Edition > Finish order placement",
+ "CLI": "",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **Security Center Console**\n2. Select **Overview**\n3. Click **Upgrade**\n4. Select **Advanced** or **Enterprise Edition**\n5. Finish order placement",
"Terraform": ""
},
"Recommendation": {
- "Text": "1. Log on to the **Security Center Console**\n2. Select **Overview**\n3. Click **Upgrade**\n4. Select **Advanced** or **Enterprise Edition**\n5. Finish order placement",
+ "Text": "Upgrade Security Center to the Advanced or Enterprise Edition to enable comprehensive threat detection including malware detection, webshell detection, and anomaly detection capabilities.",
"Url": "https://hub.prowler.com/check/securitycenter_advanced_or_enterprise_edition"
}
},
diff --git a/prowler/providers/alibabacloud/services/securitycenter/securitycenter_all_assets_agent_installed/securitycenter_all_assets_agent_installed.metadata.json b/prowler/providers/alibabacloud/services/securitycenter/securitycenter_all_assets_agent_installed/securitycenter_all_assets_agent_installed.metadata.json
index 97decc7e0a..bd63eed35f 100644
--- a/prowler/providers/alibabacloud/services/securitycenter/securitycenter_all_assets_agent_installed/securitycenter_all_assets_agent_installed.metadata.json
+++ b/prowler/providers/alibabacloud/services/securitycenter/securitycenter_all_assets_agent_installed/securitycenter_all_assets_agent_installed.metadata.json
@@ -1,36 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "securitycenter_all_assets_agent_installed",
- "CheckTitle": "All assets are installed with security agent",
- "CheckType": [
- "Suspicious process",
- "Webshell",
- "Unusual logon",
- "Sensitive file tampering",
- "Malicious software"
- ],
+ "CheckTitle": "All assets have the Security Center agent installed",
+ "CheckType": [],
"ServiceName": "securitycenter",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:sas:region:account-id:machine/{machine-id}",
+ "ResourceIdTemplate": "",
"Severity": "high",
- "ResourceType": "AlibabaCloudSecurityCenterMachine",
+ "ResourceType": "ALIYUN::SAS::Instance",
"ResourceGroup": "security",
- "Description": "The endpoint protection of **Security Center** requires an agent to be installed on the endpoint to work. Such an agent-based approach allows the security center to provide comprehensive endpoint intrusion detection and protection capabilities.\n\nThis includes remote logon detection, **webshell detection** and removal, **anomaly detection** (detection of abnormal process behaviors and network connections), and detection of changes in key files and suspicious accounts.",
- "Risk": "Assets without **Security Center agent** installed are not protected by endpoint intrusion detection and protection capabilities, leaving them vulnerable to security threats.\n\nUnprotected assets become blind spots in your security monitoring.",
+ "Description": "**Alibaba Cloud Security Center** requires an agent to be installed on each endpoint to provide comprehensive endpoint intrusion detection and protection capabilities. The agent enables remote logon detection, **webshell detection** and removal, **anomaly detection** of abnormal process behaviors and network connections, and monitoring of changes to key files and suspicious accounts.",
+ "Risk": "Assets without the **Security Center agent** installed become blind spots in security monitoring, as they are not protected by endpoint intrusion detection capabilities. This leaves them vulnerable to **malware infections**, **unauthorized access**, **webshell attacks**, and **anomalous process execution** without any alerts being generated.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/111650.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-SecurityCenter/install-security-agent.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-SecurityCenter/install-security-agent.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun sas InstallUninstallAegis --InstanceIds ,",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **Security Center Console**\n2. Select **Settings**\n3. Click **Agent**\n4. On the `Client to be installed` tab, select all items on the list\n5. Click **One-click installation** to install the agent on all assets",
"Terraform": ""
},
"Recommendation": {
- "Text": "1. Log on to the **Security Center Console**\n2. Select **Settings**\n3. Click **Agent**\n4. On the `Client to be installed` tab, select all items on the list\n5. Click **One-click installation** to install the agent on all assets",
+ "Text": "Install the Security Center agent on all assets to enable comprehensive endpoint intrusion detection and protection, including webshell detection, anomaly detection, and remote logon monitoring.",
"Url": "https://hub.prowler.com/check/securitycenter_all_assets_agent_installed"
}
},
diff --git a/prowler/providers/alibabacloud/services/securitycenter/securitycenter_notification_enabled_high_risk/securitycenter_notification_enabled_high_risk.metadata.json b/prowler/providers/alibabacloud/services/securitycenter/securitycenter_notification_enabled_high_risk/securitycenter_notification_enabled_high_risk.metadata.json
index 480e46fb8b..c688baaa48 100644
--- a/prowler/providers/alibabacloud/services/securitycenter/securitycenter_notification_enabled_high_risk/securitycenter_notification_enabled_high_risk.metadata.json
+++ b/prowler/providers/alibabacloud/services/securitycenter/securitycenter_notification_enabled_high_risk/securitycenter_notification_enabled_high_risk.metadata.json
@@ -1,36 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "securitycenter_notification_enabled_high_risk",
- "CheckTitle": "Notification is enabled on all high risk items",
- "CheckType": [
- "Suspicious process",
- "Webshell",
- "Unusual logon",
- "Sensitive file tampering",
- "Malicious software"
- ],
+ "CheckTitle": "Notifications are enabled for all high-risk items in Security Center",
+ "CheckType": [],
"ServiceName": "securitycenter",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:sas::account-id:notice-config/{project}",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudSecurityCenterNoticeConfig",
+ "ResourceType": "ALIYUN::SAS::Instance",
"ResourceGroup": "security",
- "Description": "Enable all **risk item notifications** in Vulnerability, Baseline Risks, Alerts, and AccessKey Leak event detection categories.\n\nThis ensures that relevant security operators receive notifications as soon as security events occur.",
- "Risk": "Without **notifications enabled** for high-risk items, security operators may not be aware of critical security events in a timely manner, potentially leading to **delayed response** and **increased security exposure**.",
+ "Description": "**Alibaba Cloud Security Center** should have all **risk item notifications** enabled across Vulnerability, Baseline Risks, Alerts, and AccessKey Leak event detection categories. This ensures that relevant security operators receive notifications as soon as critical security events occur, enabling timely incident response.",
+ "Risk": "Without **notifications enabled** for high-risk items in Security Center, security operators may not be aware of critical security events such as **vulnerability discoveries**, **baseline violations**, **intrusion alerts**, and **AccessKey leaks** in a timely manner. This leads to **delayed incident response** and **prolonged security exposure**, increasing the potential impact of threats.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/111648.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-SecurityCenter/enable-high-risk-item-notifications.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-SecurityCenter/enable-high-risk-item-notifications.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun sas ModifyNoticeConfig --Project --Route ",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **Security Center Console**\n2. Select **Settings**\n3. Click **Notification**\n4. Enable all high-risk items on Notification setting\n\nRoute values: `1`=text message, `2`=email, `3`=internal message, `4`=text+email, `5`=text+internal, `6`=email+internal, `7`=all methods",
"Terraform": ""
},
"Recommendation": {
- "Text": "1. Log on to the **Security Center Console**\n2. Select **Settings**\n3. Click **Notification**\n4. Enable all high-risk items on Notification setting\n\nRoute values: `1`=text message, `2`=email, `3`=internal message, `4`=text+email, `5`=text+internal, `6`=email+internal, `7`=all methods",
+ "Text": "Enable notifications for all high-risk items in Security Center including vulnerabilities, baseline risks, alerts, and AccessKey leak detection to ensure timely incident response.",
"Url": "https://hub.prowler.com/check/securitycenter_notification_enabled_high_risk"
}
},
diff --git a/prowler/providers/alibabacloud/services/securitycenter/securitycenter_vulnerability_scan_enabled/securitycenter_vulnerability_scan_enabled.metadata.json b/prowler/providers/alibabacloud/services/securitycenter/securitycenter_vulnerability_scan_enabled/securitycenter_vulnerability_scan_enabled.metadata.json
index c302a79842..6d2033f02f 100644
--- a/prowler/providers/alibabacloud/services/securitycenter/securitycenter_vulnerability_scan_enabled/securitycenter_vulnerability_scan_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/securitycenter/securitycenter_vulnerability_scan_enabled/securitycenter_vulnerability_scan_enabled.metadata.json
@@ -2,32 +2,29 @@
"Provider": "alibabacloud",
"CheckID": "securitycenter_vulnerability_scan_enabled",
"CheckTitle": "Scheduled vulnerability scan is enabled on all servers",
- "CheckType": [
- "Malicious software",
- "Web application threat detection"
- ],
+ "CheckType": [],
"ServiceName": "securitycenter",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:sas::account-id:vulnerability-scan-config",
+ "ResourceIdTemplate": "",
"Severity": "high",
- "ResourceType": "AlibabaCloudSecurityCenterVulConfig",
+ "ResourceType": "ALIYUN::SAS::Instance",
"ResourceGroup": "security",
- "Description": "Ensure that **scheduled vulnerability scan** is enabled on all servers.\n\nBe sure that vulnerability scanning is performed periodically to discover system vulnerabilities in time.",
- "Risk": "Without **scheduled vulnerability scans** enabled, system vulnerabilities may not be discovered in a timely manner, leaving systems exposed to **known security threats** and **exploits**.",
+ "Description": "**Alibaba Cloud Security Center** should have **scheduled vulnerability scanning** enabled on all servers to periodically discover system vulnerabilities. The scan should cover all vulnerability types including `yum`, `cve`, `sys`, `cms`, and `emg` to ensure comprehensive detection of known security weaknesses across the infrastructure.",
+ "Risk": "Without **scheduled vulnerability scans** enabled, system vulnerabilities may remain undetected for extended periods. This leaves servers exposed to **known security exploits**, **privilege escalation attacks**, and **malware infections** that target unpatched software, increasing the overall attack surface and risk of compromise.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/109076.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-SecurityCenter/enable-scheduled-vulnerability-scan.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-SecurityCenter/enable-scheduled-vulnerability-scan.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun sas ModifyVulConfig --Type --Config on",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **Security Center Console**\n2. Select **Vulnerabilities**\n3. Click **Settings**\n4. Apply all types of vulnerabilities (`yum`, `cve`, `sys`, `cms`, `emg`)\n5. Enable **High** (asap) and **Medium** (later) vulnerability scan levels",
"Terraform": ""
},
"Recommendation": {
- "Text": "1. Log on to the **Security Center Console**\n2. Select **Vulnerabilities**\n3. Click **Settings**\n4. Apply all types of vulnerabilities (`yum`, `cve`, `sys`, `cms`, `emg`)\n5. Enable **High** (asap) and **Medium** (later) vulnerability scan levels",
+ "Text": "Enable scheduled vulnerability scanning on all servers in Security Center, covering all vulnerability types to ensure timely discovery and remediation of known security weaknesses.",
"Url": "https://hub.prowler.com/check/securitycenter_vulnerability_scan_enabled"
}
},
diff --git a/prowler/providers/alibabacloud/services/sls/sls_cloud_firewall_changes_alert_enabled/sls_cloud_firewall_changes_alert_enabled.metadata.json b/prowler/providers/alibabacloud/services/sls/sls_cloud_firewall_changes_alert_enabled/sls_cloud_firewall_changes_alert_enabled.metadata.json
index 94baf55bc5..0e64eb6412 100644
--- a/prowler/providers/alibabacloud/services/sls/sls_cloud_firewall_changes_alert_enabled/sls_cloud_firewall_changes_alert_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/sls/sls_cloud_firewall_changes_alert_enabled/sls_cloud_firewall_changes_alert_enabled.metadata.json
@@ -2,28 +2,25 @@
"Provider": "alibabacloud",
"CheckID": "sls_cloud_firewall_changes_alert_enabled",
"CheckTitle": "Log monitoring and alerts are set up for Cloud Firewall changes",
- "CheckType": [
- "Suspicious network connection",
- "Cloud threat detection"
- ],
+ "CheckType": [],
"ServiceName": "sls",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:log:region:account-id:project/project-name/alert/alert-name",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudSLSAlert",
+ "ResourceType": "ALIYUN::SLS::Alert",
"ResourceGroup": "monitoring",
- "Description": "It is recommended that a **metric filter and alarm** be established for **Cloud Firewall** rule changes.",
- "Risk": "Monitoring for **Create** or **Update** firewall rule events gives insight into network access changes and may reduce the time it takes to detect **suspicious activity**.",
+ "Description": "**Alibaba Cloud Simple Log Service (SLS)** should have a metric filter and alarm configured for **Cloud Firewall** rule changes. By directing **ActionTrail** logs to SLS and establishing corresponding query and alert rules, real-time monitoring of firewall modifications can be achieved. This ensures that any creation, update, or deletion of Cloud Firewall control policies is promptly detected and reviewed.",
+ "Risk": "Without monitoring for **Cloud Firewall** changes, unauthorized or accidental modifications to firewall rules may go undetected. This could lead to **network exposure**, allowing malicious traffic to reach protected resources or blocking legitimate traffic. Failure to detect firewall rule changes in a timely manner increases the risk of **data breaches**, **lateral movement**, and **service disruption**.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/en/doc-detail/91784.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-SLS/cloudfirewall-control-policy-changes-alert.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-SLS/cloudfirewall-control-policy-changes-alert.html"
],
"Remediation": {
"Code": {
- "CLI": "",
+ "CLI": "aliyun sls create-alert --project --alert-name cloud-firewall-changes --alert-displayname 'Cloud Firewall Changes Alert' --condition 'event.serviceName: CloudFirewall and (event.eventName: CreateControlPolicy or event.eventName: ModifyControlPolicy or event.eventName: DeleteControlPolicy)' --dashboard --schedule '{\"type\":\"FixedRate\",\"interval\":\"1m\"}'",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **SLS Console**.\n2. Ensure **ActionTrail** is enabled and delivering logs to a **Log Service** project.\n3. Navigate to the project receiving ActionTrail logs.\n4. Select **Alerts** and click **Create Alert Rule**.\n5. Configure the query to filter for Cloud Firewall changes: `event.serviceName: CloudFirewall and (event.eventName: CreateControlPolicy or event.eventName: ModifyControlPolicy or event.eventName: DeleteControlPolicy)`.\n6. Set the alert **schedule**, **notification method**, and **severity**.\n7. Save and enable the alert rule.",
"Terraform": ""
},
"Recommendation": {
diff --git a/prowler/providers/alibabacloud/services/sls/sls_customer_created_cmk_changes_alert_enabled/sls_customer_created_cmk_changes_alert_enabled.metadata.json b/prowler/providers/alibabacloud/services/sls/sls_customer_created_cmk_changes_alert_enabled/sls_customer_created_cmk_changes_alert_enabled.metadata.json
index 2c564f7bdb..0aacaf5541 100644
--- a/prowler/providers/alibabacloud/services/sls/sls_customer_created_cmk_changes_alert_enabled/sls_customer_created_cmk_changes_alert_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/sls/sls_customer_created_cmk_changes_alert_enabled/sls_customer_created_cmk_changes_alert_enabled.metadata.json
@@ -1,29 +1,26 @@
{
"Provider": "alibabacloud",
"CheckID": "sls_customer_created_cmk_changes_alert_enabled",
- "CheckTitle": "A log monitoring and alerts are set up for disabling or deletion of customer created CMKs",
- "CheckType": [
- "Sensitive file tampering",
- "Cloud threat detection"
- ],
+ "CheckTitle": "A log monitoring and alert is set up for disabling or deletion of customer created CMKs",
+ "CheckType": [],
"ServiceName": "sls",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:log:region:account-id:project/project-name/alert/alert-name",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudSLSAlert",
+ "ResourceType": "ALIYUN::SLS::Alert",
"ResourceGroup": "monitoring",
- "Description": "Real-time monitoring of API calls can be achieved by directing **ActionTrail Logs** to Log Service and establishing corresponding query and alarms.\n\nIt is recommended that a query and alarm be established for customer-created **KMS keys** which have changed state to disabled or deletion.",
- "Risk": "Data encrypted with **disabled or deleted keys** will no longer be accessible.\n\nThis could lead to **data loss** or **business disruption** if keys are inadvertently or maliciously disabled.",
+ "Description": "**Alibaba Cloud Simple Log Service (SLS)** should have a query and alarm configured for customer-created **KMS Customer Master Keys (CMKs)** that have changed state to disabled or scheduled for deletion. Real-time monitoring of API calls can be achieved by directing **ActionTrail** logs to SLS and establishing corresponding alert rules. This ensures that any disabling or deletion of encryption keys is promptly detected, preventing accidental or malicious loss of access to encrypted data.",
+ "Risk": "Without monitoring for **CMK state changes**, data encrypted with **disabled or deleted keys** will become permanently inaccessible. This could lead to **data loss**, **business disruption**, and **compliance violations**. Malicious actors who gain access to the KMS service could silently disable or schedule deletion of critical encryption keys, rendering protected data unrecoverable if not detected in time.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/en/doc-detail/91784.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-SLS/kms-cmk-config-changes-alert.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-SLS/kms-cmk-config-changes-alert.html"
],
"Remediation": {
"Code": {
- "CLI": "",
+ "CLI": "aliyun sls create-alert --project --alert-name cmk-changes --alert-displayname 'CMK Changes Alert' --condition 'event.serviceName: Kms and (event.eventName: DisableKey or event.eventName: ScheduleKeyDeletion)' --dashboard --schedule '{\"type\":\"FixedRate\",\"interval\":\"1m\"}'",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **SLS Console**.\n2. Ensure **ActionTrail** is enabled and delivering logs to a **Log Service** project.\n3. Navigate to the project receiving ActionTrail logs.\n4. Select **Alerts** and click **Create Alert Rule**.\n5. Configure the query to filter for CMK changes: `event.serviceName: Kms and (event.eventName: DisableKey or event.eventName: ScheduleKeyDeletion)`.\n6. Set the alert **schedule**, **notification method**, and **severity**.\n7. Save and enable the alert rule.",
"Terraform": ""
},
"Recommendation": {
diff --git a/prowler/providers/alibabacloud/services/sls/sls_logstore_retention_period/sls_logstore_retention_period.metadata.json b/prowler/providers/alibabacloud/services/sls/sls_logstore_retention_period/sls_logstore_retention_period.metadata.json
index 6b5b4e5420..828a15bfb7 100644
--- a/prowler/providers/alibabacloud/services/sls/sls_logstore_retention_period/sls_logstore_retention_period.metadata.json
+++ b/prowler/providers/alibabacloud/services/sls/sls_logstore_retention_period/sls_logstore_retention_period.metadata.json
@@ -2,28 +2,26 @@
"Provider": "alibabacloud",
"CheckID": "sls_logstore_retention_period",
"CheckTitle": "Logstore data retention period is set to the recommended period (default 365 days)",
- "CheckType": [
- "Cloud threat detection"
- ],
+ "CheckType": [],
"ServiceName": "sls",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:log:region:account-id:project/project-name/logstore/logstore-name",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudSLSLogStore",
+ "ResourceType": "ALIYUN::SLS::Logstore",
"ResourceGroup": "monitoring",
- "Description": "Ensure **Activity Log Retention** is set for **365 days** or greater.",
- "Risk": "Logstore lifecycle controls how your activity log is exported and retained. It is recommended to retain your activity log for **365 days or more** to have time to respond to any incidents.\n\nShort retention periods may result in loss of **forensic evidence** needed for security investigations.",
+ "Description": "**Alibaba Cloud Simple Log Service (SLS)** Logstore data retention should be configured for at least **365 days**. The Logstore retention period controls how long activity logs are stored and available for analysis. Ensuring a minimum retention of `365` days provides sufficient time to investigate security incidents, perform forensic analysis, and meet regulatory compliance requirements.",
+ "Risk": "Insufficient log retention periods may result in the **loss of forensic evidence** needed for security investigations and incident response. If logs are deleted before an incident is detected, it becomes impossible to determine the scope, impact, and root cause of security breaches. Short retention periods may also lead to **compliance violations** with regulations that mandate specific log retention durations, affecting the organization's **integrity** and **accountability**.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/48990.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-SLS/sufficient-logstore-data-retention-period.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-SLS/sufficient-logstore-data-retention-period.html"
],
"Remediation": {
"Code": {
- "CLI": "",
+ "CLI": "aliyun sls update-logstore --project --logstore --ttl 365",
"NativeIaC": "",
- "Other": "",
- "Terraform": ""
+ "Other": "1. Log on to the **SLS Console**.\n2. Find the project in the **Projects** section.\n3. Click the **Modify** icon next to the target Logstore.\n4. Set the `Data Retention Period` to `365` days or greater.\n5. Click **Save** to apply the changes.",
+ "Terraform": "resource \"alicloud_log_store\" \"example\" {\n project = alicloud_log_project.example.name\n name = \"example-logstore\"\n retention_period = 365\n}"
},
"Recommendation": {
"Text": "1. Log on to the **SLS Console**\n2. Find the project in the Projects section\n3. Click **Modify** icon next to the Logstore\n4. Modify the `Data Retention Period` to `365` or greater",
diff --git a/prowler/providers/alibabacloud/services/sls/sls_management_console_authentication_failures_alert_enabled/sls_management_console_authentication_failures_alert_enabled.metadata.json b/prowler/providers/alibabacloud/services/sls/sls_management_console_authentication_failures_alert_enabled/sls_management_console_authentication_failures_alert_enabled.metadata.json
index dac8630a84..a99cff9645 100644
--- a/prowler/providers/alibabacloud/services/sls/sls_management_console_authentication_failures_alert_enabled/sls_management_console_authentication_failures_alert_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/sls/sls_management_console_authentication_failures_alert_enabled/sls_management_console_authentication_failures_alert_enabled.metadata.json
@@ -1,29 +1,26 @@
{
"Provider": "alibabacloud",
"CheckID": "sls_management_console_authentication_failures_alert_enabled",
- "CheckTitle": "A log monitoring and alerts are set up for Management Console authentication failures",
- "CheckType": [
- "Unusual logon",
- "Abnormal account"
- ],
+ "CheckTitle": "A log monitoring and alert is set up for Management Console authentication failures",
+ "CheckType": [],
"ServiceName": "sls",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:log:region:account-id:project/project-name/alert/alert-name",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudSLSAlert",
+ "ResourceType": "ALIYUN::SLS::Alert",
"ResourceGroup": "monitoring",
- "Description": "Real-time monitoring of API calls can be achieved by directing **ActionTrail Logs** to Log Service and establishing corresponding query and alarms.\n\nIt is recommended that a query and alarm be established for **failed console authentication attempts**.",
- "Risk": "Monitoring **failed console logins** may decrease lead time to detect an attempt to **brute force** a credential, which may provide an indicator (such as source IP) that can be used in other event correlation.",
+ "Description": "**Alibaba Cloud Simple Log Service (SLS)** should have a query and alarm configured for **failed console authentication attempts**. Real-time monitoring of API calls can be achieved by directing **ActionTrail** logs to SLS and establishing corresponding alert rules. This ensures that repeated login failures are detected promptly, enabling early identification of brute-force attacks or credential stuffing attempts against the Management Console.",
+ "Risk": "Without monitoring for **failed console authentication attempts**, brute-force attacks and credential stuffing campaigns may go undetected. Failure to identify these patterns increases the risk of **unauthorized access** to the Management Console. Monitoring failed logins provides critical indicators such as source IP addresses that can be used for **threat correlation** and proactive blocking, reducing the time to detect and respond to **account compromise** attempts.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/en/doc-detail/91784.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-SLS/account-continuous-login-failures-alert.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-SLS/account-continuous-login-failures-alert.html"
],
"Remediation": {
"Code": {
- "CLI": "",
+ "CLI": "aliyun sls create-alert --project --alert-name console-auth-failures --alert-displayname 'Console Authentication Failures Alert' --condition 'event.eventName: ConsoleSignin and event.errorCode: *' --dashboard --schedule '{\"type\":\"FixedRate\",\"interval\":\"1m\"}'",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **SLS Console**.\n2. Ensure **ActionTrail** is enabled and delivering logs to a **Log Service** project.\n3. Navigate to the project receiving ActionTrail logs.\n4. Select **Alerts** and click **Create Alert Rule**.\n5. Configure the query to filter for failed console authentication: `event.eventName: ConsoleSignin and event.errorCode: *`.\n6. Set the alert **schedule**, **notification method**, and **severity**.\n7. Save and enable the alert rule.",
"Terraform": ""
},
"Recommendation": {
diff --git a/prowler/providers/alibabacloud/services/sls/sls_management_console_signin_without_mfa_alert_enabled/sls_management_console_signin_without_mfa_alert_enabled.metadata.json b/prowler/providers/alibabacloud/services/sls/sls_management_console_signin_without_mfa_alert_enabled/sls_management_console_signin_without_mfa_alert_enabled.metadata.json
index 14cb54b236..f67c4faf3c 100644
--- a/prowler/providers/alibabacloud/services/sls/sls_management_console_signin_without_mfa_alert_enabled/sls_management_console_signin_without_mfa_alert_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/sls/sls_management_console_signin_without_mfa_alert_enabled/sls_management_console_signin_without_mfa_alert_enabled.metadata.json
@@ -1,29 +1,26 @@
{
"Provider": "alibabacloud",
"CheckID": "sls_management_console_signin_without_mfa_alert_enabled",
- "CheckTitle": "A log monitoring and alerts are set up for Management Console sign-in without MFA",
- "CheckType": [
- "Unusual logon",
- "Abnormal account"
- ],
+ "CheckTitle": "A log monitoring and alert is set up for Management Console sign-in without MFA",
+ "CheckType": [],
"ServiceName": "sls",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:log:region:account-id:project/project-name/alert/alert-name",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudSLSAlert",
+ "ResourceType": "ALIYUN::SLS::Alert",
"ResourceGroup": "monitoring",
- "Description": "Real-time monitoring of API calls can be achieved by directing **ActionTrail Logs** to Log Service and establishing corresponding query and alarms.\n\nIt is recommended that a query and alarm be established for console logins that are not protected by **multi-factor authentication (MFA)**.",
- "Risk": "Monitoring for **single-factor console logins** will increase visibility into accounts that are not protected by MFA.\n\nThis helps identify potential security gaps in authentication enforcement.",
+ "Description": "**Alibaba Cloud Simple Log Service (SLS)** should have a query and alarm configured for console logins that are not protected by **multi-factor authentication (MFA)**. Real-time monitoring of API calls can be achieved by directing **ActionTrail** logs to SLS and establishing corresponding alert rules. This ensures that any single-factor console sign-in events are detected, helping identify accounts that bypass MFA enforcement policies.",
+ "Risk": "Without monitoring for **single-factor console logins**, accounts that are not protected by MFA may go unnoticed. This increases the risk of **unauthorized access** through compromised credentials, as passwords alone are insufficient to prevent account takeover. Failure to enforce and monitor MFA compliance weakens the overall **authentication posture** and may lead to **privilege escalation** or **data breaches** if an attacker gains access to an unprotected account.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/en/doc-detail/91784.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-SLS/single-factor-console-logins-alert.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-SLS/single-factor-console-logins-alert.html"
],
"Remediation": {
"Code": {
- "CLI": "",
+ "CLI": "aliyun sls create-alert --project --alert-name console-signin-no-mfa --alert-displayname 'Console Sign-in Without MFA Alert' --condition 'event.eventName: ConsoleSignin and event.additionalEventData.MFAUsed: false' --dashboard --schedule '{\"type\":\"FixedRate\",\"interval\":\"1m\"}'",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **SLS Console**.\n2. Ensure **ActionTrail** is enabled and delivering logs to a **Log Service** project.\n3. Navigate to the project receiving ActionTrail logs.\n4. Select **Alerts** and click **Create Alert Rule**.\n5. Configure the query to filter for sign-in without MFA: `event.eventName: ConsoleSignin and event.additionalEventData.MFAUsed: false`.\n6. Set the alert **schedule**, **notification method**, and **severity**.\n7. Save and enable the alert rule.",
"Terraform": ""
},
"Recommendation": {
diff --git a/prowler/providers/alibabacloud/services/sls/sls_oss_bucket_policy_changes_alert_enabled/sls_oss_bucket_policy_changes_alert_enabled.metadata.json b/prowler/providers/alibabacloud/services/sls/sls_oss_bucket_policy_changes_alert_enabled/sls_oss_bucket_policy_changes_alert_enabled.metadata.json
index 60c37a3b6f..df8c4abfa0 100644
--- a/prowler/providers/alibabacloud/services/sls/sls_oss_bucket_policy_changes_alert_enabled/sls_oss_bucket_policy_changes_alert_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/sls/sls_oss_bucket_policy_changes_alert_enabled/sls_oss_bucket_policy_changes_alert_enabled.metadata.json
@@ -1,29 +1,26 @@
{
"Provider": "alibabacloud",
"CheckID": "sls_oss_bucket_policy_changes_alert_enabled",
- "CheckTitle": "A log monitoring and alerts are set up for OSS bucket policy changes",
- "CheckType": [
- "Sensitive file tampering",
- "Cloud threat detection"
- ],
+ "CheckTitle": "A log monitoring and alerts is set up for OSS bucket policy changes",
+ "CheckType": [],
"ServiceName": "sls",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:log:region:account-id:project/project-name/alert/alert-name",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudSLSAlert",
+ "ResourceType": "ALIYUN::SLS::Alert",
"ResourceGroup": "monitoring",
- "Description": "Real-time monitoring of API calls can be achieved by directing **ActionTrail Logs** to Log Service and establishing corresponding query and alarms.\n\nIt is recommended that a query and alarm be established for changes to **OSS bucket policies**.",
- "Risk": "Monitoring changes to **OSS bucket policies** may reduce time to detect and correct **permissive policies** on sensitive OSS buckets.\n\nThis helps prevent unintended data exposure.",
+ "Description": "**Alibaba Cloud Simple Log Service (SLS)** should have a query and alarm configured for changes to **OSS bucket policies**. Real-time monitoring of API calls can be achieved by directing **ActionTrail** logs to SLS and establishing corresponding alert rules. This ensures that any modifications to bucket access policies are detected promptly, enabling quick identification of potentially dangerous permission changes on sensitive storage resources.",
+ "Risk": "Without monitoring for **OSS bucket policy changes**, permissive or malicious policy modifications may go undetected. This could lead to **unintended data exposure**, allowing unauthorized users to access, modify, or delete sensitive objects stored in OSS buckets. Delayed detection of policy changes increases the risk of **data breaches**, **data exfiltration**, and **compliance violations**, as attackers could silently widen access to critical storage resources.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/en/doc-detail/91784.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-SLS/oss-bucket-authority-changes-alert.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-SLS/oss-bucket-authority-changes-alert.html"
],
"Remediation": {
"Code": {
- "CLI": "",
+ "CLI": "aliyun sls create-alert --project --alert-name oss-bucket-policy-changes --alert-displayname 'OSS Bucket Policy Changes Alert' --condition 'event.serviceName: Oss and (event.eventName: PutBucketPolicy or event.eventName: DeleteBucketPolicy)' --dashboard --schedule '{\"type\":\"FixedRate\",\"interval\":\"1m\"}'",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **SLS Console**.\n2. Ensure **ActionTrail** is enabled and delivering logs to a **Log Service** project.\n3. Navigate to the project receiving ActionTrail logs.\n4. Select **Alerts** and click **Create Alert Rule**.\n5. Configure the query to filter for OSS bucket policy changes: `event.serviceName: Oss and (event.eventName: PutBucketPolicy or event.eventName: DeleteBucketPolicy)`.\n6. Set the alert **schedule**, **notification method**, and **severity**.\n7. Save and enable the alert rule.",
"Terraform": ""
},
"Recommendation": {
diff --git a/prowler/providers/alibabacloud/services/sls/sls_oss_permission_changes_alert_enabled/sls_oss_permission_changes_alert_enabled.metadata.json b/prowler/providers/alibabacloud/services/sls/sls_oss_permission_changes_alert_enabled/sls_oss_permission_changes_alert_enabled.metadata.json
index 2ca4e36872..b6ec2c6aa1 100644
--- a/prowler/providers/alibabacloud/services/sls/sls_oss_permission_changes_alert_enabled/sls_oss_permission_changes_alert_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/sls/sls_oss_permission_changes_alert_enabled/sls_oss_permission_changes_alert_enabled.metadata.json
@@ -2,28 +2,25 @@
"Provider": "alibabacloud",
"CheckID": "sls_oss_permission_changes_alert_enabled",
"CheckTitle": "Log monitoring and alerts are set up for OSS permission changes",
- "CheckType": [
- "Sensitive file tampering",
- "Cloud threat detection"
- ],
+ "CheckType": [],
"ServiceName": "sls",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:log:region:account-id:project/project-name/alert/alert-name",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudSLSAlert",
+ "ResourceType": "ALIYUN::SLS::Alert",
"ResourceGroup": "monitoring",
- "Description": "It is recommended that a **metric filter and alarm** be established for **OSS Bucket RAM** changes.",
- "Risk": "Monitoring changes to **OSS permissions** may reduce time to detect and correct permissions on sensitive OSS buckets and objects inside the bucket.\n\nThis helps prevent **unauthorized access** to stored data.",
+ "Description": "**Alibaba Cloud Simple Log Service (SLS)** should have a metric filter and alarm configured for **OSS Bucket RAM** permission changes. By directing **ActionTrail** logs to SLS and establishing corresponding alert rules, real-time monitoring of OSS permission modifications can be achieved. This ensures that changes to bucket-level access controls and RAM policies affecting OSS resources are promptly detected and reviewed.",
+ "Risk": "Without monitoring for **OSS permission changes**, unauthorized modifications to bucket access controls may go undetected. This could allow attackers to grant themselves or others **unauthorized access** to sensitive objects stored in OSS buckets. Delayed detection of permission changes increases the risk of **data exfiltration**, **data tampering**, and **compliance violations**, as the confidentiality and integrity of stored data may be compromised.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/en/doc-detail/91784.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-SLS/oss-bucket-permission-changes-alert.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-SLS/oss-bucket-permission-changes-alert.html"
],
"Remediation": {
"Code": {
- "CLI": "",
+ "CLI": "aliyun sls create-alert --project --alert-name oss-permission-changes --alert-displayname 'OSS Permission Changes Alert' --condition 'event.serviceName: Oss and (event.eventName: PutBucketAcl or event.eventName: PutObjectAcl)' --dashboard --schedule '{\"type\":\"FixedRate\",\"interval\":\"1m\"}'",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **SLS Console**.\n2. Ensure **ActionTrail** is enabled and delivering logs to a **Log Service** project.\n3. Navigate to the project receiving ActionTrail logs.\n4. Select **Alerts** and click **Create Alert Rule**.\n5. Configure the query to filter for OSS permission changes: `event.serviceName: Oss and (event.eventName: PutBucketAcl or event.eventName: PutObjectAcl)`.\n6. Set the alert **schedule**, **notification method**, and **severity**.\n7. Save and enable the alert rule.",
"Terraform": ""
},
"Recommendation": {
diff --git a/prowler/providers/alibabacloud/services/sls/sls_ram_role_changes_alert_enabled/sls_ram_role_changes_alert_enabled.metadata.json b/prowler/providers/alibabacloud/services/sls/sls_ram_role_changes_alert_enabled/sls_ram_role_changes_alert_enabled.metadata.json
index a30d12cd0b..448cfa58c2 100644
--- a/prowler/providers/alibabacloud/services/sls/sls_ram_role_changes_alert_enabled/sls_ram_role_changes_alert_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/sls/sls_ram_role_changes_alert_enabled/sls_ram_role_changes_alert_enabled.metadata.json
@@ -2,28 +2,25 @@
"Provider": "alibabacloud",
"CheckID": "sls_ram_role_changes_alert_enabled",
"CheckTitle": "Log monitoring and alerts are set up for RAM Role changes",
- "CheckType": [
- "Abnormal account",
- "Cloud threat detection"
- ],
+ "CheckType": [],
"ServiceName": "sls",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:log:region:account-id:project/project-name/alert/alert-name",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudSLSAlert",
+ "ResourceType": "ALIYUN::SLS::Alert",
"ResourceGroup": "monitoring",
- "Description": "It is recommended that a query and alarm be established for **RAM Role** creation, deletion, and updating activities.",
- "Risk": "Monitoring **role creation**, **deletion**, and **updating** activities will help in identifying potential **malicious actions** at an early stage.\n\nUnauthorized role changes could lead to privilege escalation.",
+ "Description": "**Alibaba Cloud Simple Log Service (SLS)** should have a query and alarm configured for **RAM Role** creation, deletion, and updating activities. By directing **ActionTrail** logs to SLS and establishing corresponding alert rules, real-time monitoring of role changes can be achieved. This ensures that any modifications to RAM roles are detected promptly, enabling early identification of unauthorized privilege escalation attempts.",
+ "Risk": "Without monitoring for **RAM role changes**, unauthorized creation, modification, or deletion of roles may go undetected. This could lead to **privilege escalation**, where an attacker creates or modifies roles to gain elevated access to cloud resources. Undetected role changes compromise the **integrity** of the identity and access management framework and may result in **unauthorized access** to sensitive data and services across the entire cloud environment.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/91784.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-SLS/ram-policy-changes-alert.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-SLS/ram-policy-changes-alert.html"
],
"Remediation": {
"Code": {
- "CLI": "",
+ "CLI": "aliyun sls create-alert --project --alert-name ram-role-changes --alert-displayname 'RAM Role Changes Alert' --condition 'event.serviceName: Ram and (event.eventName: CreateRole or event.eventName: DeleteRole or event.eventName: UpdateRole or event.eventName: AttachPolicyToRole or event.eventName: DetachPolicyFromRole)' --dashboard --schedule '{\"type\":\"FixedRate\",\"interval\":\"1m\"}'",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **SLS Console**.\n2. Ensure **ActionTrail** is enabled and delivering logs to a **Log Service** project.\n3. Navigate to the project receiving ActionTrail logs.\n4. Select **Alerts** and click **Create Alert Rule**.\n5. Configure the query to filter for RAM role changes: `event.serviceName: Ram and (event.eventName: CreateRole or event.eventName: DeleteRole or event.eventName: UpdateRole or event.eventName: AttachPolicyToRole or event.eventName: DetachPolicyFromRole)`.\n6. Set the alert **schedule**, **notification method**, and **severity**.\n7. Save and enable the alert rule.",
"Terraform": ""
},
"Recommendation": {
diff --git a/prowler/providers/alibabacloud/services/sls/sls_rds_instance_configuration_changes_alert_enabled/sls_rds_instance_configuration_changes_alert_enabled.metadata.json b/prowler/providers/alibabacloud/services/sls/sls_rds_instance_configuration_changes_alert_enabled/sls_rds_instance_configuration_changes_alert_enabled.metadata.json
index d196b1802d..202a24b167 100644
--- a/prowler/providers/alibabacloud/services/sls/sls_rds_instance_configuration_changes_alert_enabled/sls_rds_instance_configuration_changes_alert_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/sls/sls_rds_instance_configuration_changes_alert_enabled/sls_rds_instance_configuration_changes_alert_enabled.metadata.json
@@ -2,28 +2,25 @@
"Provider": "alibabacloud",
"CheckID": "sls_rds_instance_configuration_changes_alert_enabled",
"CheckTitle": "Log monitoring and alerts are set up for RDS instance configuration changes",
- "CheckType": [
- "Intrusion into applications",
- "Cloud threat detection"
- ],
+ "CheckType": [],
"ServiceName": "sls",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:log:region:account-id:project/project-name/alert/alert-name",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudSLSAlert",
+ "ResourceType": "ALIYUN::SLS::Alert",
"ResourceGroup": "monitoring",
- "Description": "It is recommended that a **metric filter and alarm** be established for **RDS Instance** configuration changes.",
- "Risk": "Monitoring changes to **RDS Instance configuration** may reduce time to detect and correct **misconfigurations** done on database servers.\n\nThis helps prevent security gaps in database deployments.",
+ "Description": "**Alibaba Cloud Simple Log Service (SLS)** should have a metric filter and alarm configured for **RDS Instance** configuration changes. By directing **ActionTrail** logs to SLS and establishing corresponding alert rules, real-time monitoring of database configuration modifications can be achieved. This ensures that any changes to RDS instance settings, such as security parameters, network configurations, or access controls, are promptly detected and reviewed.",
+ "Risk": "Without monitoring for **RDS Instance configuration changes**, unauthorized or accidental modifications to database servers may go undetected. This could lead to **security misconfigurations** such as enabling public access, disabling encryption, or weakening authentication settings. Delayed detection of configuration changes increases the risk of **data breaches**, **unauthorized database access**, and **service disruption**, potentially compromising the **confidentiality** and **integrity** of stored data.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/en/doc-detail/91784.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-SLS/rds-instance-config-changes-alert.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-SLS/rds-instance-config-changes-alert.html"
],
"Remediation": {
"Code": {
- "CLI": "",
+ "CLI": "aliyun sls create-alert --project --alert-name rds-config-changes --alert-displayname 'RDS Instance Configuration Changes Alert' --condition 'event.serviceName: Rds and (event.eventName: ModifyDBInstanceSpec or event.eventName: ModifySecurityIps or event.eventName: ModifyDBInstanceNetworkType)' --dashboard --schedule '{\"type\":\"FixedRate\",\"interval\":\"1m\"}'",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **SLS Console**.\n2. Ensure **ActionTrail** is enabled and delivering logs to a **Log Service** project.\n3. Navigate to the project receiving ActionTrail logs.\n4. Select **Alerts** and click **Create Alert Rule**.\n5. Configure the query to filter for RDS configuration changes: `event.serviceName: Rds and (event.eventName: ModifyDBInstanceSpec or event.eventName: ModifySecurityIps or event.eventName: ModifyDBInstanceNetworkType)`.\n6. Set the alert **schedule**, **notification method**, and **severity**.\n7. Save and enable the alert rule.",
"Terraform": ""
},
"Recommendation": {
diff --git a/prowler/providers/alibabacloud/services/sls/sls_root_account_usage_alert_enabled/sls_root_account_usage_alert_enabled.metadata.json b/prowler/providers/alibabacloud/services/sls/sls_root_account_usage_alert_enabled/sls_root_account_usage_alert_enabled.metadata.json
index e6d271b482..ff8de28cd6 100644
--- a/prowler/providers/alibabacloud/services/sls/sls_root_account_usage_alert_enabled/sls_root_account_usage_alert_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/sls/sls_root_account_usage_alert_enabled/sls_root_account_usage_alert_enabled.metadata.json
@@ -1,29 +1,26 @@
{
"Provider": "alibabacloud",
"CheckID": "sls_root_account_usage_alert_enabled",
- "CheckTitle": "A log monitoring and alerts are set up for usage of root account",
- "CheckType": [
- "Unusual logon",
- "Cloud threat detection"
- ],
+ "CheckTitle": "A log monitoring and alert is set up for usage of root account",
+ "CheckType": [],
"ServiceName": "sls",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:log:region:account-id:project/project-name/alert/alert-name",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudSLSAlert",
+ "ResourceType": "ALIYUN::SLS::Alert",
"ResourceGroup": "monitoring",
- "Description": "Real-time monitoring of API calls can be achieved by directing **ActionTrail Logs** to Log Service and establishing corresponding query and alarms.\n\nIt is recommended that a query and alarm be established for **root account login** attempts.",
- "Risk": "Monitoring for **root account logins** will provide visibility into the use of a fully privileged account and an opportunity to reduce its use.\n\nRoot account usage should be minimized and closely monitored.",
+ "Description": "**Alibaba Cloud Simple Log Service (SLS)** should have a query and alarm configured for **root account login** attempts. Real-time monitoring of API calls can be achieved by directing **ActionTrail** logs to SLS and establishing corresponding alert rules. This ensures that any usage of the fully privileged root account is detected promptly, supporting the principle of least privilege and enabling timely review of root-level operations.",
+ "Risk": "Without monitoring for **root account usage**, activities performed by the most privileged account may go unnoticed. The root account has unrestricted access to all resources and services, making it a high-value target for attackers. Failure to detect unauthorized root account usage increases the risk of **complete account takeover**, **data destruction**, and **irreversible configuration changes** that could compromise the **confidentiality**, **integrity**, and **availability** of all cloud resources.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/en/doc-detail/91784.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-SLS/root-account-login-frequent-alert.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-SLS/root-account-login-frequent-alert.html"
],
"Remediation": {
"Code": {
- "CLI": "",
+ "CLI": "aliyun sls create-alert --project --alert-name root-account-usage --alert-displayname 'Root Account Usage Alert' --condition 'event.userIdentity.type: root-account' --dashboard --schedule '{\"type\":\"FixedRate\",\"interval\":\"1m\"}'",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **SLS Console**.\n2. Ensure **ActionTrail** is enabled and delivering logs to a **Log Service** project.\n3. Navigate to the project receiving ActionTrail logs.\n4. Select **Alerts** and click **Create Alert Rule**.\n5. Configure the query to filter for root account usage: `event.userIdentity.type: root-account`.\n6. Set the alert **schedule**, **notification method**, and **severity**.\n7. Save and enable the alert rule.",
"Terraform": ""
},
"Recommendation": {
diff --git a/prowler/providers/alibabacloud/services/sls/sls_security_group_changes_alert_enabled/sls_security_group_changes_alert_enabled.metadata.json b/prowler/providers/alibabacloud/services/sls/sls_security_group_changes_alert_enabled/sls_security_group_changes_alert_enabled.metadata.json
index eca2fd9b53..b80e7da84b 100644
--- a/prowler/providers/alibabacloud/services/sls/sls_security_group_changes_alert_enabled/sls_security_group_changes_alert_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/sls/sls_security_group_changes_alert_enabled/sls_security_group_changes_alert_enabled.metadata.json
@@ -1,29 +1,26 @@
{
"Provider": "alibabacloud",
"CheckID": "sls_security_group_changes_alert_enabled",
- "CheckTitle": "A log monitoring and alerts are set up for security group changes",
- "CheckType": [
- "Suspicious network connection",
- "Cloud threat detection"
- ],
+ "CheckTitle": "A log monitoring and alert is set up for security group changes",
+ "CheckType": [],
"ServiceName": "sls",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:log:region:account-id:project/project-name/alert/alert-name",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudSLSAlert",
+ "ResourceType": "ALIYUN::SLS::Alert",
"ResourceGroup": "monitoring",
- "Description": "Real-time monitoring of API calls can be achieved by directing **ActionTrail Logs** to Log Service and establishing corresponding query and alarms.\n\n**Security Groups** are a stateful packet filter that controls ingress and egress traffic within a VPC. It is recommended that a query and alarm be established for changes to Security Groups.",
- "Risk": "Monitoring changes to **security groups** will help ensure that resources and services are not unintentionally exposed.\n\nUnauthorized security group modifications could lead to **network exposure** and **unauthorized access**.",
+ "Description": "**Alibaba Cloud Simple Log Service (SLS)** should have a query and alarm configured for changes to **Security Groups**. Real-time monitoring of API calls can be achieved by directing **ActionTrail** logs to SLS and establishing corresponding alert rules. **Security Groups** are stateful packet filters that control ingress and egress traffic within a VPC, and monitoring their changes ensures that network access modifications are promptly detected and reviewed.",
+ "Risk": "Without monitoring for **security group changes**, unauthorized modifications to network access controls may go undetected. This could lead to resources and services being **unintentionally exposed** to the internet or untrusted networks. Unauthorized security group modifications increase the risk of **network exposure**, **unauthorized access**, and **lateral movement** within the cloud environment, potentially compromising the **confidentiality** and **availability** of critical services.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/en/doc-detail/91784.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-SLS/security-group-config-changes-alert.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-SLS/security-group-config-changes-alert.html"
],
"Remediation": {
"Code": {
- "CLI": "",
+ "CLI": "aliyun sls create-alert --project --alert-name security-group-changes --alert-displayname 'Security Group Changes Alert' --condition 'event.serviceName: Ecs and (event.eventName: AuthorizeSecurityGroup or event.eventName: RevokeSecurityGroup or event.eventName: CreateSecurityGroup or event.eventName: DeleteSecurityGroup)' --dashboard --schedule '{\"type\":\"FixedRate\",\"interval\":\"1m\"}'",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **SLS Console**.\n2. Ensure **ActionTrail** is enabled and delivering logs to a **Log Service** project.\n3. Navigate to the project receiving ActionTrail logs.\n4. Select **Alerts** and click **Create Alert Rule**.\n5. Configure the query to filter for security group changes: `event.serviceName: Ecs and (event.eventName: AuthorizeSecurityGroup or event.eventName: RevokeSecurityGroup or event.eventName: CreateSecurityGroup or event.eventName: DeleteSecurityGroup)`.\n6. Set the alert **schedule**, **notification method**, and **severity**.\n7. Save and enable the alert rule.",
"Terraform": ""
},
"Recommendation": {
diff --git a/prowler/providers/alibabacloud/services/sls/sls_unauthorized_api_calls_alert_enabled/sls_unauthorized_api_calls_alert_enabled.metadata.json b/prowler/providers/alibabacloud/services/sls/sls_unauthorized_api_calls_alert_enabled/sls_unauthorized_api_calls_alert_enabled.metadata.json
index 5115561aba..fb2d54f3c0 100644
--- a/prowler/providers/alibabacloud/services/sls/sls_unauthorized_api_calls_alert_enabled/sls_unauthorized_api_calls_alert_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/sls/sls_unauthorized_api_calls_alert_enabled/sls_unauthorized_api_calls_alert_enabled.metadata.json
@@ -1,29 +1,26 @@
{
"Provider": "alibabacloud",
"CheckID": "sls_unauthorized_api_calls_alert_enabled",
- "CheckTitle": "A log monitoring and alerts are set up for unauthorized API calls",
- "CheckType": [
- "Unusual logon",
- "Cloud threat detection"
- ],
+ "CheckTitle": "A log monitoring and alert is set up for unauthorized API calls",
+ "CheckType": [],
"ServiceName": "sls",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:log:region:account-id:project/project-name/alert/alert-name",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudSLSAlert",
+ "ResourceType": "ALIYUN::SLS::Alert",
"ResourceGroup": "monitoring",
- "Description": "Real-time monitoring of API calls can be achieved by directing **ActionTrail Logs** to Log Service and establishing corresponding query and alarms.\n\nIt is recommended that a query and alarm be established for **unauthorized API calls**.",
- "Risk": "Monitoring **unauthorized API calls** will help reveal application errors and may reduce time to detect **malicious activity**.\n\nThis is essential for early detection of potential security breaches.",
+ "Description": "**Alibaba Cloud Simple Log Service (SLS)** should have a query and alarm configured for **unauthorized API calls**. Real-time monitoring of API calls can be achieved by directing **ActionTrail** logs to SLS and establishing corresponding alert rules. This ensures that any API calls resulting in unauthorized errors are detected promptly, helping identify misconfigured applications, compromised credentials, or active reconnaissance by attackers.",
+ "Risk": "Without monitoring for **unauthorized API calls**, patterns of failed access attempts may go undetected. These patterns often indicate **malicious activity** such as attackers probing for permissions or attempting to exploit misconfigured access controls. Failure to detect unauthorized API calls in a timely manner increases the risk of **security breaches**, as it delays identification of compromised credentials and **privilege escalation** attempts across cloud services.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/en/doc-detail/91784.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-SLS/unauthorized-api-calls-alert.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-SLS/unauthorized-api-calls-alert.html"
],
"Remediation": {
"Code": {
- "CLI": "",
+ "CLI": "aliyun sls create-alert --project --alert-name unauthorized-api-calls --alert-displayname 'Unauthorized API Calls Alert' --condition 'event.errorCode: Forbidden or event.errorCode: AccessDenied' --dashboard --schedule '{\"type\":\"FixedRate\",\"interval\":\"1m\"}'",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **SLS Console**.\n2. Ensure **ActionTrail** is enabled and delivering logs to a **Log Service** project.\n3. Navigate to the project receiving ActionTrail logs.\n4. Select **Alerts** and click **Create Alert Rule**.\n5. Configure the query to filter for unauthorized API calls: `event.errorCode: Forbidden or event.errorCode: AccessDenied`.\n6. Set the alert **schedule**, **notification method**, and **severity**.\n7. Save and enable the alert rule.",
"Terraform": ""
},
"Recommendation": {
diff --git a/prowler/providers/alibabacloud/services/sls/sls_vpc_changes_alert_enabled/sls_vpc_changes_alert_enabled.metadata.json b/prowler/providers/alibabacloud/services/sls/sls_vpc_changes_alert_enabled/sls_vpc_changes_alert_enabled.metadata.json
index e922cd50c9..fe091fd369 100644
--- a/prowler/providers/alibabacloud/services/sls/sls_vpc_changes_alert_enabled/sls_vpc_changes_alert_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/sls/sls_vpc_changes_alert_enabled/sls_vpc_changes_alert_enabled.metadata.json
@@ -2,28 +2,25 @@
"Provider": "alibabacloud",
"CheckID": "sls_vpc_changes_alert_enabled",
"CheckTitle": "Log monitoring and alerts are set up for VPC changes",
- "CheckType": [
- "Suspicious network connection",
- "Cloud threat detection"
- ],
+ "CheckType": [],
"ServiceName": "sls",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:log:region:account-id:project/project-name/alert/alert-name",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudSLSAlert",
+ "ResourceType": "ALIYUN::SLS::Alert",
"ResourceGroup": "monitoring",
- "Description": "It is recommended that a **log search/analysis query and alarm** be established for **VPC changes**.",
- "Risk": "Monitoring changes to **VPC** will help ensure VPC traffic flow is not getting impacted.\n\nUnauthorized VPC modifications could disrupt network connectivity or create security vulnerabilities.",
+ "Description": "**Alibaba Cloud Simple Log Service (SLS)** should have a log search/analysis query and alarm configured for **VPC** changes. By directing **ActionTrail** logs to SLS and establishing corresponding alert rules, real-time monitoring of VPC modifications can be achieved. This ensures that any creation, deletion, or modification of VPCs and their associated components is promptly detected and reviewed.",
+ "Risk": "Without monitoring for **VPC changes**, unauthorized or accidental modifications to virtual network infrastructure may go undetected. This could disrupt **network connectivity**, create **security vulnerabilities**, or expose internal resources to untrusted networks. Unauthorized VPC modifications increase the risk of **service disruption**, **data interception**, and **lateral movement** within the cloud environment, potentially compromising the **confidentiality** and **availability** of all connected resources.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/en/doc-detail/91784.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-SLS/vpc-config-changes-alert.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-SLS/vpc-config-changes-alert.html"
],
"Remediation": {
"Code": {
- "CLI": "",
+ "CLI": "aliyun sls create-alert --project --alert-name vpc-changes --alert-displayname 'VPC Changes Alert' --condition 'event.serviceName: Vpc and (event.eventName: CreateVpc or event.eventName: DeleteVpc or event.eventName: ModifyVpcAttribute)' --dashboard --schedule '{\"type\":\"FixedRate\",\"interval\":\"1m\"}'",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **SLS Console**.\n2. Ensure **ActionTrail** is enabled and delivering logs to a **Log Service** project.\n3. Navigate to the project receiving ActionTrail logs.\n4. Select **Alerts** and click **Create Alert Rule**.\n5. Configure the query to filter for VPC changes: `event.serviceName: Vpc and (event.eventName: CreateVpc or event.eventName: DeleteVpc or event.eventName: ModifyVpcAttribute)`.\n6. Set the alert **schedule**, **notification method**, and **severity**.\n7. Save and enable the alert rule.",
"Terraform": ""
},
"Recommendation": {
diff --git a/prowler/providers/alibabacloud/services/sls/sls_vpc_network_route_changes_alert_enabled/sls_vpc_network_route_changes_alert_enabled.metadata.json b/prowler/providers/alibabacloud/services/sls/sls_vpc_network_route_changes_alert_enabled/sls_vpc_network_route_changes_alert_enabled.metadata.json
index fccd21af53..626ab7becf 100644
--- a/prowler/providers/alibabacloud/services/sls/sls_vpc_network_route_changes_alert_enabled/sls_vpc_network_route_changes_alert_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/sls/sls_vpc_network_route_changes_alert_enabled/sls_vpc_network_route_changes_alert_enabled.metadata.json
@@ -1,29 +1,26 @@
{
"Provider": "alibabacloud",
"CheckID": "sls_vpc_network_route_changes_alert_enabled",
- "CheckTitle": "Log monitoring and alerts are set up for VPC network route changes",
- "CheckType": [
- "Suspicious network connection",
- "Cloud threat detection"
- ],
+ "CheckTitle": "A log monitoring and alert is set up for VPC network route changes",
+ "CheckType": [],
"ServiceName": "sls",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:log:region:account-id:project/project-name/alert/alert-name",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudSLSAlert",
+ "ResourceType": "ALIYUN::SLS::Alert",
"ResourceGroup": "monitoring",
- "Description": "It is recommended that a **metric filter and alarm** be established for **VPC network route** changes.",
- "Risk": "Monitoring changes to **route tables** will help ensure that all VPC traffic flows through an expected path.\n\nUnauthorized route changes could redirect traffic through malicious intermediaries.",
+ "Description": "**Alibaba Cloud Simple Log Service (SLS)** should have a metric filter and alarm configured for **VPC network route** changes. By directing **ActionTrail** logs to SLS and establishing corresponding alert rules, real-time monitoring of route table modifications can be achieved. This ensures that any creation, deletion, or modification of route entries is promptly detected, helping verify that all VPC traffic flows through the expected network paths.",
+ "Risk": "Without monitoring for **route table changes**, unauthorized modifications to network routes may go undetected. This could allow attackers to redirect traffic through **malicious intermediaries** for interception or manipulation. Undetected route changes increase the risk of **man-in-the-middle attacks**, **data exfiltration**, and **service disruption**, as traffic may be silently diverted away from intended destinations, compromising the **confidentiality** and **integrity** of data in transit.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/en/doc-detail/91784.htm",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-SLS/vpc-network-route-changes-alert.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-SLS/vpc-network-route-changes-alert.html"
],
"Remediation": {
"Code": {
- "CLI": "",
+ "CLI": "aliyun sls create-alert --project --alert-name vpc-route-changes --alert-displayname 'VPC Network Route Changes Alert' --condition 'event.serviceName: Vpc and (event.eventName: CreateRouteEntry or event.eventName: DeleteRouteEntry or event.eventName: ModifyRouteEntry)' --dashboard --schedule '{\"type\":\"FixedRate\",\"interval\":\"1m\"}'",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **SLS Console**.\n2. Ensure **ActionTrail** is enabled and delivering logs to a **Log Service** project.\n3. Navigate to the project receiving ActionTrail logs.\n4. Select **Alerts** and click **Create Alert Rule**.\n5. Configure the query to filter for VPC network route changes: `event.serviceName: Vpc and (event.eventName: CreateRouteEntry or event.eventName: DeleteRouteEntry or event.eventName: ModifyRouteEntry)`.\n6. Set the alert **schedule**, **notification method**, and **severity**.\n7. Save and enable the alert rule.",
"Terraform": ""
},
"Recommendation": {
diff --git a/prowler/providers/alibabacloud/services/vpc/vpc_flow_logs_enabled/vpc_flow_logs_enabled.metadata.json b/prowler/providers/alibabacloud/services/vpc/vpc_flow_logs_enabled/vpc_flow_logs_enabled.metadata.json
index c150689c06..414bc8b80a 100644
--- a/prowler/providers/alibabacloud/services/vpc/vpc_flow_logs_enabled/vpc_flow_logs_enabled.metadata.json
+++ b/prowler/providers/alibabacloud/services/vpc/vpc_flow_logs_enabled/vpc_flow_logs_enabled.metadata.json
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "vpc_flow_logs_enabled",
- "CheckTitle": "VPC flow logging is enabled in all VPCs",
- "CheckType": [
- "Suspicious network connection",
- "Cloud threat detection"
- ],
+ "CheckTitle": "VPC flow logging is enabled for all VPCs",
+ "CheckType": [],
"ServiceName": "vpc",
"SubServiceName": "",
- "ResourceIdTemplate": "acs:vpc:region:account-id:vpc/{vpc-id}",
+ "ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "AlibabaCloudVPC",
+ "ResourceType": "ALIYUN::VPC::FlowLog",
"ResourceGroup": "network",
- "Description": "You can use the **flow log function** to monitor the IP traffic information for an ENI, a VSwitch, or a VPC.\n\nIf you create a flow log for a VSwitch or a VPC, all the **Elastic Network Interfaces**, including the newly created ones, are monitored. Such flow log data is stored in **Log Service**, where you can view and analyze IP traffic information. It is recommended that VPC Flow Logs be enabled for packet \"Rejects\" for VPCs.",
- "Risk": "**VPC Flow Logs** provide visibility into network traffic that traverses the VPC and can be used to detect **anomalous traffic** or provide insight during security workflows.\n\nWithout flow logs, it is difficult to investigate network-based security incidents.",
+ "Description": "**Alibaba Cloud VPC Flow Logs** capture IP traffic information for Elastic Network Interfaces, VSwitches, or entire VPCs. When a flow log is created for a VPC, all ENIs within it, including newly created ones, are automatically monitored. Flow log data is stored in **Log Service** where it can be viewed and analyzed for security and operational purposes.",
+ "Risk": "Without **VPC Flow Logs** enabled, there is no visibility into network traffic traversing the VPC. This prevents detection of **anomalous traffic patterns**, **unauthorized network connections**, and **data exfiltration attempts**, and severely limits the ability to investigate network-based security incidents.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/90628.html",
- "https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-VPC/enable-flow-logs.html"
+ "https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-VPC/enable-flow-logs.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun vpc CreateFlowLog --ResourceId --ResourceType VPC --FlowLogName --LogStoreName --ProjectName ",
"NativeIaC": "",
- "Other": "",
+ "Other": "1. Log on to the **VPC Console**\n2. In the left-side navigation pane, click **FlowLog**\n3. Click **Create Flow Log**\n4. Select the target VPC as the resource\n5. Configure the Log Service project and logstore for storing flow log data\n6. Click **OK** to enable flow logging",
"Terraform": "resource \"alicloud_vpc_flow_log\" \"example\" {\n flow_log_name = \"example-flow-log\"\n resource_type = \"VPC\"\n resource_id = alicloud_vpc.example.id\n traffic_type = \"All\"\n project_name = alicloud_log_project.example.project_name\n log_store_name = alicloud_log_store.example.logstore_name\n}"
},
"Recommendation": {
- "Text": "1. Log on to the **VPC Console**\n2. In the left-side navigation pane, click **FlowLog**\n3. Follow the instructions to create FlowLog for each of your VPCs",
+ "Text": "Enable VPC Flow Logs for all VPCs to capture IP traffic information and store it in Log Service for security analysis, anomaly detection, and incident response.",
"Url": "https://hub.prowler.com/check/vpc_flow_logs_enabled"
}
},
From 23a8d4e680f2ff0cdcfb55a72012dcd24d61626f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pedro=20Mart=C3=ADn?=
Date: Mon, 9 Mar 2026 16:54:50 +0100
Subject: [PATCH 004/108] feat(ui): improve organizations onboarding (#10274)
---
ui/CHANGELOG.md | 5 +-
.../organizations/org-setup-form.tsx | 149 ++++++++++++------
ui/lib/external-urls.ts | 28 +++-
3 files changed, 132 insertions(+), 50 deletions(-)
diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md
index 011ed8ec9a..6f1482426f 100644
--- a/ui/CHANGELOG.md
+++ b/ui/CHANGELOG.md
@@ -4,10 +4,13 @@ All notable changes to the **Prowler UI** are documented in this file.
## [1.20.0] (Prowler v5.20.0 UNRELEASED)
-### 🐞 Changed
+### 🔄 Changed
- Attack Paths: Improved error handling for server errors (5xx) and network failures with user-friendly messages instead of raw internal errors and layout changes. [(#10249)](https://github.com/prowler-cloud/prowler/pull/10249)
- Refactor simple providers with new components and styles.[(#10259)](https://github.com/prowler-cloud/prowler/pull/10259)
+- AWS Organizations onboarding now uses a clearer 3-step flow: deploy the ProwlerScan role in the management account via CloudFormation Stack, deploy to member accounts via StackSet with a copyable template URL, and confirm with the Role ARN [(#10274)](https://github.com/prowler-cloud/prowler/pull/10274)
+
+---
## [1.19.1] (Prowler v5.19.1 UNRELEASED)
diff --git a/ui/components/providers/organizations/org-setup-form.tsx b/ui/components/providers/organizations/org-setup-form.tsx
index 0a62efedb7..3990ffb435 100644
--- a/ui/components/providers/organizations/org-setup-form.tsx
+++ b/ui/components/providers/organizations/org-setup-form.tsx
@@ -1,5 +1,6 @@
"use client";
+import { useClipboard } from "@heroui/use-clipboard";
import { zodResolver } from "@hookform/resolvers/zod";
import { Check, Copy, ExternalLink } from "lucide-react";
import { useSession } from "next-auth/react";
@@ -18,7 +19,11 @@ import { Button } from "@/components/shadcn/button/button";
import { Checkbox } from "@/components/shadcn/checkbox/checkbox";
import { TreeSpinner } from "@/components/shadcn/tree-view/tree-spinner";
import { Form } from "@/components/ui/form";
-import { getAWSCredentialsTemplateLinks } from "@/lib";
+import {
+ getAWSCredentialsTemplateLinks,
+ PROWLER_CF_TEMPLATE_URL,
+ STACKSET_CONSOLE_URL,
+} from "@/lib";
import { ORG_SETUP_PHASE, OrgSetupPhase } from "@/types/organizations";
import { useOrgSetupSubmission } from "./hooks/use-org-setup-submission";
@@ -39,7 +44,7 @@ const orgSetupSchema = z.object({
.min(1, "Role ARN is required")
.regex(
/^arn:aws:iam::\d{12}:role\//,
- "Must be a valid IAM Role ARN (e.g., arn:aws:iam::123456789012:role/ProwlerOrgRole)",
+ "Must be a valid IAM Role ARN (e.g., arn:aws:iam::123456789012:role/ProwlerScan)",
),
stackSetDeployed: z.boolean().refine((value) => value, {
message: "You must confirm the StackSet deployment before continuing.",
@@ -64,8 +69,13 @@ export function OrgSetupForm({
initialPhase = ORG_SETUP_PHASE.DETAILS,
}: OrgSetupFormProps) {
const { data: session } = useSession();
- const [isExternalIdCopied, setIsExternalIdCopied] = useState(false);
const stackSetExternalId = session?.tenantId ?? "";
+ const { copied: isExternalIdCopied, copy: copyExternalId } = useClipboard({
+ timeout: 1500,
+ });
+ const { copied: isTemplateUrlCopied, copy: copyTemplateUrl } = useClipboard({
+ timeout: 1500,
+ });
const [setupPhase, setSetupPhase] = useState(initialPhase);
const formId = "org-wizard-setup-form";
@@ -90,9 +100,10 @@ export function OrgSetupForm({
const awsOrgId = watch("awsOrgId") || "";
const isOrgIdValid = /^o-[a-z0-9]{10,32}$/.test(awsOrgId.trim());
- const stackSetQuickLink =
- stackSetExternalId &&
- getAWSCredentialsTemplateLinks(stackSetExternalId).cloudformationQuickLink;
+ const templateLinks = stackSetExternalId
+ ? getAWSCredentialsTemplateLinks(stackSetExternalId)
+ : null;
+ const orgQuickLink = templateLinks?.cloudformationOrgQuickLink;
const { apiError, setApiError, submitOrganizationSetup } =
useOrgSetupSubmission({
@@ -260,35 +271,11 @@ export function OrgSetupForm({
{setupPhase === ORG_SETUP_PHASE.ACCESS && !isSubmitting && (
+ {/* External ID - shown first for both deployment steps */}
- 1) Launch the Prowler CloudFormation StackSet in your AWS
- Console.
-
-
-
-
-
-
- 2) Use the following Prowler External ID parameter in the
- StackSet.
+ Use the following External ID when deploying
+ the CloudFormation Stack and StackSet.
@@ -302,15 +289,7 @@ export function OrgSetupForm({
- 3) Copy the Prowler IAM Role ARN from AWS and confirm the
- StackSet is successfully deployed by clicking the checkbox
- below.
+ 1) Deploy the ProwlerScan role in your{" "}
+ management account using a CloudFormation
+ Stack.
+
+ 2) Deploy the ProwlerScan role to{" "}
+ member accounts using a CloudFormation
+ StackSet.
+
+
+ Open the StackSets console, select{" "}
+ Service-managed permissions, and paste the
+ template URL below. Set the ExternalId{" "}
+ parameter to the value shown above.
+
+
+
+ {PROWLER_CF_TEMPLATE_URL}
+
+
+
+
+
+
+ {/* Step 3: Role ARN + confirm */}
+
+
+ 3) Paste the management account Role ARN and confirm both
+ deployments are complete.
@@ -365,7 +417,8 @@ export function OrgSetupForm({
htmlFor="stackSetDeployed"
className="text-text-neutral-tertiary text-xs leading-5 font-normal"
>
- The StackSet has been successfully deployed in AWS
+ The Stack and StackSet have been successfully deployed in
+ AWS
*
>
diff --git a/ui/lib/external-urls.ts b/ui/lib/external-urls.ts
index 1fe10ef603..654bf27b43 100644
--- a/ui/lib/external-urls.ts
+++ b/ui/lib/external-urls.ts
@@ -8,6 +8,18 @@ export const DOCS_URLS = {
"https://docs.prowler.com/user-guide/tutorials/prowler-cloud-aws-organizations",
} as const;
+// CloudFormation template URL for the ProwlerScan role.
+// Also used (URL-encoded) as the templateURL param in cloudformationQuickLink
+// and cloudformationOrgQuickLink below — keep both in sync.
+export const PROWLER_CF_TEMPLATE_URL =
+ "https://prowler-cloud-public.s3.eu-west-1.amazonaws.com/permissions/templates/aws/cloudformation/prowler-scan-role.yml";
+
+// AWS Console URL for creating a new StackSet.
+// Hardcoded to us-east-1 — StackSets are typically managed from this region.
+// Users in AWS GovCloud or China partitions would need different URLs.
+export const STACKSET_CONSOLE_URL =
+ "https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacksets/create";
+
export const getProviderHelpText = (provider: string) => {
switch (provider) {
case "aws":
@@ -86,6 +98,7 @@ export const getAWSCredentialsTemplateLinks = (
cloudformation: string;
terraform: string;
cloudformationQuickLink: string;
+ cloudformationOrgQuickLink: string;
} => {
let links = {};
@@ -107,11 +120,24 @@ export const getAWSCredentialsTemplateLinks = (
};
}
+ const encodedTemplateUrl = encodeURIComponent(PROWLER_CF_TEMPLATE_URL);
+ const cfBaseUrl =
+ "https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/quickcreate";
+ const s3Params = bucketName
+ ? `¶m_EnableS3Integration=true¶m_S3IntegrationBucketName=${bucketName}`
+ : "";
+
return {
...(links as {
cloudformation: string;
terraform: string;
}),
- cloudformationQuickLink: `https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/quickcreate?templateURL=https%3A%2F%2Fprowler-cloud-public.s3.eu-west-1.amazonaws.com%2Fpermissions%2Ftemplates%2Faws%2Fcloudformation%2Fprowler-scan-role.yml&stackName=Prowler¶m_ExternalId=${externalId}${bucketName ? `¶m_EnableS3Integration=true¶m_S3IntegrationBucketName=${bucketName}` : ""}`,
+ cloudformationQuickLink:
+ `${cfBaseUrl}?templateURL=${encodedTemplateUrl}` +
+ `&stackName=Prowler¶m_ExternalId=${externalId}${s3Params}`,
+ cloudformationOrgQuickLink:
+ `${cfBaseUrl}?templateURL=${encodedTemplateUrl}` +
+ `&stackName=Prowler¶m_ExternalId=${externalId}` +
+ `¶m_EnableOrganizations=true${s3Params}`,
};
};
From 9b64efeec2b24ac5192ba7706e1ac40387e6eb5d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rub=C3=A9n=20De=20la=20Torre=20Vico?=
Date: Mon, 9 Mar 2026 19:48:23 +0100
Subject: [PATCH 005/108] chore(m365): enhance metadata for `admincenter`
service (#9680)
Co-authored-by: Daniel Barranquero
---
prowler/CHANGELOG.md | 1 +
...al_calendar_sharing_disabled.metadata.json | 25 ++++++++++++-------
...groups_not_public_visibility.metadata.json | 25 +++++++++++--------
...ion_customer_lockbox_enabled.metadata.json | 25 ++++++++++++-------
...ttings_password_never_expire.metadata.json | 22 +++++++++-------
...ns_reduced_license_footprint.metadata.json | 25 +++++++++++--------
...n_two_and_four_global_admins.metadata.json | 24 +++++++++++-------
7 files changed, 91 insertions(+), 56 deletions(-)
diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md
index fff5d21773..d31287a230 100644
--- a/prowler/CHANGELOG.md
+++ b/prowler/CHANGELOG.md
@@ -31,6 +31,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- Update Oracle Cloud Events service metadata to new format [(#9373)](https://github.com/prowler-cloud/prowler/pull/9373)
- Update Oracle Cloud Identity service metadata to new format [(#9375)](https://github.com/prowler-cloud/prowler/pull/9375)
- Update Alibaba Cloud services metadata to new format [(#10289)](https://github.com/prowler-cloud/prowler/pull/10289)
+- Update M365 Admin Center service metadata to new format [(#9680)](https://github.com/prowler-cloud/prowler/pull/9680)
---
diff --git a/prowler/providers/m365/services/admincenter/admincenter_external_calendar_sharing_disabled/admincenter_external_calendar_sharing_disabled.metadata.json b/prowler/providers/m365/services/admincenter/admincenter_external_calendar_sharing_disabled/admincenter_external_calendar_sharing_disabled.metadata.json
index 1756b1acd2..c5ff3c2125 100644
--- a/prowler/providers/m365/services/admincenter/admincenter_external_calendar_sharing_disabled/admincenter_external_calendar_sharing_disabled.metadata.json
+++ b/prowler/providers/m365/services/admincenter/admincenter_external_calendar_sharing_disabled/admincenter_external_calendar_sharing_disabled.metadata.json
@@ -1,30 +1,37 @@
{
"Provider": "m365",
"CheckID": "admincenter_external_calendar_sharing_disabled",
- "CheckTitle": "Ensure external sharing of calendars is disabled",
+ "CheckTitle": "External calendar sharing is disabled at the organization level",
"CheckType": [],
"ServiceName": "admincenter",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "Sharing Policy",
+ "ResourceType": "NotDefined",
"ResourceGroup": "governance",
- "Description": "Restrict the ability for users to share their calendars externally in Microsoft 365. This prevents users from sending calendar sharing links to external recipients, reducing information exposure.",
- "Risk": "Allowing calendar sharing outside the organization can help attackers build knowledge of personnel availability, relationships, and activity patterns, aiding social engineering or targeted attacks.",
- "RelatedUrl": "https://learn.microsoft.com/en-us/microsoft-365/admin/manage/share-calendars-with-external-users?view=o365-worldwide",
+ "Description": "**Microsoft 365 calendar sharing** is evaluated at the organization level to determine if sharing with external recipients is disabled, including blocking anonymous access and links sent outside the tenant.",
+ "Risk": "Allowing **external calendar sharing** exposes meeting metadata (subjects, locations, attendees, patterns), weakening confidentiality. Adversaries can profile staff, craft convincing spear-phish or fake invites, time fraud attempts, and stage meeting hijacks, increasing **BEC** and social engineering success.",
+ "RelatedUrl": "",
+ "AdditionalURLs": [
+ "https://learn.microsoft.com/en-us/microsoft-365/admin/manage/share-calendars-with-external-users?view=o365-worldwide",
+ "https://m365scripts.com/microsoft365/how-to-stop-users-from-sharing-their-own-calendars/",
+ "https://learn.microsoft.com/en-my/answers/questions/1165808/external-calendar-sharing-for-single-user-in-exo"
+ ],
"Remediation": {
"Code": {
- "CLI": "Set-SharingPolicy -Identity \"Default Sharing Policy\" -Enabled $False",
+ "CLI": "Set-SharingPolicy -Identity \"Default Sharing Policy\" -Enabled $false",
"NativeIaC": "",
- "Other": "1. Navigate to https://admin.microsoft.com. 2. Click Settings > Org settings. 3. Select Calendar in the Services section. 4. Uncheck 'Let your users share their calendars with people outside of your organization who have Office 365 or Exchange'. 5. Click Save.",
+ "Other": "1. Go to https://admin.microsoft.com and sign in\n2. Navigate to Settings > Org settings > Services > Calendar\n3. Under External sharing, uncheck \"Let your users share their calendars with people outside of your organization who have Microsoft 365 or Exchange\"\n4. Click Save",
"Terraform": ""
},
"Recommendation": {
- "Text": "Disable external calendar sharing by setting the Default Sharing Policy to disabled.",
- "Url": "https://learn.microsoft.com/en-us/microsoft-365/admin/manage/share-calendars-with-external-users?view=o365-worldwide"
+ "Text": "Apply **least privilege**: disable **external calendar sharing** tenant-wide. If business-needed, allow only approved domains, require authenticated recipients (no anonymous), and limit details to `Free/Busy (time only)`. Review sharing policies regularly under **zero trust** and **defense in depth**.",
+ "Url": "https://hub.prowler.com/check/admincenter_external_calendar_sharing_disabled"
}
},
"Categories": [
+ "internet-exposed",
+ "trust-boundaries",
"e5"
],
"DependsOn": [],
diff --git a/prowler/providers/m365/services/admincenter/admincenter_groups_not_public_visibility/admincenter_groups_not_public_visibility.metadata.json b/prowler/providers/m365/services/admincenter/admincenter_groups_not_public_visibility/admincenter_groups_not_public_visibility.metadata.json
index c46e21a037..821ddb4273 100644
--- a/prowler/providers/m365/services/admincenter/admincenter_groups_not_public_visibility/admincenter_groups_not_public_visibility.metadata.json
+++ b/prowler/providers/m365/services/admincenter/admincenter_groups_not_public_visibility/admincenter_groups_not_public_visibility.metadata.json
@@ -1,30 +1,35 @@
{
"Provider": "m365",
"CheckID": "admincenter_groups_not_public_visibility",
- "CheckTitle": "Ensure that only organizationally managed/approved public groups exist",
+ "CheckTitle": "Microsoft 365 group has Private visibility",
"CheckType": [],
"ServiceName": "admincenter",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
- "ResourceType": "Active teams & groups",
+ "ResourceType": "NotDefined",
"ResourceGroup": "collaboration",
- "Description": "Ensure that only organizationally managed and approved public groups exist to prevent unauthorized access to sensitive group resources like SharePoint, Teams, or other shared assets.",
- "Risk": "Unmanaged public groups can allow unauthorized access to organizational resources, posing a risk of data leakage or misuse through easily guessable SharePoint URLs or self-adding to groups via the Azure portal.",
- "RelatedUrl": "https://learn.microsoft.com/en-us/microsoft-365/admin/create-groups/manage-groups?view=o365-worldwide",
+ "Description": "**Microsoft 365 groups** are assessed for their visibility setting.\n\nThe finding highlights groups configured as `Public`; groups set to `Private` align with the intended privacy posture.",
+ "Risk": "**Public visibility** lets any authenticated tenant user discover and self-join, accessing group files, conversations, SharePoint sites, and calendars.\n\nThis weakens **confidentiality** and can enable unauthorized changes that affect **integrity**.",
+ "RelatedUrl": "",
+ "AdditionalURLs": [
+ "https://learn.microsoft.com/en-us/microsoft-365/admin/create-groups/manage-groups?view=o365-worldwide",
+ "https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/microsoft-365-groups-governance"
+ ],
"Remediation": {
"Code": {
- "CLI": "",
+ "CLI": "Set-UnifiedGroup -Identity -AccessType Private",
"NativeIaC": "",
- "Other": "1. Navigate to Microsoft 365 admin center https://admin.microsoft.com. 2. Click to expand Teams & groups select Active teams & groups. 3. On the Active teams and groups page, select the group's name that is public. 4. On the popup groups name page, select Settings. 5. Under Privacy, select Private.",
- "Terraform": ""
+ "Other": "1. Sign in to https://admin.microsoft.com\n2. Go to Teams & groups > Active teams & groups\n3. Select the group with visibility Public\n4. Open Settings\n5. Under Privacy, select Private\n6. Click Save",
+ "Terraform": "```hcl\nresource \"azuread_group\" \"\" {\n display_name = \"\"\n mail_enabled = true\n security_enabled = false\n types = [\"Unified\"]\n visibility = \"Private\" # Critical: sets the group visibility to Private to pass the check\n}\n```"
},
"Recommendation": {
- "Text": "Review and adjust the privacy settings of Microsoft 365 Groups to ensure only organizationally managed and approved public groups exist.",
- "Url": "https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/microsoft-365-groups-governance"
+ "Text": "Set groups to `Private` by default and allow `Public` only for clearly non-sensitive communities.\n\nApply **least privilege**: restrict who can create or change visibility, require owner approval for membership, review access regularly, and use **sensitivity labels** to reinforce classification.",
+ "Url": "https://hub.prowler.com/check/admincenter_groups_not_public_visibility"
}
},
"Categories": [
+ "identity-access",
"e3"
],
"DependsOn": [],
diff --git a/prowler/providers/m365/services/admincenter/admincenter_organization_customer_lockbox_enabled/admincenter_organization_customer_lockbox_enabled.metadata.json b/prowler/providers/m365/services/admincenter/admincenter_organization_customer_lockbox_enabled/admincenter_organization_customer_lockbox_enabled.metadata.json
index 78a9102daf..04370ddc66 100644
--- a/prowler/providers/m365/services/admincenter/admincenter_organization_customer_lockbox_enabled/admincenter_organization_customer_lockbox_enabled.metadata.json
+++ b/prowler/providers/m365/services/admincenter/admincenter_organization_customer_lockbox_enabled/admincenter_organization_customer_lockbox_enabled.metadata.json
@@ -1,30 +1,37 @@
{
"Provider": "m365",
"CheckID": "admincenter_organization_customer_lockbox_enabled",
- "CheckTitle": "Ensure that customer lockbox is enabled for the organization",
+ "CheckTitle": "Customer Lockbox is enabled at the organization level",
"CheckType": [],
"ServiceName": "admincenter",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
- "ResourceType": "Exchange Organization Configuration",
+ "ResourceType": "NotDefined",
"ResourceGroup": "collaboration",
- "Description": "Customer Lockbox ensures that Microsoft support engineers cannot access content in your tenant to perform a service operation without explicit approval. This feature provides an additional layer of control and transparency over data access requests.",
- "Risk": "If Customer Lockbox is not enabled, Microsoft support personnel can access your organization's data for troubleshooting without explicit approval, potentially increasing the risk of unauthorized access or data exfiltration.",
- "RelatedUrl": "https://learn.microsoft.com/en-us/azure/security/fundamentals/customer-lockbox-overview",
+ "Description": "**Microsoft 365 organization** setting indicates whether **Customer Lockbox** is enabled to require explicit approval for Microsoft support access to customer content",
+ "Risk": "Without **Customer Lockbox**, Microsoft engineers may access tenant content during support without your approval, undermining **confidentiality** and **accountability**. This increases risks of targeted data exposure, insider misuse, and weakens auditability of access during troubleshooting.",
+ "RelatedUrl": "",
+ "AdditionalURLs": [
+ "https://www.gitbit.org/course/ms-500/learn/locking-down-your-microsoft-365-tenant-from-microsoft-engineers-fldnualgc",
+ "https://learn.microsoft.com/en-us/azure/security/fundamentals/customer-lockbox-overview",
+ "https://learn.microsoft.com/en-us/microsoft-365/compliance/customer-lockbox-requests?redirectSourcePath=%2flv-lv%2farticle%2fOffice-365-klientu-lockbox-piepras%25C4%25ABjumu-36f9cdd1-e64c-421b-a7e4-4a54d16440a2&view=o365-worldwide",
+ "https://learnthecontent.com/exam/microsoft-365/ms-700-managing-microsoft-teams/s/enable-customer-lockbox-for-data-security"
+ ],
"Remediation": {
"Code": {
- "CLI": "Set-OrganizationConfig -CustomerLockBoxEnabled $true",
+ "CLI": "Set-OrganizationConfig -CustomerLockboxEnabled $true",
"NativeIaC": "",
- "Other": "1. Navigate to Microsoft 365 admin center https://admin.microsoft.com. 2. Click Settings > Org settings. 3. Select the Security & privacy tab. 4. Click Customer lockbox. 5. Check the box 'Require approval for all data access requests'. 6. Click Save.",
+ "Other": "1. Go to https://admin.microsoft.com and sign in\n2. Navigate to Settings > Org settings\n3. Select Security & privacy\n4. Click Customer Lockbox\n5. Check \"Require approval for all data access requests\"\n6. Click Save",
"Terraform": ""
},
"Recommendation": {
- "Text": "Enable the Customer Lockbox feature to ensure explicit approval is required before Microsoft engineers can access your data during support operations.",
- "Url": "https://learn.microsoft.com/en-us/azure/security/fundamentals/customer-lockbox-overview"
+ "Text": "Enable **Customer Lockbox** to enforce tenant approval for data access (`Require approval for all data access requests`).\n- Limit approvers per **least privilege**\n- Establish an on-call review workflow\n- Audit requests and engineer actions\n- Layer with **defense in depth** (MFA, RBAC, logging) to strengthen control",
+ "Url": "https://hub.prowler.com/check/admincenter_organization_customer_lockbox_enabled"
}
},
"Categories": [
+ "identity-access",
"e5"
],
"DependsOn": [],
diff --git a/prowler/providers/m365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire.metadata.json b/prowler/providers/m365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire.metadata.json
index a844c80b00..e79fdee041 100644
--- a/prowler/providers/m365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire.metadata.json
+++ b/prowler/providers/m365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire.metadata.json
@@ -1,30 +1,34 @@
{
"Provider": "m365",
"CheckID": "admincenter_settings_password_never_expire",
- "CheckTitle": "Ensure the 'Password expiration policy' is set to 'Set passwords to never expire (recommended)'",
+ "CheckTitle": "Tenant password policy is set to never expire",
"CheckType": [],
"ServiceName": "admincenter",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "Security & privacy settings",
+ "ResourceType": "NotDefined",
"ResourceGroup": "security",
- "Description": "This control ensures that the password expiration policy is set to 'Set passwords to never expire (recommended)'. This aligns with modern recommendations to enhance security by avoiding arbitrary password changes and focusing on supplementary controls like MFA.",
- "Risk": "Arbitrary password expiration policies can lead to weaker passwords due to frequent changes. Users may adopt insecure habits such as using simple, memorable passwords.",
- "RelatedUrl": "https://www.cisecurity.org/insights/white-papers/cis-password-policy-guide",
+ "Description": "**Microsoft 365 tenant password policy** is configured so user passwords **do not expire** (`never expire`), meaning no time-based rotation is enforced across accounts.",
+ "Risk": "Forced password expiration degrades security: users adopt predictable patterns and reuse credentials, reducing **confidentiality** and **integrity**. It boosts success of **password spraying** and **credential stuffing**, encourages insecure storage and helpdesk resets, and can disrupt service accounts, impacting **availability**.",
+ "RelatedUrl": "",
+ "AdditionalURLs": [
+ "https://learn.microsoft.com/en-us/microsoft-365/admin/misc/password-policy-recommendations?view=o365-worldwide"
+ ],
"Remediation": {
"Code": {
- "CLI": "Set-MsolUser -UserPrincipalName -PasswordNeverExpires $true",
+ "CLI": "Set-MsolPasswordPolicy -DomainName -ValidityPeriod 2147483647",
"NativeIaC": "",
- "Other": "1. Navigate to Microsoft 365 admin center https://admin.microsoft.com. 2. Click to expand Settings select Org Settings. 3. Click on Security & privacy. 4. Check the Set passwords to never expire (recommended) box. 5. Click Save.",
+ "Other": "1. Sign in to the Microsoft 365 admin center: https://admin.microsoft.com\n2. Go to Settings > Org settings > Security & privacy\n3. Open Password expiration policy and check Set passwords to never expire\n4. Click Save",
"Terraform": ""
},
"Recommendation": {
- "Text": "Enable the 'Never Expire Passwords' option in Microsoft 365 Admin Center.",
- "Url": "https://learn.microsoft.com/en-us/microsoft-365/admin/misc/password-policy-recommendations?view=o365-worldwide"
+ "Text": "Set passwords to `never expire` and enforce layered controls: **MFA** with risk-based challenges, banned-password checks, and strong length. Apply **least privilege** and monitor sign-ins. Rotate credentials only after compromise or risk, and favor managed non-human identities for automation instead of time-based password cycling.",
+ "Url": "https://hub.prowler.com/check/admincenter_settings_password_never_expire"
}
},
"Categories": [
+ "identity-access",
"e3"
],
"DependsOn": [],
diff --git a/prowler/providers/m365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint.metadata.json b/prowler/providers/m365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint.metadata.json
index 44654f0fb2..7d1e782451 100644
--- a/prowler/providers/m365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint.metadata.json
+++ b/prowler/providers/m365/services/admincenter/admincenter_users_admins_reduced_license_footprint/admincenter_users_admins_reduced_license_footprint.metadata.json
@@ -1,30 +1,35 @@
{
"Provider": "m365",
"CheckID": "admincenter_users_admins_reduced_license_footprint",
- "CheckTitle": "Ensure administrative accounts use licenses with a reduced application footprint",
+ "CheckTitle": "Administrative user has no license or an allowed license (AAD_PREMIUM or AAD_PREMIUM_P2)",
"CheckType": [],
"ServiceName": "admincenter",
"SubServiceName": "",
"ResourceIdTemplate": "",
- "Severity": "medium",
- "ResourceType": "Active users",
+ "Severity": "high",
+ "ResourceType": "NotDefined",
"ResourceGroup": "IAM",
- "Description": "Administrative accounts must use licenses with a reduced application footprint, such as Microsoft Entra ID P1 or P2, or avoid licenses entirely when possible. This minimizes the attack surface associated with privileged identities.",
- "Risk": "Licensing administrative accounts with applications like email or collaborative tools increases their exposure to social engineering attacks and malicious content, putting privileged accounts at risk.",
- "RelatedUrl": "https://learn.microsoft.com/en-us/microsoft-365/enterprise/protect-your-global-administrator-accounts?view=o365-worldwide",
+ "Description": "Privileged users in Microsoft 365 are evaluated for a **reduced license footprint**: only `AAD_PREMIUM` (P1) or `AAD_PREMIUM_P2` (P2), or no license. Assignments that include productivity app suites indicate excess application entitlements on administrative accounts.",
+ "Risk": "Productivity licenses on **privileged identities** create mailboxes and collaboration surfaces that widen attack paths. Phishing, malicious content, or rogue OAuth apps can steal credentials/tokens, enabling tenant-wide role misuse, data exfiltration, and configuration tampering-compromising **confidentiality** and **integrity**.",
+ "RelatedUrl": "",
+ "AdditionalURLs": [
+ "https://learn.microsoft.com/en-us/microsoft-365/enterprise/protect-your-global-administrator-accounts?view=o365-worldwide",
+ "https://learn.microsoft.com/en-us/microsoft-365/admin/add-users/add-users?view=o365-worldwide"
+ ],
"Remediation": {
"Code": {
- "CLI": "",
+ "CLI": "Set-MgUserLicense -UserId -AddLicenses @() -RemoveLicenses @(Get-MgUserLicenseDetail -UserId | Select-Object -ExpandProperty SkuId)",
"NativeIaC": "",
- "Other": "1. Navigate to Microsoft 365 admin center https://admin.microsoft.com. 2. Click to expand Users select Active users. 3. Click Add a user. 4. Fill out the appropriate fields for Name, user, etc. 5. When prompted to assign licenses select as needed Microsoft Entra ID P1 or Microsoft Entra ID P2, then click Next. 6. Under the Option settings screen you may choose from several types of privileged roles. Choose Admin center access followed by the appropriate role then click Next. 7. Select Finish adding.",
+ "Other": "1. Sign in to https://admin.microsoft.com\n2. Go to Users > Active users\n3. Select the affected administrative user\n4. Open Licenses and apps\n5. To pass the check, do ONE of the following:\n - Unassign all licenses (make the user unlicensed); or\n - Assign only Microsoft Entra ID P1 or P2 and remove all other licenses\n6. Click Save changes",
"Terraform": ""
},
"Recommendation": {
- "Text": "Assign Microsoft Entra ID P1 or P2 licenses to administrative accounts to participate in essential security services without enabling access to vulnerable applications.",
- "Url": "https://learn.microsoft.com/en-us/microsoft-365/admin/add-users/add-users?view=o365-worldwide"
+ "Text": "Maintain **dedicated admin accounts** with a **least-privilege, reduced-license** model: assign only Microsoft Entra P1/P2 features needed for identity security, or keep them unlicensed for apps. Separate admin from daily-use accounts, enforce **MFA** and just-in-time elevation, and use **privileged access workstations** for administration.",
+ "Url": "https://hub.prowler.com/check/admincenter_users_admins_reduced_license_footprint"
}
},
"Categories": [
+ "identity-access",
"e3"
],
"DependsOn": [],
diff --git a/prowler/providers/m365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins.metadata.json b/prowler/providers/m365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins.metadata.json
index b429d3a66b..617ae2a35c 100644
--- a/prowler/providers/m365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins.metadata.json
+++ b/prowler/providers/m365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins.metadata.json
@@ -1,30 +1,36 @@
{
"Provider": "m365",
"CheckID": "admincenter_users_between_two_and_four_global_admins",
- "CheckTitle": "Ensure that between two and four global admins are designated",
+ "CheckTitle": "Tenant has between two and four Global Administrators",
"CheckType": [],
"ServiceName": "admincenter",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "Active users",
+ "ResourceType": "NotDefined",
"ResourceGroup": "IAM",
- "Description": "Ensure that there are between two and four global administrators designated in your tenant. This ensures monitoring, redundancy, and reduces the risk associated with having too many privileged accounts.",
- "Risk": "Having only one global administrator increases the risk of unmonitored actions and operational disruptions if that administrator is unavailable. Having more than four increases the likelihood of a breach through one of these highly privileged accounts.",
- "RelatedUrl": "https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/best-practices#5-limit-the-number-of-global-administrators-to-less-than-5",
+ "Description": "Count of users assigned the **Global Administrator** role in the **Microsoft 365 tenant** is between `2` and `4` (inclusive).",
+ "Risk": "Having 1 **Global Administrator** jeopardizes availability if that account is unavailable or locked out. Having 5 expands the attack surface; one compromised admin can enable tenant-wide changes, data exfiltration, and privilege escalation, impacting confidentiality and integrity.",
+ "RelatedUrl": "",
+ "AdditionalURLs": [
+ "https://admin.microsoft.com",
+ "https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/best-practices#5-limit-the-number-of-global-administrators-to-less-than-5",
+ "https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/manage-roles-portal"
+ ],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
- "Other": "1. Navigate to the Microsoft 365 admin center https://admin.microsoft.com 2. Select Users > Active Users. 3. In the Search field enter the name of the user to be made a Global Administrator. 4. To create a new Global Admin: 1. Select the user's name. 2. A window will appear to the right. 3. Select Manage roles. 4. Select Admin center access. 5. Check Global Administrator. 6. Click Save changes. 5. To remove Global Admins: 1. Select User. 2. Under Roles select Manage roles. 3. De-Select the appropriate role. 4. Click Save changes.",
- "Terraform": ""
+ "Other": "1. Go to https://entra.microsoft.com and sign in as Privileged Role Administrator or Global Administrator\n2. Navigate to Entra ID > Roles & admins > Global Administrator\n3. Select Assignments\n4. To add: click Add assignments, select user(s), click Add; repeat until total Global Administrators is between 2 and 4\n5. To remove: select extra assignment(s), click Remove, confirm\n6. Verify the Assignments count shows between 2 and 4",
+ "Terraform": "```hcl\n# Assign the Global Administrator role to a specific principal (user or group)\n# Critical: Looks up the built-in Global Administrator role\ndata \"azuread_directory_role\" \"global_admin\" {\n display_name = \"Global Administrator\"\n}\n\n# Critical: Creates the role assignment so the principal becomes a Global Administrator\n# This helps reach the recommended 2-4 Global Administrators\nresource \"azuread_directory_role_assignment\" \"\" {\n role_id = data.azuread_directory_role.global_admin.object_id # Critical: GA role ID\n principal_object_id = \"\" # Critical: target user/group object ID\n}\n```"
},
"Recommendation": {
- "Text": "Review the number of global administrators in your tenant. Add or remove global admins as necessary to ensure compliance with the recommended range of two to four.",
- "Url": "https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/manage-roles-portal"
+ "Text": "Maintain `2-4` standing **Global Administrators** (include two break-glass accounts). Apply **least privilege** using scoped roles and **just-in-time** elevation. Enforce **MFA**, run periodic **access reviews**, and implement **separation of duties** to limit standing privileged exposure.",
+ "Url": "https://hub.prowler.com/check/admincenter_users_between_two_and_four_global_admins"
}
},
"Categories": [
+ "identity-access",
"e3"
],
"DependsOn": [],
From 20efe001ff91f4f0fe88d9e33e62d91f644cc4df Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rub=C3=A9n=20De=20la=20Torre=20Vico?=
Date: Mon, 9 Mar 2026 20:13:45 +0100
Subject: [PATCH 006/108] chore(m365): enhance metadata for `defender` service
(#9681)
Co-authored-by: Daniel Barranquero
---
prowler/CHANGELOG.md | 1 +
...tiphishing_policy_configured.metadata.json | 24 ++++++++++-------
...er_policy_empty_ip_allowlist.metadata.json | 20 ++++++++------
..._filter_policy_safe_list_off.metadata.json | 21 +++++++++------
...m_outbound_policy_configured.metadata.json | 23 +++++++++-------
...d_policy_forwarding_disabled.metadata.json | 22 +++++++++-------
...y_inbound_no_allowed_domains.metadata.json | 24 ++++++++++-------
...achments_and_docs_configured.metadata.json | 4 +--
...hat_report_policy_configured.metadata.json | 22 +++++++++-------
...defender_domain_dkim_enabled.metadata.json | 24 ++++++++++-------
...n_attachments_filter_enabled.metadata.json | 24 ++++++++++-------
...e_attachments_filter_applied.metadata.json | 26 ++++++++++++-------
...ternal_users_malware_enabled.metadata.json | 25 +++++++++++-------
...e_attachments_policy_enabled.metadata.json | 4 +--
...der_safelinks_policy_enabled.metadata.json | 4 +--
...fender_zap_for_teams_enabled.metadata.json | 4 +--
16 files changed, 165 insertions(+), 107 deletions(-)
diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md
index d31287a230..969a08da62 100644
--- a/prowler/CHANGELOG.md
+++ b/prowler/CHANGELOG.md
@@ -32,6 +32,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- Update Oracle Cloud Identity service metadata to new format [(#9375)](https://github.com/prowler-cloud/prowler/pull/9375)
- Update Alibaba Cloud services metadata to new format [(#10289)](https://github.com/prowler-cloud/prowler/pull/10289)
- Update M365 Admin Center service metadata to new format [(#9680)](https://github.com/prowler-cloud/prowler/pull/9680)
+- Update M365 Defender service metadata to new format [(#9681)](https://github.com/prowler-cloud/prowler/pull/9681)
---
diff --git a/prowler/providers/m365/services/defender/defender_antiphishing_policy_configured/defender_antiphishing_policy_configured.metadata.json b/prowler/providers/m365/services/defender/defender_antiphishing_policy_configured/defender_antiphishing_policy_configured.metadata.json
index e312a637e9..1572e0010b 100644
--- a/prowler/providers/m365/services/defender/defender_antiphishing_policy_configured/defender_antiphishing_policy_configured.metadata.json
+++ b/prowler/providers/m365/services/defender/defender_antiphishing_policy_configured/defender_antiphishing_policy_configured.metadata.json
@@ -1,30 +1,36 @@
{
"Provider": "m365",
"CheckID": "defender_antiphishing_policy_configured",
- "CheckTitle": "Ensure anti-phishing policies are properly configured and active.",
+ "CheckTitle": "Defender anti-phishing policy active, quarantines spoofed senders and DMARC reject/quarantine failures, honors DMARC policy, safety tips enabled",
"CheckType": [],
"ServiceName": "defender",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "low",
- "ResourceType": "Defender Anti-Phishing Policy",
+ "ResourceType": "NotDefined",
"ResourceGroup": "security",
- "Description": "Ensure that anti-phishing policies are created and configured for specific users, groups, or domains, taking precedence over the default policy. This check verifies the existence of rules within policies and validates specific policy settings such as spoof intelligence, DMARC actions, safety tips, and unauthenticated sender actions.",
- "Risk": "Without anti-phishing policies, organizations may rely solely on default settings, which might not adequately protect against phishing attacks targeted at specific users, groups, or domains. This increases the risk of successful phishing attempts and potential data breaches.",
- "RelatedUrl": "https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/set-up-anti-phishing-policies?view=o365-worldwide",
+ "Description": "**Microsoft Defender for Office 365 anti-phishing policies** are evaluated for custom scoping to users, groups, or domains and precedence over the default, plus key settings: **spoof intelligence**, DMARC honoring, `quarantine` actions for spoof/DMARC, **safety tips**, unauthenticated sender indicators, and policy enablement.",
+ "Risk": "Missing or lax configuration lets **spoofed** and **impersonated** emails reach inboxes. Ignoring DMARC or not using `quarantine` enables delivery of fraudulent messages, driving **credential theft**, **BEC**, and **account takeover**, compromising data **confidentiality** and **integrity** and enabling lateral movement via mailbox rule abuse.",
+ "RelatedUrl": "",
+ "AdditionalURLs": [
+ "https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/set-up-anti-phishing-policies?view=o365-worldwide",
+ "https://learn.microsoft.com/en-us/defender-office-365/anti-phishing-policies-mdo-configure",
+ "https://learn.microsoft.com/en-us/defender-office-365/anti-phishing-policies-about"
+ ],
"Remediation": {
"Code": {
- "CLI": "$params = @{Name='';PhishThresholdLevel=3;EnableTargetedUserProtection=$true;EnableOrganizationDomainsProtection=$true;EnableMailboxIntelligence=$true;EnableMailboxIntelligenceProtection=$true;EnableSpoofIntelligence=$true;TargetedUserProtectionAction='Quarantine';TargetedDomainProtectionAction='Quarantine';MailboxIntelligenceProtectionAction='Quarantine';TargetedUserQuarantineTag='DefaultFullAccessWithNotificationPolicy';MailboxIntelligenceQuarantineTag='DefaultFullAccessWithNotificationPolicy';TargetedDomainQuarantineTag='DefaultFullAccessWithNotificationPolicy';EnableFirstContactSafetyTips=$true;EnableSimilarUsersSafetyTips=$true;EnableSimilarDomainsSafetyTips=$true;EnableUnusualCharactersSafetyTips=$true;HonorDmarcPolicy=$true}; New-AntiPhishPolicy @params; New-AntiPhishRule -Name $params.Name -AntiPhishPolicy $params.Name -RecipientDomainIs (Get-AcceptedDomain).Name -Priority 0",
+ "CLI": "",
"NativeIaC": "",
- "Other": "1. Navigate to Microsoft 365 Defender https://security.microsoft.com. 2. Click to expand Email & collaboration and select Policies & rules. 3. On the Policies & rules page select Threat policies. 4. Under Policies, select Anti-phishing 5. Ensure policies have rules with the state set to 'on' and validate settings: spoof intelligence enabled, spoof intelligence action set to 'Quarantine', DMARC reject and quarantine actions, safety tips enabled, unauthenticated sender action enabled, show tag enabled, and honor DMARC policy enabled. If not, modify them to be as recommended.",
+ "Other": "1. Go to Microsoft 365 Defender: https://security.microsoft.com > Email & collaboration > Policies & rules > Threat policies > Anti-phishing\n2. Open the Default anti-phishing policy and click Edit\n3. Spoof settings: ensure Enable spoof intelligence is On and set If the message is detected as spoof by spoof intelligence to Quarantine\n4. DMARC: turn On Honor DMARC record policy and set both actions to Quarantine:\n - If DMARC policy is p=quarantine: Quarantine\n - If DMARC policy is p=reject: Quarantine\n5. Safety tips & indicators: turn On Show first contact safety tip, Show (?) for unauthenticated senders for spoof, and Show \"via\" tag\n6. Save changes\n7. If using custom anti-phishing policies, ensure their rule Status is On",
"Terraform": ""
},
"Recommendation": {
- "Text": "Create and configure anti-phishing policies for specific users, groups, or domains to enhance protection against phishing attacks.",
- "Url": "https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/set-up-anti-phishing-policies?view=o365-worldwide"
+ "Text": "Apply **defense in depth** for email:\n- Create high-priority custom policies for sensitive users/groups/domains\n- Enable **spoof intelligence**; honor DMARC (`p=quarantine`, `p=reject`) with `quarantine` actions\n- Turn on **safety tips** and unauthenticated sender tags\n- Review policy precedence, scope, and thresholds regularly to minimize false positives",
+ "Url": "https://hub.prowler.com/check/defender_antiphishing_policy_configured"
}
},
"Categories": [
+ "email-security",
"e5"
],
"DependsOn": [],
diff --git a/prowler/providers/m365/services/defender/defender_antispam_connection_filter_policy_empty_ip_allowlist/defender_antispam_connection_filter_policy_empty_ip_allowlist.metadata.json b/prowler/providers/m365/services/defender/defender_antispam_connection_filter_policy_empty_ip_allowlist/defender_antispam_connection_filter_policy_empty_ip_allowlist.metadata.json
index 17907a0bc7..fc7d6676cb 100644
--- a/prowler/providers/m365/services/defender/defender_antispam_connection_filter_policy_empty_ip_allowlist/defender_antispam_connection_filter_policy_empty_ip_allowlist.metadata.json
+++ b/prowler/providers/m365/services/defender/defender_antispam_connection_filter_policy_empty_ip_allowlist/defender_antispam_connection_filter_policy_empty_ip_allowlist.metadata.json
@@ -1,30 +1,34 @@
{
"Provider": "m365",
"CheckID": "defender_antispam_connection_filter_policy_empty_ip_allowlist",
- "CheckTitle": "Ensure the Anti-Spam Connection Filter Policy IP Allowlist is empty or undefined.",
+ "CheckTitle": "Defender Antispam Connection Filter Policy IP Allowlist is empty or undefined",
"CheckType": [],
"ServiceName": "defender",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "Defender Anti-Spam Policy",
+ "ResourceType": "NotDefined",
"ResourceGroup": "security",
- "Description": "This check focuses on Microsoft 365 organizations with Exchange Online mailboxes or standalone Exchange Online Protection (EOP) organizations. It ensures that the connection filter policy's IP Allowlist is empty or undefined to prevent bypassing spam filtering and sender authentication checks, which could lead to successful delivery of malicious emails.",
- "Risk": "Using the IP Allowlist without additional verification like mail flow rules poses a risk, as emails from these sources skip essential security checks (SPF, DKIM, DMARC). This could allow attackers to deliver harmful emails directly to the Inbox.",
+ "Description": "**Microsoft Defender connection filter policy** is evaluated to determine whether the **IP Allowlist** (`IPAllowList`) is configured. The finding indicates if any IP addresses are present in the policy's allow list for Exchange Online or standalone EOP environments.",
+ "Risk": "Allowlisted IPs bypass **SPF**, **DKIM**, **DMARC** and antispam, enabling **phishing** and **spoofing** that steal credentials (confidentiality), deliver **malware** or fraudulent mail (integrity), and cause **inbox flooding** (availability). Attackers can abuse compromised or shared relays on those IPs to deliver malicious mail.",
"RelatedUrl": "",
+ "AdditionalURLs": [
+ "https://learn.microsoft.com/en-us/powershell/module/exchange/set-hostedconnectionfilterpolicy?view=exchange-ps"
+ ],
"Remediation": {
"Code": {
- "CLI": "Set-HostedConnectionFilterPolicy -Identity Default -IPAllowList @{}",
+ "CLI": "Set-HostedConnectionFilterPolicy -Identity Default -IPAllowList $null",
"NativeIaC": "",
- "Other": "1. Navigate to Microsoft 365 Defender https://security.microsoft.com. 2. Click to expand Email & collaboration and select Policies & rules. 3. On the Policies & rules page select Threat policies. 4. Under Policies, select Anti-spam and click on the Connection filter policy (Default). 5. Remove IP entries from the allow list. 6. Click Save.",
+ "Other": "1. Go to https://security.microsoft.com and sign in\n2. Email & collaboration > Policies & rules > Threat policies\n3. Open Anti-spam > Connection filter policy (Default)\n4. Edit the IP allow list and remove all entries\n5. Click Save",
"Terraform": ""
},
"Recommendation": {
- "Text": "Ensure that the IP Allowlist in your connection filter policy is empty or undefined to prevent bypassing essential security checks.",
- "Url": "https://learn.microsoft.com/en-us/powershell/module/exchange/set-hostedconnectionfilterpolicy?view=exchange-ps"
+ "Text": "Keep the **IP Allowlist** empty. Rely on layered controls: **SPF**, **DKIM**, **DMARC**, connection filtering, and content scanning. *If an exception is unavoidable*, apply **defense in depth** (sender auth, TLS, reputation checks, and tight scope/time limits) and prefer domain- or certificate-based trust over static IPs. Monitor delivery logs for abuse.",
+ "Url": "https://hub.prowler.com/check/defender_antispam_connection_filter_policy_empty_ip_allowlist"
}
},
"Categories": [
+ "email-security",
"e3"
],
"DependsOn": [],
diff --git a/prowler/providers/m365/services/defender/defender_antispam_connection_filter_policy_safe_list_off/defender_antispam_connection_filter_policy_safe_list_off.metadata.json b/prowler/providers/m365/services/defender/defender_antispam_connection_filter_policy_safe_list_off/defender_antispam_connection_filter_policy_safe_list_off.metadata.json
index 202b67c678..fc99632352 100644
--- a/prowler/providers/m365/services/defender/defender_antispam_connection_filter_policy_safe_list_off/defender_antispam_connection_filter_policy_safe_list_off.metadata.json
+++ b/prowler/providers/m365/services/defender/defender_antispam_connection_filter_policy_safe_list_off/defender_antispam_connection_filter_policy_safe_list_off.metadata.json
@@ -1,30 +1,35 @@
{
"Provider": "m365",
"CheckID": "defender_antispam_connection_filter_policy_safe_list_off",
- "CheckTitle": "Ensure the default connection filter policy has the SafeList setting disabled",
+ "CheckTitle": "Defender Antispam Connection Filter Policy has Safe List disabled",
"CheckType": [],
"ServiceName": "defender",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "Defender Anti-Spam Policy",
+ "ResourceType": "NotDefined",
"ResourceGroup": "security",
- "Description": "This check ensures that the EnableSafeList setting in the default connection filter policy is set to False. The safe list, managed dynamically by Microsoft, allows emails from listed IPs to bypass spam filtering and sender authentication checks, posing a security risk.",
- "Risk": "If the safe list is enabled, emails from IPs on this list can bypass essential security checks (SPF, DKIM, DMARC), potentially allowing malicious emails to be delivered directly to users' inboxes.",
- "RelatedUrl": "https://learn.microsoft.com/en-us/defender-office-365/connection-filter-policies-configure",
+ "Description": "**Microsoft Defender for Office 365 connection filter policy** safe list setting is evaluated. When enabled, mail from Microsoft-managed IPs skips spam filtering and some sender authentication. The finding indicates whether this implicit bypass is turned off.",
+ "Risk": "With the safe list on, inbound mail can bypass SPF/DKIM/DMARC and spam heuristics, allowing spoofed or phishing messages to reach inboxes. This risks credential theft (confidentiality), enables account takeover and tampering (integrity), and may lead to malware-driven outages (availability).",
+ "RelatedUrl": "",
+ "AdditionalURLs": [
+ "https://learn.microsoft.com/en-us/defender-office-365/connection-filter-policies-configure",
+ "https://learn.microsoft.com/en-us/defender-office-365/create-safe-sender-lists-in-office-365#use-the-ip-allow-list"
+ ],
"Remediation": {
"Code": {
"CLI": "Set-HostedConnectionFilterPolicy -Identity Default -EnableSafeList $false",
"NativeIaC": "",
- "Other": "1. Navigate to Microsoft 365 Defender https://security.microsoft.com. 2. Click to expand Email & collaboration and select Policies & rules. 3. On the Policies & rules page select Threat policies. 4. Under Policies, select Anti-spam and click on the Connection filter policy (Default). 5. Disable the safe list option. 6. Click Save.",
+ "Other": "1. Go to https://security.microsoft.com/antispam\n2. Select Connection filter policy (Default)\n3. Click Edit connection filter policy\n4. Uncheck Turn on safe list\n5. Click Save",
"Terraform": ""
},
"Recommendation": {
- "Text": "Ensure that the EnableSafeList setting in your connection filter policy is set to False to prevent bypassing essential security checks.",
- "Url": "https://learn.microsoft.com/en-us/defender-office-365/create-safe-sender-lists-in-office-365#use-the-ip-allow-list"
+ "Text": "Disable the **safe list** (`EnableSafeList=false`). Favor **allow-by-exception**: use the **Tenant Allow/Block List** or tightly scoped IPs only when necessary and validated by strong **email authentication**. Apply **least privilege**, review exceptions regularly, and layer **defense in depth** with anti-phishing and monitoring.",
+ "Url": "https://hub.prowler.com/check/defender_antispam_connection_filter_policy_safe_list_off"
}
},
"Categories": [
+ "email-security",
"e3"
],
"DependsOn": [],
diff --git a/prowler/providers/m365/services/defender/defender_antispam_outbound_policy_configured/defender_antispam_outbound_policy_configured.metadata.json b/prowler/providers/m365/services/defender/defender_antispam_outbound_policy_configured/defender_antispam_outbound_policy_configured.metadata.json
index a0b33c0449..1844220471 100644
--- a/prowler/providers/m365/services/defender/defender_antispam_outbound_policy_configured/defender_antispam_outbound_policy_configured.metadata.json
+++ b/prowler/providers/m365/services/defender/defender_antispam_outbound_policy_configured/defender_antispam_outbound_policy_configured.metadata.json
@@ -1,30 +1,35 @@
{
"Provider": "m365",
"CheckID": "defender_antispam_outbound_policy_configured",
- "CheckTitle": "Ensure Defender Outbound Spam Policies are set to notify administrators.",
+ "CheckTitle": "Defender outbound spam policy is configured to notify recipients when senders are blocked or exceed sending limits",
"CheckType": [],
"ServiceName": "defender",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "low",
- "ResourceType": "Defender Anti-Spam Outbound Policy",
+ "ResourceType": "NotDefined",
"ResourceGroup": "security",
- "Description": "Ensure that outbound anti-spam policies are configured to notify administrators and copy suspicious outbound messages to designated recipients when a sender is blocked for sending spam emails.",
- "Risk": "Without outbound spam notifications and message copies, compromised accounts may go undetected, increasing the risk of reputation damage or data leakage through unauthorized email activity.",
- "RelatedUrl": "https://learn.microsoft.com/en-us/defender-office-365/outbound-spam-protection-about",
+ "Description": "**Microsoft Defender for Office 365 outbound spam policies** must send **administrator alerts** and Bcc **suspicious outbound messages** when a sender exceeds limits or is blocked. The assessment checks for `notify limit exceeded` and `notify sender blocked` with recipient addresses in the default policy and any applicable custom policies.",
+ "Risk": "Absent alerts and copies, **compromised mailboxes** can exfiltrate data and send phishing undetected. This harms **email deliverability** through blocklisting and throttling (**availability**), undermines domain **integrity**, and impedes **forensics** by removing evidence needed to triage abusive outbound traffic.",
+ "RelatedUrl": "",
+ "AdditionalURLs": [
+ "https://learn.microsoft.com/en-us/defender-office-365/outbound-spam-protection-about",
+ "https://learn.microsoft.com/is-is/defender-office-365/outbound-spam-policies-configure"
+ ],
"Remediation": {
"Code": {
- "CLI": "$BccEmailAddress = @(\"\")\n$NotifyEmailAddress = @(\"\")\nSet-HostedOutboundSpamFilterPolicy -Identity Default -BccSuspiciousOutboundAdditionalRecipients $BccEmailAddress -BccSuspiciousOutboundMail $true -NotifyOutboundSpam $true -NotifyOutboundSpamRecipients $NotifyEmailAddress",
+ "CLI": "Set-HostedOutboundSpamFilterPolicy -Identity Default -BccSuspiciousOutboundMail $true -BccSuspiciousOutboundAdditionalRecipients \"\" -NotifyOutboundSpam $true -NotifyOutboundSpamRecipients \"\"",
"NativeIaC": "",
- "Other": "1. Navigate to Microsoft 365 Defender https://security.microsoft.com. 2. Click to expand Email & collaboration and select Policies & rules > Threat policies. 3. Under Policies, select Anti-spam. 4. Click on the Anti-spam outbound policy (default). 5. Select Edit protection settings then under Notifications: 6. Check 'Send a copy of suspicious outbound messages or message that exceed these limits to these users and groups' and enter the email addresses. 7. Check 'Notify these users and groups if a sender is blocked due to sending outbound spam' and enter the desired email addresses. 8. Click Save.",
+ "Other": "1. Sign in to Microsoft 365 Defender: https://security.microsoft.com\n2. Go to Email & collaboration > Policies & rules > Threat policies > Anti-spam\n3. Open Anti-spam outbound policy (Default) and select Edit protection settings\n4. Under Notifications:\n - Check \"Send a copy of suspicious outbound messages or messages that exceed these limits to these users and groups\" and add \n - Check \"Notify these users and groups if a sender is blocked due to sending outbound spam\" and add \n5. Click Save",
"Terraform": ""
},
"Recommendation": {
- "Text": "Configure Defender outbound spam filter policies to notify administrators and copy suspicious outbound messages when users are blocked for sending spam.",
- "Url": "https://learn.microsoft.com/en-us/defender-office-365/outbound-spam-protection-about"
+ "Text": "Enable outbound spam notifications and Bcc suspicious messages to a monitored mailbox, applying them consistently to default and scoped policies. Set prudent sending limits and block actions, disable unnecessary external forwarding, and monitor alerts-aligning with **least privilege** and **defense in depth**.",
+ "Url": "https://hub.prowler.com/check/defender_antispam_outbound_policy_configured"
}
},
"Categories": [
+ "email-security",
"e3"
],
"DependsOn": [],
diff --git a/prowler/providers/m365/services/defender/defender_antispam_outbound_policy_forwarding_disabled/defender_antispam_outbound_policy_forwarding_disabled.metadata.json b/prowler/providers/m365/services/defender/defender_antispam_outbound_policy_forwarding_disabled/defender_antispam_outbound_policy_forwarding_disabled.metadata.json
index bcb9f406cf..98158f8af3 100644
--- a/prowler/providers/m365/services/defender/defender_antispam_outbound_policy_forwarding_disabled/defender_antispam_outbound_policy_forwarding_disabled.metadata.json
+++ b/prowler/providers/m365/services/defender/defender_antispam_outbound_policy_forwarding_disabled/defender_antispam_outbound_policy_forwarding_disabled.metadata.json
@@ -1,30 +1,34 @@
{
"Provider": "m365",
"CheckID": "defender_antispam_outbound_policy_forwarding_disabled",
- "CheckTitle": "Ensure Defender Outbound Spam Policies are set to disable mail forwarding.",
+ "CheckTitle": "Defender Outbound Spam policy disables mail forwarding",
"CheckType": [],
"ServiceName": "defender",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
- "ResourceType": "Defender Anti-Spam Outbound Policy",
+ "ResourceType": "NotDefined",
"ResourceGroup": "security",
- "Description": "Ensure Defender Outbound Spam Policies are set to disable mail forwarding.",
- "Risk": "Enabling email auto-forwarding can be exploited by attackers or malicious insiders to exfiltrate sensitive data outside the organization, often without detection.",
- "RelatedUrl": "https://learn.microsoft.com/en-us/defender-office-365/outbound-spam-protection-about",
+ "Description": "**Microsoft Defender for Office 365 outbound spam policies** are evaluated to confirm that automatic mail forwarding is disabled in the default policy and in any custom policies applied to users, groups, or domains.",
+ "Risk": "Allowing **automatic forwarding** enables covert **data exfiltration**, eroding **confidentiality**. Attackers or insiders can auto-route mail to external inboxes, persist access, evade monitoring, and harvest sensitive content (tickets, approvals, MFA codes), enabling **lateral movement** and fraud while reducing auditability.",
+ "RelatedUrl": "",
+ "AdditionalURLs": [
+ "https://learn.microsoft.com/en-us/defender-office-365/outbound-spam-protection-about"
+ ],
"Remediation": {
"Code": {
- "CLI": "Set-HostedOutboundSpamFilterPolicy -Identity {policyName} -AutoForwardingMode Off",
+ "CLI": "Set-HostedOutboundSpamFilterPolicy -Identity -AutoForwardingMode Off",
"NativeIaC": "",
- "Other": "1. Navigate to Microsoft 365 Defender https://security.microsoft.com/. 2. Expand E-mail & collaboration then select Policies & rules. 3. Select Threat policies > Anti-spam. 4. Select Anti-spam outbound policy (default). 5. Click Edit protection settings. 6. Set Automatic forwarding rules dropdown to Off - Forwarding is disabled and click Save. 7. Repeat steps 4-6 for any additional higher priority, custom policies.",
+ "Other": "1. Sign in to https://security.microsoft.com\n2. Go to Email & collaboration > Policies & rules > Threat policies > Anti-spam\n3. Open Anti-spam outbound policy (Default) or the target custom policy\n4. Click Edit protection settings and set Automatic forwarding rules to Off - Forwarding is disabled, then Save\n5. For custom policies, ensure the policy Status is On (enabled); repeat for any additional policies",
"Terraform": ""
},
"Recommendation": {
- "Text": "Block all forms of mail forwarding using Anti-spam outbound policies in Exchange Online. Apply exclusions only where justified by organizational policy.",
- "Url": "https://learn.microsoft.com/en-us/defender-office-365/outbound-spam-protection-about"
+ "Text": "Disable **automatic forwarding** globally in outbound spam policies to enforce **least privilege** on data flows. *If exceptions are required*, restrict to named senders or domains, document approvals, and review regularly. Add **DLP**, alerts on new forwarding rules, and mailbox auditing for **defense in depth**.",
+ "Url": "https://hub.prowler.com/check/defender_antispam_outbound_policy_forwarding_disabled"
}
},
"Categories": [
+ "email-security",
"e3"
],
"DependsOn": [],
diff --git a/prowler/providers/m365/services/defender/defender_antispam_policy_inbound_no_allowed_domains/defender_antispam_policy_inbound_no_allowed_domains.metadata.json b/prowler/providers/m365/services/defender/defender_antispam_policy_inbound_no_allowed_domains/defender_antispam_policy_inbound_no_allowed_domains.metadata.json
index 8413a8fc82..ed178c87f1 100644
--- a/prowler/providers/m365/services/defender/defender_antispam_policy_inbound_no_allowed_domains/defender_antispam_policy_inbound_no_allowed_domains.metadata.json
+++ b/prowler/providers/m365/services/defender/defender_antispam_policy_inbound_no_allowed_domains/defender_antispam_policy_inbound_no_allowed_domains.metadata.json
@@ -1,30 +1,36 @@
{
"Provider": "m365",
"CheckID": "defender_antispam_policy_inbound_no_allowed_domains",
- "CheckTitle": "Ensure inbound anti-spam policies do not contain allowed domains",
+ "CheckTitle": "Inbound anti-spam policy does not contain allowed domains",
"CheckType": [],
"ServiceName": "defender",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "low",
- "ResourceType": "Defender Anti-Spam Policy",
+ "ResourceType": "NotDefined",
"ResourceGroup": "security",
- "Description": "Ensure that inbound anti-spam policies do not have any domains listed in the AllowedSenderDomains. Messages from these domains bypass most email protections, increasing the risk of successful phishing attacks.",
- "Risk": "Having domains in the AllowedSenderDomains list allows emails from these domains to bypass essential security checks, increasing the risk of phishing attacks and other malicious activities.",
- "RelatedUrl": "https://learn.microsoft.com/en-us/defender-office-365/anti-spam-protection-about#allow-and-block-lists-in-anti-spam-policies",
+ "Description": "**Microsoft Defender for Office 365 inbound anti-spam policies** are evaluated for domains listed in `AllowedSenderDomains`.\n\nThe finding identifies any policy where this list is populated rather than empty.",
+ "Risk": "Populating `AllowedSenderDomains` makes messages from those domains skip **spam filtering** and **email authentication** (SPF, DKIM, DMARC), often delivered with SCL `-1`. Attackers can spoof such domains to phish credentials, enable BEC, and alter mailboxes, undermining **confidentiality** and **integrity**.",
+ "RelatedUrl": "",
+ "AdditionalURLs": [
+ "https://learn.microsoft.com/en-us/powershell/module/exchangepowershell/set-hostedcontentfilterpolicy?view=exchange-ps",
+ "https://learn.microsoft.com/en-us/defender-office-365/anti-spam-policies-configure",
+ "https://learn.microsoft.com/en-us/defender-office-365/anti-spam-protection-about#allow-and-block-lists-in-anti-spam-policies"
+ ],
"Remediation": {
"Code": {
- "CLI": "Set-HostedContentFilterPolicy -Identity -AllowedSenderDomains @{}",
+ "CLI": "Set-HostedContentFilterPolicy -Identity -AllowedSenderDomains $null",
"NativeIaC": "",
- "Other": "1. Navigate to Microsoft 365 Defender (https://security.microsoft.com). 2. Click to expand Email & collaboration and select Policies & rules > Threat policies. 3. Under Policies, select Anti-spam. 4. Open each out-of-compliance inbound anti-spam policy by clicking on it. 5. Click Edit allowed and blocked senders and domains. 6. Select Allow domains. 7. Delete each domain from the domains list. 8. Click Done > Save. 9. Repeat as needed.",
+ "Other": "1. Open Microsoft 365 Defender: https://security.microsoft.com/antispam\n2. Open each inbound anti-spam policy (Default and any custom).\n3. Click Edit allowed and blocked senders and domains.\n4. Select Allow domains.\n5. Remove all domains, then click Done and Save.\n6. Repeat for any remaining inbound anti-spam policies.",
"Terraform": ""
},
"Recommendation": {
- "Text": "Ensure that the AllowedSenderDomains list in your inbound anti-spam policies is empty to prevent bypassing essential security checks.",
- "Url": "https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/configure-the-allowed-sender-domains?view=o365-worldwide"
+ "Text": "- Keep `AllowedSenderDomains` empty.\n- Use narrowly scoped allow logic that requires authentication alignment and additional conditions (sender, IP, headers).\n- Make any exceptions temporary and reviewed.\n\nApply **least privilege** and **defense in depth** to email trust decisions.",
+ "Url": "https://hub.prowler.com/check/defender_antispam_policy_inbound_no_allowed_domains"
}
},
"Categories": [
+ "email-security",
"e3"
],
"DependsOn": [],
diff --git a/prowler/providers/m365/services/defender/defender_atp_safe_attachments_and_docs_configured/defender_atp_safe_attachments_and_docs_configured.metadata.json b/prowler/providers/m365/services/defender/defender_atp_safe_attachments_and_docs_configured/defender_atp_safe_attachments_and_docs_configured.metadata.json
index 5b69d64b5f..f485b342f3 100644
--- a/prowler/providers/m365/services/defender/defender_atp_safe_attachments_and_docs_configured/defender_atp_safe_attachments_and_docs_configured.metadata.json
+++ b/prowler/providers/m365/services/defender/defender_atp_safe_attachments_and_docs_configured/defender_atp_safe_attachments_and_docs_configured.metadata.json
@@ -7,9 +7,9 @@
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "Defender ATP Policy",
+ "ResourceType": "NotDefined",
"ResourceGroup": "security",
- "Description": "**Safe Attachments** for SharePoint, OneDrive, and Microsoft Teams protects organizations from inadvertently sharing malicious files. When enabled, files identified as malicious are blocked and cannot be opened, copied, moved, or shared until further actions are taken by the organization's security team.",
+ "Description": "**Microsoft Defender for Office 365 Safe Attachments** for SharePoint, OneDrive, and Microsoft Teams protects organizations from inadvertently sharing malicious files. When enabled, files identified as malicious are blocked and cannot be opened, copied, moved, or shared until further actions are taken by the organization's security team.",
"Risk": "Without **Safe Attachments** enabled, users may download, sync, or access **malicious files** from SharePoint, OneDrive, or Teams, potentially leading to **malware infections** across the organization. If **Safe Documents** is disabled or users can bypass **Protected View**, malicious content in Office documents may execute and **compromise endpoints**.",
"RelatedUrl": "",
"AdditionalURLs": [
diff --git a/prowler/providers/m365/services/defender/defender_chat_report_policy_configured/defender_chat_report_policy_configured.metadata.json b/prowler/providers/m365/services/defender/defender_chat_report_policy_configured/defender_chat_report_policy_configured.metadata.json
index feb96141a4..20428b2ae5 100644
--- a/prowler/providers/m365/services/defender/defender_chat_report_policy_configured/defender_chat_report_policy_configured.metadata.json
+++ b/prowler/providers/m365/services/defender/defender_chat_report_policy_configured/defender_chat_report_policy_configured.metadata.json
@@ -1,30 +1,34 @@
{
"Provider": "m365",
"CheckID": "defender_chat_report_policy_configured",
- "CheckTitle": "Ensure chat report submission policy is properly configured in Defender",
+ "CheckTitle": "Defender report submission policy uses customized addresses for junk, not junk and phish, and chat reports are sent only to a customized address",
"CheckType": [],
"ServiceName": "defender",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "Defender Report Submission Policy",
+ "ResourceType": "NotDefined",
"ResourceGroup": "security",
- "Description": "Ensure Defender report submission policy is properly configured to use customized addresses and enable chat message reporting to customized addresses, while disabling report chat message to Microsoft.",
- "Risk": "If Defender report submission policy is not properly configured, reported messages from Teams may not be handled or routed correctly, reducing the organization's ability to respond to threats.",
- "RelatedUrl": "https://learn.microsoft.com/en-us/defender-office-365/submissions-teams?view=o365-worldwide",
+ "Description": "**Microsoft Defender for Office 365** user-reported settings ensure `junk`, `not-junk`, and `phish` reports are sent to **customized addresses** with valid destinations, and that **Teams chat reports** route to customized addresses while direct chat reporting to Microsoft is disabled.",
+ "Risk": "Misrouted or disabled user reports reduce **visibility** into Teams threats, delaying containment. Attackers can keep distributing **phishing links** or **malicious files**, causing credential theft (**confidentiality**), message manipulation (**integrity**), and channel disruption from ongoing spam (**availability**).",
+ "RelatedUrl": "",
+ "AdditionalURLs": [
+ "https://learn.microsoft.com/en-us/defender-office-365/submissions-teams?view=o365-worldwide"
+ ],
"Remediation": {
"Code": {
- "CLI": "Set-ReportSubmissionPolicy -Identity DefaultReportSubmissionPolicy -EnableReportToMicrosoft $false -ReportChatMessageEnabled $false -ReportChatMessageToCustomizedAddressEnabled $true -ReportJunkToCustomizedAddress $true -ReportNotJunkToCustomizedAddress $true -ReportPhishToCustomizedAddress $true -ReportJunkAddresses $usersub -ReportNotJunkAddresses $usersub -ReportPhishAddresses $usersub",
+ "CLI": "Set-ReportSubmissionPolicy -Identity DefaultReportSubmissionPolicy -ReportJunkToCustomizedAddress $true -ReportNotJunkToCustomizedAddress $true -ReportPhishToCustomizedAddress $true -ReportJunkAddresses -ReportNotJunkAddresses -ReportPhishAddresses -ReportChatMessageEnabled $false -ReportChatMessageToCustomizedAddressEnabled $true",
"NativeIaC": "",
- "Other": "1. Navigate to Microsoft 365 Defender (https://security.microsoft.com/). 2. Click on Settings > Email & collaboration > User reported settings. 3. Scroll to Microsoft Teams section. 4. Ensure Monitor reported messages in Microsoft Teams is checked. 5. Ensure Send reported messages to: is set to My reporting mailbox only with report email addresses defined for authorized staff.",
+ "Other": "1. Go to Microsoft 365 Defender: https://security.microsoft.com\n2. Navigate to Settings > Email & collaboration > User reported settings\n3. In Reported message destinations (Outlook):\n - Turn on Send Junk to a customized address and enter \n - Turn on Send Not junk to a customized address and enter \n - Turn on Send Phish to a customized address and enter \n4. In Microsoft Teams section:\n - Turn off Monitor reported messages in Microsoft Teams\n - Turn on Send reported Teams messages to a customized address",
"Terraform": ""
},
"Recommendation": {
- "Text": "Configure Defender report submission policy to use customized addresses and enable chat message reporting to customized addresses, while disabling report chat message to Microsoft.",
- "Url": "https://learn.microsoft.com/en-us/defender-office-365/submissions-teams?view=o365-worldwide"
+ "Text": "Send all user-reported `junk`, `not-junk`, and `phish` to monitored **custom mailboxes** and enable **Teams chat reporting** to those addresses, keeping direct chat submissions to Microsoft disabled. Apply **least privilege** to reviewer access, establish a **triage workflow**, and integrate alerts for **defense in depth**.",
+ "Url": "https://hub.prowler.com/check/defender_chat_report_policy_configured"
}
},
"Categories": [
+ "email-security",
"e3"
],
"DependsOn": [],
diff --git a/prowler/providers/m365/services/defender/defender_domain_dkim_enabled/defender_domain_dkim_enabled.metadata.json b/prowler/providers/m365/services/defender/defender_domain_dkim_enabled/defender_domain_dkim_enabled.metadata.json
index 93921a4e0c..a57bce662a 100644
--- a/prowler/providers/m365/services/defender/defender_domain_dkim_enabled/defender_domain_dkim_enabled.metadata.json
+++ b/prowler/providers/m365/services/defender/defender_domain_dkim_enabled/defender_domain_dkim_enabled.metadata.json
@@ -1,30 +1,36 @@
{
"Provider": "m365",
"CheckID": "defender_domain_dkim_enabled",
- "CheckTitle": "Ensure that DKIM is enabled for all Exchange Online Domains",
+ "CheckTitle": "Exchange Online domain has DKIM enabled",
"CheckType": [],
"ServiceName": "defender",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
- "ResourceType": "Exchange Online Domain",
+ "ResourceType": "NotDefined",
"ResourceGroup": "collaboration",
- "Description": "This check ensures that DomainKeys Identified Mail (DKIM) is enabled for all Exchange Online domains. DKIM is a crucial authentication method that, along with SPF and DMARC, helps prevent attackers from sending spoofed emails that appear to originate from your domain. By adding a digital signature to outbound emails, DKIM allows receiving email systems to verify the legitimacy of incoming messages.",
- "Risk": "If DKIM is not enabled, attackers may send spoofed emails that appear to originate from your domain, potentially leading to phishing attacks and damage to your domain's reputation.",
- "RelatedUrl": "https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/use-dkim-to-validate-outbound-email?view=o365-worldwide",
+ "Description": "**Microsoft 365 Exchange Online domains** use **DKIM signing** for outbound mail. This evaluates each domain to confirm an active DKIM configuration so messages include a verifiable signature via the domain's DKIM selectors.",
+ "Risk": "Without **DKIM**, recipients can't verify sender authenticity, enabling **domain spoofing** and **BEC phishing**.\n\nAttackers can impersonate trusted mail to steal credentials, deliver malware, and pivot internally, impacting **confidentiality** and **integrity**. Messages may also fail **DMARC** alignment, reducing deliverability and trust.",
+ "RelatedUrl": "",
+ "AdditionalURLs": [
+ "https://learn.microsoft.com/en-us/powershell/module/exchangepowershell/set-dkimsigningconfig?view=exchange-ps",
+ "https://learn.microsoft.com/en-us/powershell/module/exchange/set-dkimsigningconfig?view=exchange-ps",
+ "https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/use-dkim-to-validate-outbound-email?view=o365-worldwide"
+ ],
"Remediation": {
"Code": {
- "CLI": "Set-DkimSigningConfig -Identity -Enabled $True",
+ "CLI": "Set-DkimSigningConfig -Identity -Enabled $true",
"NativeIaC": "",
- "Other": "1. After DNS records are created, enable DKIM signing in Microsoft 365 Defender. 2. Navigate to Microsoft 365 Defender at https://security.microsoft.com/. 3. Go to Email & collaboration > Policies & rules > Threat policies. 4. Under Rules, select Email authentication settings. 5. Choose DKIM, click on each domain, and enable 'Sign messages for this domain with DKIM signature'.",
+ "Other": "1. Sign in to Microsoft 365 Defender: https://security.microsoft.com\n2. Go to Email & collaboration > Policies & rules > Threat policies > Email authentication settings > DKIM\n3. Select the domain and enable \"Sign messages for this domain with DKIM signatures\"\n4. If prompted for CNAMEs, publish the two records shown at your DNS provider, wait for DNS to update, then return and enable in step 3",
"Terraform": ""
},
"Recommendation": {
- "Text": "Enable DKIM for all your Exchange Online domains to ensure emails are cryptographically signed and to protect against email spoofing.",
- "Url": "https://learn.microsoft.com/en-us/powershell/module/exchange/set-dkimsigningconfig?view=exchange-ps"
+ "Text": "- Enable **DKIM signing** for all sending domains and subdomains\n- Combine with **SPF** and **DMARC** to enforce alignment (defense in depth)\n- Apply **least privilege** to mail auth settings\n- Rotate DKIM keys regularly and monitor authentication results to detect anomalies",
+ "Url": "https://hub.prowler.com/check/defender_domain_dkim_enabled"
}
},
"Categories": [
+ "email-security",
"e3"
],
"DependsOn": [],
diff --git a/prowler/providers/m365/services/defender/defender_malware_policy_common_attachments_filter_enabled/defender_malware_policy_common_attachments_filter_enabled.metadata.json b/prowler/providers/m365/services/defender/defender_malware_policy_common_attachments_filter_enabled/defender_malware_policy_common_attachments_filter_enabled.metadata.json
index 7a2a731f82..d581e9fc0c 100644
--- a/prowler/providers/m365/services/defender/defender_malware_policy_common_attachments_filter_enabled/defender_malware_policy_common_attachments_filter_enabled.metadata.json
+++ b/prowler/providers/m365/services/defender/defender_malware_policy_common_attachments_filter_enabled/defender_malware_policy_common_attachments_filter_enabled.metadata.json
@@ -1,30 +1,36 @@
{
"Provider": "m365",
"CheckID": "defender_malware_policy_common_attachments_filter_enabled",
- "CheckTitle": "Ensure the Common Attachment Types Filter is enabled.",
+ "CheckTitle": "Defender malware policy has Common Attachment Types Filter enabled",
"CheckType": [],
"ServiceName": "defender",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "critical",
- "ResourceType": "Defender Malware Policy",
+ "ResourceType": "NotDefined",
"ResourceGroup": "security",
- "Description": "Ensure that the Common Attachment Types Filter is enabled in anti-malware policies to block known and custom malicious file types from being attached to emails.",
- "Risk": "If this setting is not enabled, users may receive emails with malicious attachments that could contain malware, increasing the risk of endpoint infection or data compromise.",
- "RelatedUrl": "https://learn.microsoft.com/en-us/defender-office-365/anti-malware-policies-configure?view=o365-worldwide",
+ "Description": "**Microsoft Defender for Office 365 anti-malware policies** use the **Common Attachment Types Filter** to block risky file formats regardless of extension. The evaluation checks whether this filter is enabled across default and custom policies and considers policy precedence that could override or bypass the protection.",
+ "Risk": "Without consistent **attachment type blocking**, malicious `exe`, `js`, `iso`, or `zip` payloads can reach users, enabling code execution and phishing kits.\n- Confidentiality: data exfiltration\n- Integrity: credential theft/tampering\n- Availability: ransomware\n\nPolicy scope/priority gaps can leave specific users unprotected.",
+ "RelatedUrl": "",
+ "AdditionalURLs": [
+ "https://learn.microsoft.com/en-us/exchange/security-and-compliance/mail-flow-rules/common-attachment-blocking-scenarios",
+ "https://learn.microsoft.com/en-us/powershell/module/exchange/set-malwarefilterpolicy?view=exchange-ps",
+ "https://learn.microsoft.com/en-us/defender-office-365/anti-malware-policies-configure?view=o365-worldwide"
+ ],
"Remediation": {
"Code": {
- "CLI": "Set-MalwareFilterPolicy -Identity Default -EnableFileFilter $true",
+ "CLI": "Set-MalwareFilterPolicy -Identity -EnableFileFilter $true",
"NativeIaC": "",
- "Other": "1. Navigate to Microsoft 365 Defender https://security.microsoft.com. 2. Click to expand Email & collaboration and select Policies & rules. 3. On the Policies & rules page select Threat policies. 4. Under Policies, select Anti-malware and click on the Default (Default) policy. 5. On the policy page, scroll to the bottom and click Edit protection settings. 6. Check the option Enable the common attachments filter. 7. Click Save.",
+ "Other": "1. Sign in to Microsoft 365 Defender: https://security.microsoft.com\n2. Go to Email & collaboration > Policies & rules > Threat policies > Anti-malware\n3. Open the failing anti-malware policy (e.g., Default or the named custom policy)\n4. Click Edit protection settings\n5. Enable \"Enable the common attachments filter\"\n6. Click Save",
"Terraform": ""
},
"Recommendation": {
- "Text": "Enable the common attachment types filter in your default or custom anti-malware policy to prevent the delivery of emails with potentially dangerous attachments.",
- "Url": "https://learn.microsoft.com/en-us/powershell/module/exchange/set-malwarefilterpolicy?view=exchange-ps"
+ "Text": "Enable the **Common Attachment Types Filter** in all applicable anti-malware policies and choose a strict action (e.g., `Quarantine` or `Reject`).\n- Block high-risk formats; review the list regularly\n- Align policy precedence to cover every recipient\n- Use defense-in-depth: **Safe Attachments**, **Safe Links**, **ZAP**; apply **least privilege** to file types",
+ "Url": "https://hub.prowler.com/check/defender_malware_policy_common_attachments_filter_enabled"
}
},
"Categories": [
+ "email-security",
"e3"
],
"DependsOn": [],
diff --git a/prowler/providers/m365/services/defender/defender_malware_policy_comprehensive_attachments_filter_applied/defender_malware_policy_comprehensive_attachments_filter_applied.metadata.json b/prowler/providers/m365/services/defender/defender_malware_policy_comprehensive_attachments_filter_applied/defender_malware_policy_comprehensive_attachments_filter_applied.metadata.json
index 3b3616d27f..0b0f2f8d67 100644
--- a/prowler/providers/m365/services/defender/defender_malware_policy_comprehensive_attachments_filter_applied/defender_malware_policy_comprehensive_attachments_filter_applied.metadata.json
+++ b/prowler/providers/m365/services/defender/defender_malware_policy_comprehensive_attachments_filter_applied/defender_malware_policy_comprehensive_attachments_filter_applied.metadata.json
@@ -1,30 +1,36 @@
{
"Provider": "m365",
"CheckID": "defender_malware_policy_comprehensive_attachments_filter_applied",
- "CheckTitle": "Ensure the Common Attachment Types Filter is enabled and applied in a comprehensive way",
+ "CheckTitle": "Defender anti-malware policy has Common Attachment Types Filter enabled and blocks all recommended file types",
"CheckType": [],
"ServiceName": "defender",
"SubServiceName": "",
"ResourceIdTemplate": "",
- "Severity": "medium",
- "ResourceType": "Defender Malware Policy",
+ "Severity": "high",
+ "ResourceType": "NotDefined",
"ResourceGroup": "security",
- "Description": "Ensure that the Common Attachment Types Filter is enabled in all enabled anti-malware policies in a Comprehensive way to block known and custom malicious file types from being attached to emails. This means that the file types that the filter blocks are checked by the organization, by default all the default file types from M365 defender should be blocked but you can change that with the config file.",
- "Risk": "If this setting or the policy is not enabled, users may receive emails with malicious attachments that could contain malware, increasing the risk of endpoint infection or data compromise.",
- "RelatedUrl": "https://learn.microsoft.com/en-us/defender-office-365/anti-malware-protection-about?view=o365-worldwide#common-attachments-filter-in-anti-malware-policies",
+ "Description": "**Microsoft Defender anti-malware policies** use the **Common Attachment Types Filter** to block a comprehensive set of risky file extensions. It evaluates whether the filter is enabled and all recommended types are blocked across the default policy and any enabled custom policies, considering scope and precedence.",
+ "Risk": "Missing or partial blocking of dangerous extensions lets **malicious attachments** reach users, enabling code execution, malware staging, and credential theft. Mis-scoped custom policies can override safer defaults, risking **confidentiality** via data exfiltration and **availability** through ransomware and lateral movement.",
+ "RelatedUrl": "",
+ "AdditionalURLs": [
+ "https://learn.microsoft.com/en-us/defender-office-365/anti-malware-policies-configure",
+ "https://learn.microsoft.com/en-us/defender-office-365/anti-malware-protection-about?view=o365-worldwide#common-attachments-filter-in-anti-malware-policies",
+ "https://learn.microsoft.com/en-us/powershell/module/exchange/set-malwarefilterpolicy?view=exchange-ps"
+ ],
"Remediation": {
"Code": {
- "CLI": "$Policy = @{Name = 'CIS L2 Attachment Policy'; EnableFileFilter = $true; }; $L2Extensions = @('ace','ani','apk','app','appx','arj','bat','cab','cmd','com','deb','dex','dll','docm','elf','exe','hta','img','iso','jar','jnlp','kext','lha','lib','library','lnk','lzh','macho','msc','msi','msix','msp','mst','pif','ppa','ppam','reg','rev','scf','scr','sct','sys','uif','vb','vbe','vbs','vxd','wsc','wsf','wsh','xll','xz','z'); New-MalwareFilterPolicy @Policy -FileTypes $L2Extensions; $Rule = @{Name = $Policy.Name; Enabled = $false; MalwareFilterPolicy = $Policy.Name; Priority = 0}; New-MalwareFilterRule @Rule",
+ "CLI": "Set-MalwareFilterPolicy -Identity Default -EnableFileFilter $true -FileTypes ace,ani,apk,app,appx,arj,bat,cab,cmd,com,deb,dex,dll,docm,elf,exe,hta,img,iso,jar,jnlp,kext,lha,lib,library,lnk,lzh,macho,msc,msi,msix,msp,mst,pif,ppa,ppam,reg,rev,scf,scr,sct,sys,uif,vb,vbe,vbs,vxd,wsc,wsf,wsh,xll,xz,z",
"NativeIaC": "",
- "Other": "1. Navigate to Microsoft 365 Defender https://security.microsoft.com. 2. Click to expand Email & collaboration and select Policies & rules. 3. On the Policies & rules page select Threat policies. 4. Under Policies, select Anti-malware and click on the Default (Default) policy. 5. On the policy page, scroll to the bottom and click Edit protection settings. 6. Check the option Enable the common attachments filter. 7. Click on select file types and select the file types you want to block. 8. Click Save. 9. Ensure the status of the policy is On",
+ "Other": "1. Go to Microsoft 365 Defender: https://security.microsoft.com\n2. Navigate to Email & collaboration > Policies & rules > Threat policies > Anti-malware\n3. Open Default (Default) policy and select Edit protection settings\n4. Enable \"Enable the common attachments filter\"\n5. Select file types and ensure ALL of these are selected: ace, ani, apk, app, appx, arj, bat, cab, cmd, com, deb, dex, dll, docm, elf, exe, hta, img, iso, jar, jnlp, kext, lha, lib, library, lnk, lzh, macho, msc, msi, msix, msp, mst, pif, ppa, ppam, reg, rev, scf, scr, sct, sys, uif, vb, vbe, vbs, vxd, wsc, wsf, wsh, xll, xz, z\n6. Save",
"Terraform": ""
},
"Recommendation": {
- "Text": "Enable the common attachment types filter in your default or custom anti-malware policy to prevent the delivery of emails with potentially dangerous attachments.",
- "Url": "https://learn.microsoft.com/en-us/powershell/module/exchange/set-malwarefilterpolicy?view=exchange-ps"
+ "Text": "Enable and enforce the **Common Attachment Types Filter** in all anti-malware policies and block the full recommended set. Align custom policy scope and priority to avoid weakening coverage. Apply **least privilege** to exceptions, prefer quarantine, and regularly review/expand blocked types. Use ZAP and monitoring for **defense-in-depth**.",
+ "Url": "https://hub.prowler.com/check/defender_malware_policy_comprehensive_attachments_filter_applied"
}
},
"Categories": [
+ "email-security",
"e3"
],
"DependsOn": [],
diff --git a/prowler/providers/m365/services/defender/defender_malware_policy_notifications_internal_users_malware_enabled/defender_malware_policy_notifications_internal_users_malware_enabled.metadata.json b/prowler/providers/m365/services/defender/defender_malware_policy_notifications_internal_users_malware_enabled/defender_malware_policy_notifications_internal_users_malware_enabled.metadata.json
index 1fc47b03c6..4380f8eed4 100644
--- a/prowler/providers/m365/services/defender/defender_malware_policy_notifications_internal_users_malware_enabled/defender_malware_policy_notifications_internal_users_malware_enabled.metadata.json
+++ b/prowler/providers/m365/services/defender/defender_malware_policy_notifications_internal_users_malware_enabled/defender_malware_policy_notifications_internal_users_malware_enabled.metadata.json
@@ -1,30 +1,35 @@
{
"Provider": "m365",
"CheckID": "defender_malware_policy_notifications_internal_users_malware_enabled",
- "CheckTitle": "Ensure notifications for internal users sending malware is Enabled",
+ "CheckTitle": "Defender anti-malware policy has admin notifications enabled for internal users sending malware",
"CheckType": [],
"ServiceName": "defender",
"SubServiceName": "",
"ResourceIdTemplate": "",
- "Severity": "high",
- "ResourceType": "Defender Malware Policy",
+ "Severity": "medium",
+ "ResourceType": "NotDefined",
"ResourceGroup": "security",
- "Description": "Verify that Exchange Online Protection (EOP) is configured to notify admins of malicious activity from internal users.",
- "Risk": "If notifications for internal users sending malware are not enabled, administrators may not be aware of potential threats originating from within the organization, increasing the risk of undetected malicious activities.",
- "RelatedUrl": "https://learn.microsoft.com/en-us/defender-office-365/anti-malware-protection-about",
+ "Description": "**Microsoft Defender for Office 365 anti-malware policies** are checked for **admin notifications** on malware detected from **internal senders**, ensuring a valid notification address is defined (`EnableInternalSenderAdminNotifications` and `InternalSenderAdminAddress`).\n\n*Effective settings across default and custom policies are considered.*",
+ "Risk": "Without these notifications, malware sent from internal accounts can persist unnoticed, delaying response and containment. This undermines **integrity** of email, enables **lateral movement** and **outbound propagation**, and can cause **domain reputation** damage and blocklisting, affecting **availability**.",
+ "RelatedUrl": "",
+ "AdditionalURLs": [
+ "https://learn.microsoft.com/en-us/powershell/module/exchange/set-malwarefilterpolicy?view=exchange-ps",
+ "https://learn.microsoft.com/en-us/defender-office-365/anti-malware-protection-about"
+ ],
"Remediation": {
"Code": {
- "CLI": "Set-MalwareFilterPolicy -Identity Default -EnableInternalSenderAdminNotifications $true -InternalSenderAdminAddress 'admin@example.com'",
+ "CLI": "Set-MalwareFilterPolicy -Identity Default -EnableInternalSenderAdminNotifications $true -InternalSenderAdminAddress \"\"",
"NativeIaC": "",
- "Other": "1. Connect to Exchange Online using Connect-ExchangeOnline. 2. Execute the command: Get-MalwareFilterPolicy | fl Identity, EnableInternalSenderAdminNotifications, InternalSenderAdminAddress. 3. Ensure 'Notify an admin about undelivered messages from internal senders' is set to On and that at least one email address is listed under Administrator email address.",
+ "Other": "1. In the Microsoft Defender portal (security.microsoft.com), go to Email & collaboration > Policies & rules > Threat policies > Anti-malware\n2. Select the affected policy (e.g., Default) and click Edit policy\n3. Open Notifications\n4. Turn on \"Notify an admin about undelivered messages from internal senders\"\n5. Add at least one Administrator email address\n6. Save",
"Terraform": ""
},
"Recommendation": {
- "Text": "Enable notifications for internal users sending malware in your Defender Malware Policy to ensure admins are alerted of potential threats.",
- "Url": "https://learn.microsoft.com/en-us/powershell/module/exchange/set-malwarefilterpolicy?view=exchange-ps"
+ "Text": "Enable and maintain admin alerts for internal-sender malware and route to a monitored mailbox or SOC list (`EnableInternalSenderAdminNotifications` and `InternalSenderAdminAddress`).\n\nEnsure coverage via policy precedence, integrate with SIEM, and apply **least privilege** and **defense in depth** to limit impact.",
+ "Url": "https://hub.prowler.com/check/defender_malware_policy_notifications_internal_users_malware_enabled"
}
},
"Categories": [
+ "email-security",
"e3"
],
"DependsOn": [],
diff --git a/prowler/providers/m365/services/defender/defender_safe_attachments_policy_enabled/defender_safe_attachments_policy_enabled.metadata.json b/prowler/providers/m365/services/defender/defender_safe_attachments_policy_enabled/defender_safe_attachments_policy_enabled.metadata.json
index 0a4176a0df..da2d92d35d 100644
--- a/prowler/providers/m365/services/defender/defender_safe_attachments_policy_enabled/defender_safe_attachments_policy_enabled.metadata.json
+++ b/prowler/providers/m365/services/defender/defender_safe_attachments_policy_enabled/defender_safe_attachments_policy_enabled.metadata.json
@@ -7,9 +7,9 @@
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "Defender Safe Attachments Policy",
+ "ResourceType": "NotDefined",
"ResourceGroup": "security",
- "Description": "Safe Attachments provides an additional layer of protection by scanning email attachments in a secure environment before delivering them to recipients.\n\nThe Built-In Protection Policy should have **Enable=True**, **Action=Block**, and **QuarantineTag=AdminOnlyAccessPolicy** to ensure malicious attachments are blocked and quarantined with admin-only access.",
+ "Description": "**Microsoft Defender for Office 365 Safe Attachments** provides an additional layer of protection by scanning email attachments in a secure environment before delivering them to recipients.\n\nThe Built-In Protection Policy should have **Enable=True**, **Action=Block**, and **QuarantineTag=AdminOnlyAccessPolicy** to ensure malicious attachments are blocked and quarantined with admin-only access.",
"Risk": "Without properly configured Safe Attachments policies, malicious email attachments could reach users' mailboxes and potentially compromise the organization through:\n\n- **Malware delivery** via infected documents\n- **Ransomware attacks** through weaponized attachments\n- **Data exfiltration** using malicious scripts",
"RelatedUrl": "",
"AdditionalURLs": [
diff --git a/prowler/providers/m365/services/defender/defender_safelinks_policy_enabled/defender_safelinks_policy_enabled.metadata.json b/prowler/providers/m365/services/defender/defender_safelinks_policy_enabled/defender_safelinks_policy_enabled.metadata.json
index cccebcfc7f..0c2c716822 100644
--- a/prowler/providers/m365/services/defender/defender_safelinks_policy_enabled/defender_safelinks_policy_enabled.metadata.json
+++ b/prowler/providers/m365/services/defender/defender_safelinks_policy_enabled/defender_safelinks_policy_enabled.metadata.json
@@ -7,9 +7,9 @@
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "Defender Safe Links Policy",
+ "ResourceType": "NotDefined",
"ResourceGroup": "security",
- "Description": "Safe Links is a Microsoft Defender for Office 365 feature that provides URL scanning and rewriting of inbound email messages, as well as time-of-click verification of URLs and links in email messages, Teams, and Office apps.\n\nThis check verifies that the Safe Links policy is properly configured with all recommended settings enabled.",
+ "Description": "**Microsoft Defender for Office 365 Safe Links** is a feature that provides URL scanning and rewriting of inbound email messages, as well as time-of-click verification of URLs and links in email messages, Teams, and Office apps.\n\nThis check verifies that the Safe Links policy is properly configured with all recommended settings enabled.",
"Risk": "Without properly configured Safe Links protection, users may be vulnerable to **phishing attacks** and **malicious URLs** in emails, Teams messages, and Office documents.\n\nAttackers could bypass security by using URLs that redirect to malicious content after initial scanning.",
"RelatedUrl": "",
"AdditionalURLs": [
diff --git a/prowler/providers/m365/services/defender/defender_zap_for_teams_enabled/defender_zap_for_teams_enabled.metadata.json b/prowler/providers/m365/services/defender/defender_zap_for_teams_enabled/defender_zap_for_teams_enabled.metadata.json
index d2df3f8892..d19f3c5229 100644
--- a/prowler/providers/m365/services/defender/defender_zap_for_teams_enabled/defender_zap_for_teams_enabled.metadata.json
+++ b/prowler/providers/m365/services/defender/defender_zap_for_teams_enabled/defender_zap_for_teams_enabled.metadata.json
@@ -7,9 +7,9 @@
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "Teams Protection Policy",
+ "ResourceType": "NotDefined",
"ResourceGroup": "collaboration",
- "Description": "Zero-hour auto purge (ZAP) is a protection feature that retroactively detects and neutralizes **malware** and **high confidence phishing** in Teams messages.\n\nWhen ZAP blocks a message, it is blocked for everyone in the chat. The initial block happens right after delivery, but ZAP can occur up to 48 hours after delivery.",
+ "Description": "**Microsoft Defender for Office 365 Zero-hour auto purge (ZAP)** is a protection feature that retroactively detects and neutralizes **malware** and **high confidence phishing** in Teams messages.\n\nWhen ZAP blocks a message, it is blocked for everyone in the chat. The initial block happens right after delivery, but ZAP can occur up to 48 hours after delivery.",
"Risk": "Without ZAP enabled, malicious content delivered to Teams chats remains accessible to users for up to 48 hours after delivery, even after being identified as harmful.\n\nThis extended exposure window could lead to:\n- **Malware infections** from weaponized attachments or links\n- **Phishing attacks** compromising user credentials and MFA tokens\n- **Lateral movement** as attackers exploit compromised accounts within the organization",
"RelatedUrl": "",
"AdditionalURLs": [
From e3e2408717de9e93aff60ad51f46811f40020e2a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rub=C3=A9n=20De=20la=20Torre=20Vico?=
Date: Mon, 9 Mar 2026 20:42:33 +0100
Subject: [PATCH 007/108] chore(m365): enhance metadata for `purview` service
(#9092)
Co-authored-by: Daniel Barranquero
---
prowler/CHANGELOG.md | 1 +
...entity_health_issues_no_open.metadata.json | 4 ++--
...management_pending_approvals.metadata.json | 8 +++----
...ged_user_exposed_credentials.metadata.json | 4 ++--
...iew_audit_log_search_enabled.metadata.json | 21 ++++++++++++-------
5 files changed, 22 insertions(+), 16 deletions(-)
diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md
index 969a08da62..90e0e86103 100644
--- a/prowler/CHANGELOG.md
+++ b/prowler/CHANGELOG.md
@@ -33,6 +33,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- Update Alibaba Cloud services metadata to new format [(#10289)](https://github.com/prowler-cloud/prowler/pull/10289)
- Update M365 Admin Center service metadata to new format [(#9680)](https://github.com/prowler-cloud/prowler/pull/9680)
- Update M365 Defender service metadata to new format [(#9681)](https://github.com/prowler-cloud/prowler/pull/9681)
+- Update M365 Purview service metadata to new format [(#9092)](https://github.com/prowler-cloud/prowler/pull/9092)
---
diff --git a/prowler/providers/m365/services/defenderidentity/defenderidentity_health_issues_no_open/defenderidentity_health_issues_no_open.metadata.json b/prowler/providers/m365/services/defenderidentity/defenderidentity_health_issues_no_open/defenderidentity_health_issues_no_open.metadata.json
index deaf9078f8..52b09e830f 100644
--- a/prowler/providers/m365/services/defenderidentity/defenderidentity_health_issues_no_open/defenderidentity_health_issues_no_open.metadata.json
+++ b/prowler/providers/m365/services/defenderidentity/defenderidentity_health_issues_no_open/defenderidentity_health_issues_no_open.metadata.json
@@ -7,9 +7,9 @@
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "Defender for Identity Health Issue",
+ "ResourceType": "NotDefined",
"ResourceGroup": "security",
- "Description": "Microsoft Defender for Identity (MDI) monitors your hybrid identity infrastructure and detects advanced threats targeting Active Directory. This check verifies that MDI sensors are deployed and that there are no unresolved health issues that may affect the ability to detect identity-based attacks.",
+ "Description": "**Microsoft Defender for Identity (MDI)** monitors your hybrid identity infrastructure and detects advanced threats targeting Active Directory. This check verifies that MDI sensors are deployed and that there are no unresolved health issues that may affect the ability to detect identity-based attacks.",
"Risk": "Without deployed MDI sensors or with unresolved health issues, organizations face critical gaps in threat detection. Misconfigured or missing sensors fail to monitor domain controllers, allowing identity-based attacks like Pass-the-Hash, Golden Ticket, or lateral movement to go undetected. Attackers commonly exploit these blind spots to compromise hybrid environments while evading detection.",
"RelatedUrl": "",
"AdditionalURLs": [
diff --git a/prowler/providers/m365/services/defenderxdr/defenderxdr_critical_asset_management_pending_approvals/defenderxdr_critical_asset_management_pending_approvals.metadata.json b/prowler/providers/m365/services/defenderxdr/defenderxdr_critical_asset_management_pending_approvals/defenderxdr_critical_asset_management_pending_approvals.metadata.json
index 1d2c58ba2b..4bd6c1a5a3 100644
--- a/prowler/providers/m365/services/defenderxdr/defenderxdr_critical_asset_management_pending_approvals/defenderxdr_critical_asset_management_pending_approvals.metadata.json
+++ b/prowler/providers/m365/services/defenderxdr/defenderxdr_critical_asset_management_pending_approvals/defenderxdr_critical_asset_management_pending_approvals.metadata.json
@@ -1,16 +1,16 @@
{
"Provider": "m365",
"CheckID": "defenderxdr_critical_asset_management_pending_approvals",
- "CheckTitle": "Ensure all Critical Asset Management classifications are reviewed and approved in Microsoft Defender XDR",
+ "CheckTitle": "Critical asset management classifications are reviewed and approved",
"CheckType": [],
"ServiceName": "defenderxdr",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
- "ResourceType": "Defender XDR Critical Asset Management",
+ "ResourceType": "NotDefined",
"ResourceGroup": "security",
- "Description": "Assets with a lower classification confidence score in Microsoft Defender XDR must be approved by a security administrator.\n\nAsset classifications that have not yet been reviewed and approved may result in incomplete **critical asset** visibility.",
- "Risk": "Stale pending approvals lead to limited visibility in Microsoft Defender XDR. **Critical assets** that are not properly identified and classified may not receive appropriate security monitoring and protections, creating gaps in the organization's security posture.",
+ "Description": "**Microsoft Defender XDR critical asset management classifications** with a lower classification confidence score must be approved by a security administrator.\n\nAsset classifications that have not yet been reviewed and approved may result in incomplete **critical asset** visibility.",
+ "Risk": "Stale pending approvals lead to limited visibility in **Microsoft Defender XDR**. **Critical assets** that are not properly identified and classified may not receive appropriate security monitoring and protections, creating gaps in the organization's security posture.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/security-exposure-management/classify-critical-assets",
diff --git a/prowler/providers/m365/services/defenderxdr/defenderxdr_endpoint_privileged_user_exposed_credentials/defenderxdr_endpoint_privileged_user_exposed_credentials.metadata.json b/prowler/providers/m365/services/defenderxdr/defenderxdr_endpoint_privileged_user_exposed_credentials/defenderxdr_endpoint_privileged_user_exposed_credentials.metadata.json
index 3f4efe68c8..4142484eec 100644
--- a/prowler/providers/m365/services/defenderxdr/defenderxdr_endpoint_privileged_user_exposed_credentials/defenderxdr_endpoint_privileged_user_exposed_credentials.metadata.json
+++ b/prowler/providers/m365/services/defenderxdr/defenderxdr_endpoint_privileged_user_exposed_credentials/defenderxdr_endpoint_privileged_user_exposed_credentials.metadata.json
@@ -7,9 +7,9 @@
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "critical",
- "ResourceType": "Exposure Management",
+ "ResourceType": "NotDefined",
"ResourceGroup": "security",
- "Description": "Privileged users may have authentication artifacts (CLI secrets, cookies, tokens) exposed on endpoints with high risk scores. Microsoft Defender XDR's Security Exposure Management detects when credentials from users with Entra ID privileged roles are present on vulnerable devices.",
+ "Description": "Microsoft Defender XDR's **Security Exposure Management** detects when credentials from users with Entra ID privileged roles are present on vulnerable devices. Privileged users may have authentication artifacts (CLI secrets, cookies, tokens) exposed on endpoints with high risk scores.",
"Risk": "Exposed credentials on vulnerable endpoints enable account takeover through stolen tokens or cookies, Conditional Access bypass via primary refresh tokens, lateral movement to sensitive resources, and persistence until tokens are explicitly revoked.",
"RelatedUrl": "",
"AdditionalURLs": [
diff --git a/prowler/providers/m365/services/purview/purview_audit_log_search_enabled/purview_audit_log_search_enabled.metadata.json b/prowler/providers/m365/services/purview/purview_audit_log_search_enabled/purview_audit_log_search_enabled.metadata.json
index 6a7f056fe5..68e458ee49 100644
--- a/prowler/providers/m365/services/purview/purview_audit_log_search_enabled/purview_audit_log_search_enabled.metadata.json
+++ b/prowler/providers/m365/services/purview/purview_audit_log_search_enabled/purview_audit_log_search_enabled.metadata.json
@@ -1,30 +1,35 @@
{
"Provider": "m365",
"CheckID": "purview_audit_log_search_enabled",
- "CheckTitle": "Ensure Purview audit log search is enabled",
+ "CheckTitle": "Purview audit log search is enabled",
"CheckType": [],
"ServiceName": "purview",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "critical",
- "ResourceType": "Purview Settings",
+ "ResourceType": "NotDefined",
"ResourceGroup": "governance",
- "Description": "Ensure Purview audit log search is enabled.",
- "Risk": "Disabling Microsoft 365 audit log search can hinder the ability to track and monitor user and admin activities, making it harder to detect suspicious behavior, security incidents, or compliance violations. This can result in undetected breaches and inability to respond to incidents effectively.",
- "RelatedUrl": "https://learn.microsoft.com/en-us/purview/audit-search?tabs=microsoft-purview-portal",
+ "Description": "Microsoft Purview tenant setting for **audit log search** is assessed to confirm unified audit log ingestion (`UnifiedAuditLogIngestionEnabled`), which records user and admin activities and makes them searchable.",
+ "Risk": "Without **audit log ingestion/search**, activity trails are missing or delayed, reducing visibility and accountability.\n- Data exfiltration and privilege abuse go undetected (confidentiality/integrity)\n- Incident response and forensics fail due to absent evidence, increasing dwell time",
+ "RelatedUrl": "",
+ "AdditionalURLs": [
+ "https://learn.microsoft.com/en-us/purview/audit-search?tabs=microsoft-purview-portal",
+ "https://learn.microsoft.com/en-us/purview/audit-log-enable-disable"
+ ],
"Remediation": {
"Code": {
"CLI": "Set-AdminAuditLogConfig -UnifiedAuditLogIngestionEnabled $true",
"NativeIaC": "",
- "Other": "1. Navigate to Microsoft Purview https://compliance.microsoft.com. 2. Select Audit to open the audit search. 3. Click Start recording user and admin activity next to the information warning at the top. 4. Click Yes on the dialog box to confirm.",
+ "Other": "1. Go to https://compliance.microsoft.com and sign in with an admin account\n2. Open Solutions > Audit\n3. Click Start recording user and admin activity\n4. Click Yes to confirm",
"Terraform": ""
},
"Recommendation": {
- "Text": "Ensure that Microsoft 365 audit log search is enabled to maintain a comprehensive record of user and admin activities. This will help improve security monitoring, support compliance needs, and provide critical insights for responding to incidents.",
- "Url": "https://learn.microsoft.com/en-us/purview/audit-search?tabs=microsoft-purview-portal"
+ "Text": "Enable and keep **audit log search** on (`UnifiedAuditLogIngestionEnabled=true`). Apply **least privilege** to audit roles, set retention aligned to sensitivity, forward logs to a SIEM for **defense in depth**, and routinely review and alert on audit events. *Avoid disabling auditing even when using third-party tools.*",
+ "Url": "https://hub.prowler.com/check/purview_audit_log_search_enabled"
}
},
"Categories": [
+ "logging",
"e3"
],
"DependsOn": [],
From e4bccfb26e025ff7a70f58dcc519d61988760f98 Mon Sep 17 00:00:00 2001
From: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com>
Date: Tue, 10 Mar 2026 09:54:30 +0100
Subject: [PATCH 008/108] chore(ui): move security changelog entry from v19.1
to v20 (#10291)
---
ui/CHANGELOG.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md
index 6f1482426f..bff954f997 100644
--- a/ui/CHANGELOG.md
+++ b/ui/CHANGELOG.md
@@ -12,16 +12,16 @@ All notable changes to the **Prowler UI** are documented in this file.
---
+### 🔐 Security
+
+- npm transitive dependencies patched to resolve 11 Dependabot alerts (6 HIGH, 4 MEDIUM, 1 LOW): hono, @hono/node-server, fast-xml-parser, serialize-javascript, minimatch [(#10267)](https://github.com/prowler-cloud/prowler/pull/10267)
+
## [1.19.1] (Prowler v5.19.1 UNRELEASED)
### 🐞 Fixed
- Provider wizard now closes after updating credentials instead of incorrectly advancing to the Launch Scan step, which caused API errors for providers with existing scheduled scans [(#10278)](https://github.com/prowler-cloud/prowler/pull/10278)
-### 🔐 Security
-
-- npm transitive dependencies patched to resolve 11 Dependabot alerts (6 HIGH, 4 MEDIUM, 1 LOW): hono, @hono/node-server, fast-xml-parser, serialize-javascript, minimatch [(#10267)](https://github.com/prowler-cloud/prowler/pull/10267)
-
---
## [1.19.0] (Prowler v5.19.0)
From d3213e9f1e16751a8f3fd1261895ec7c5a4cce84 Mon Sep 17 00:00:00 2001
From: Pepe Fagoaga
Date: Tue, 10 Mar 2026 09:54:09 +0000
Subject: [PATCH 009/108] chore(providers): Return 409 on conflict (#10293)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
api/CHANGELOG.md | 8 ++++++++
api/src/backend/api/tests/test_views.py | 6 +++++-
api/src/backend/api/v1/serializers.py | 21 +++++++++++++++++++++
3 files changed, 34 insertions(+), 1 deletion(-)
diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md
index 22da548d71..cfaecfa2f2 100644
--- a/api/CHANGELOG.md
+++ b/api/CHANGELOG.md
@@ -2,6 +2,14 @@
All notable changes to the **Prowler API** are documented in this file.
+## [1.21.0] (Prowler UNRELEASED)
+
+### 🔄 Changed
+
+- `POST /api/v1/providers` returns `409 Conflict` if already exists [(#10293)](https://github.com/prowler-cloud/prowler/pull/10293)
+
+---
+
## [1.20.0] (Prowler v5.19.0)
### 🚀 Added
diff --git a/api/src/backend/api/tests/test_views.py b/api/src/backend/api/tests/test_views.py
index 22e7ab091e..1631b4878d 100644
--- a/api/src/backend/api/tests/test_views.py
+++ b/api/src/backend/api/tests/test_views.py
@@ -1342,7 +1342,11 @@ class TestProviderViewSet:
response = authenticated_client.post(
reverse("provider-list"), data=provider_json_payload, format="json"
)
- assert response.status_code == status.HTTP_400_BAD_REQUEST
+ assert response.status_code == status.HTTP_409_CONFLICT
+ error = response.json()["errors"][0]
+ assert error["detail"] == "Provider already exists."
+ assert error["code"] == "conflict"
+ assert error["source"]["pointer"] == "/data/attributes/uid"
mock_delete_task.reset_mock()
mock_delete_task.return_value = task_mock
diff --git a/api/src/backend/api/v1/serializers.py b/api/src/backend/api/v1/serializers.py
index 7c4bb41ca5..3ed623d365 100644
--- a/api/src/backend/api/v1/serializers.py
+++ b/api/src/backend/api/v1/serializers.py
@@ -6,6 +6,7 @@ from django.conf import settings
from django.contrib.auth import authenticate
from django.contrib.auth.models import update_last_login
from django.contrib.auth.password_validation import validate_password
+from django.core.exceptions import ValidationError as DjangoValidationError
from django.db import IntegrityError
from drf_spectacular.utils import extend_schema_field
from jwt.exceptions import InvalidKeyError
@@ -959,6 +960,26 @@ class ProviderCreateSerializer(RLSSerializer, BaseWriteSerializer):
},
}
+ def create(self, validated_data):
+ try:
+ return super().create(validated_data)
+ except DjangoValidationError as e:
+ if "unique_provider_uids" in str(e):
+ raise ConflictException(
+ detail="Provider already exists.",
+ pointer="/data/attributes/uid",
+ )
+ raise
+ except IntegrityError as e:
+ # Handle race conditions where the unique constraint is enforced at the DB level
+ # after validation has already passed.
+ if "unique_provider_uids" in str(e):
+ raise ConflictException(
+ detail="Provider already exists.",
+ pointer="/data/attributes/uid",
+ )
+ raise
+
class ProviderUpdateSerializer(BaseWriteSerializer):
"""
From 0b461233c1871ecc550a877c3df75534cb231230 Mon Sep 17 00:00:00 2001
From: Eran Cohen
Date: Tue, 10 Mar 2026 13:12:54 +0200
Subject: [PATCH 010/108] feat(iam): Add trusted IP configurable option to
reduce false positives in 'opensearch' check (#8631)
Co-authored-by: Daniel Barranquero
---
.../cli/tutorials/configuration_file.mdx | 1 +
prowler/CHANGELOG.md | 1 +
prowler/config/config.yaml | 5 +
.../providers/aws/services/iam/lib/policy.py | 56 +++++++++++
...service_domains_not_publicly_accessible.py | 5 +-
tests/config/config_test.py | 2 +
tests/config/fixtures/config.yaml | 5 +
tests/config/fixtures/config_old.yaml | 5 +
.../aws/services/iam/lib/policy_test.py | 86 ++++++++++++++++
...ce_domains_not_publicly_accessible_test.py | 97 +++++++++++++++++++
10 files changed, 262 insertions(+), 1 deletion(-)
diff --git a/docs/user-guide/cli/tutorials/configuration_file.mdx b/docs/user-guide/cli/tutorials/configuration_file.mdx
index e5039bff21..923bef7ce1 100644
--- a/docs/user-guide/cli/tutorials/configuration_file.mdx
+++ b/docs/user-guide/cli/tutorials/configuration_file.mdx
@@ -73,6 +73,7 @@ The following list includes all the AWS checks with configurable variables that
| `ssm_documents_set_as_public` | `trusted_account_ids` | List of Strings |
| `vpc_endpoint_connections_trust_boundaries` | `trusted_account_ids` | List of Strings |
| `vpc_endpoint_services_allowed_principals_trust_boundaries` | `trusted_account_ids` | List of Strings |
+| `opensearch_service_domains_not_publicly_accessible` | `trusted_ips` | List of Strings |
## Azure
diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md
index 90e0e86103..925062b178 100644
--- a/prowler/CHANGELOG.md
+++ b/prowler/CHANGELOG.md
@@ -8,6 +8,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- `entra_conditional_access_policy_approved_client_app_required_for_mobile` check for m365 provider [(#10216)](https://github.com/prowler-cloud/prowler/pull/10216)
- `entra_conditional_access_policy_compliant_device_hybrid_joined_device_mfa_required` check for M365 provider [(#10197)](https://github.com/prowler-cloud/prowler/pull/10197)
+- Add `trusted_ips` configurable option to `opensearch_service_domains_not_publicly_accessible` check to reduce false positives on IP-restricted policies [(#8631)](https://github.com/prowler-cloud/prowler/pull/8631)
### 🔄 Changed
diff --git a/prowler/config/config.yaml b/prowler/config/config.yaml
index 4682afc7af..dcff0a6169 100644
--- a/prowler/config/config.yaml
+++ b/prowler/config/config.yaml
@@ -72,6 +72,11 @@ aws:
# trusted_account_ids : ["123456789012", "098765432109", "678901234567"]
trusted_account_ids: []
+ # AWS OpenSearch Configuration (opensearch_service_domains_not_publicly_accessible)
+ # Trusted IP addresses or CIDR ranges that should not be considered as public access, e.g.
+ # trusted_ips: ["1.2.3.4", "10.0.0.0/8"]
+ trusted_ips: []
+
# AWS Cloudwatch Configuration
# aws.cloudwatch_log_group_retention_policy_specific_days_enabled --> by default is 365 days
log_group_retention_days: 365
diff --git a/prowler/providers/aws/services/iam/lib/policy.py b/prowler/providers/aws/services/iam/lib/policy.py
index 8442fefa66..d8806f280b 100644
--- a/prowler/providers/aws/services/iam/lib/policy.py
+++ b/prowler/providers/aws/services/iam/lib/policy.py
@@ -380,6 +380,56 @@ def is_condition_restricting_from_private_ip(condition_statement: dict) -> bool:
return is_from_private_ip
+def is_condition_restricting_to_trusted_ips(
+ condition_statement: dict, trusted_ips: list = None
+) -> bool:
+ """Check if the policy condition restricts access to trusted IP addresses.
+
+ Keyword arguments:
+ condition_statement -- The policy condition to check. For example:
+ {
+ "IpAddress": {
+ "aws:SourceIp": "X.X.X.X"
+ }
+ }
+ trusted_ips -- A list of trusted IP addresses or CIDR ranges.
+ """
+ if not trusted_ips:
+ return False
+
+ try:
+ CONDITION_OPERATOR = "IpAddress"
+ CONDITION_KEY = "aws:sourceip"
+
+ if condition_statement.get(CONDITION_OPERATOR, {}):
+ condition_statement[CONDITION_OPERATOR] = {
+ k.lower(): v for k, v in condition_statement[CONDITION_OPERATOR].items()
+ }
+
+ if condition_statement[CONDITION_OPERATOR].get(CONDITION_KEY, ""):
+ if not isinstance(
+ condition_statement[CONDITION_OPERATOR][CONDITION_KEY], list
+ ):
+ condition_statement[CONDITION_OPERATOR][CONDITION_KEY] = [
+ condition_statement[CONDITION_OPERATOR][CONDITION_KEY]
+ ]
+
+ trusted_ips_set = {ip.lower() for ip in trusted_ips}
+ for ip in condition_statement[CONDITION_OPERATOR][CONDITION_KEY]:
+ if ip == "*" or ip == "0.0.0.0/0":
+ return False
+ if ip not in trusted_ips_set:
+ return False
+ return True
+
+ except Exception as error:
+ logger.error(
+ f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
+ )
+
+ return False
+
+
# TODO: Add logic for deny statements
def is_policy_public(
policy: dict,
@@ -388,6 +438,7 @@ def is_policy_public(
not_allowed_actions: list = [],
check_cross_service_confused_deputy=False,
trusted_account_ids: list = None,
+ trusted_ips: list = None,
) -> bool:
"""
Check if the policy allows public access to the resource.
@@ -399,6 +450,7 @@ def is_policy_public(
not_allowed_actions (list): List of actions that are not allowed, default: []. If not_allowed_actions is empty, the function will not consider the actions in the policy.
check_cross_service_confused_deputy (bool): If the policy is checked for cross-service confused deputy, default: False
trusted_account_ids (list): A list of trusted accound ids to reduce false positives on cross-account checks
+ trusted_ips (list): A list of trusted IP addresses or CIDR ranges to reduce false positives on IP-based checks
Returns:
bool: True if the policy allows public access, False otherwise
"""
@@ -511,6 +563,10 @@ def is_policy_public(
and not is_condition_restricting_from_private_ip(
statement.get("Condition", {})
)
+ and not is_condition_restricting_to_trusted_ips(
+ statement.get("Condition", {}),
+ trusted_ips,
+ )
)
if is_public:
break
diff --git a/prowler/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible.py b/prowler/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible.py
index 19a890620b..d02ed12a5a 100644
--- a/prowler/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible.py
+++ b/prowler/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible.py
@@ -8,6 +8,7 @@ from prowler.providers.aws.services.opensearch.opensearch_client import (
class opensearch_service_domains_not_publicly_accessible(Check):
def execute(self):
findings = []
+ trusted_ips = opensearch_client.audit_config.get("trusted_ips", [])
for domain in opensearch_client.opensearch_domains.values():
report = Check_Report_AWS(metadata=self.metadata(), resource=domain)
report.status = "PASS"
@@ -18,7 +19,9 @@ class opensearch_service_domains_not_publicly_accessible(Check):
if domain.vpc_id:
report.status_extended = f"Opensearch domain {domain.name} is in a VPC, then it is not publicly accessible."
elif domain.access_policy is not None and is_policy_public(
- domain.access_policy, opensearch_client.audited_account
+ domain.access_policy,
+ opensearch_client.audited_account,
+ trusted_ips=trusted_ips,
):
report.status = "FAIL"
report.status_extended = f"Opensearch domain {domain.name} is publicly accessible via access policy."
diff --git a/tests/config/config_test.py b/tests/config/config_test.py
index f67f3fda35..08b1f7d5e0 100644
--- a/tests/config/config_test.py
+++ b/tests/config/config_test.py
@@ -31,6 +31,7 @@ old_config_aws = {
"ec2_allowed_interface_types": ["api_gateway_managed", "vpc_endpoint"],
"ec2_allowed_instance_owners": ["amazon-elb"],
"trusted_account_ids": [],
+ "trusted_ips": [],
"log_group_retention_days": 365,
"max_idle_disconnect_timeout_in_seconds": 600,
"max_disconnect_timeout_in_seconds": 300,
@@ -95,6 +96,7 @@ config_aws = {
"fargate_linux_latest_version": "1.4.0",
"fargate_windows_latest_version": "1.0.0",
"trusted_account_ids": [],
+ "trusted_ips": [],
"log_group_retention_days": 365,
"max_idle_disconnect_timeout_in_seconds": 600,
"max_disconnect_timeout_in_seconds": 300,
diff --git a/tests/config/fixtures/config.yaml b/tests/config/fixtures/config.yaml
index 44e826d382..1b63e2387f 100644
--- a/tests/config/fixtures/config.yaml
+++ b/tests/config/fixtures/config.yaml
@@ -72,6 +72,11 @@ aws:
# trusted_account_ids : ["123456789012", "098765432109", "678901234567"]
trusted_account_ids: []
+ # AWS OpenSearch Configuration (opensearch_service_domains_not_publicly_accessible)
+ # Trusted IP addresses or CIDR ranges that should not be considered as public access, e.g.
+ # trusted_ips: ["1.2.3.4", "10.0.0.0/8"]
+ trusted_ips: []
+
# AWS Cloudwatch Configuration
# aws.cloudwatch_log_group_retention_policy_specific_days_enabled --> by default is 365 days
log_group_retention_days: 365
diff --git a/tests/config/fixtures/config_old.yaml b/tests/config/fixtures/config_old.yaml
index cbd3bf4fa0..33220e1246 100644
--- a/tests/config/fixtures/config_old.yaml
+++ b/tests/config/fixtures/config_old.yaml
@@ -25,6 +25,11 @@ ec2_allowed_instance_owners:
# trusted_account_ids : ["123456789012", "098765432109", "678901234567"]
trusted_account_ids: []
+# AWS OpenSearch Configuration (opensearch_service_domains_not_publicly_accessible)
+# Trusted IP addresses or CIDR ranges that should not be considered as public access, e.g.
+# trusted_ips: ["1.2.3.4", "10.0.0.0/8"]
+trusted_ips: []
+
# AWS Cloudwatch Configuration
# aws.cloudwatch_log_group_retention_policy_specific_days_enabled --> by default is 365 days
log_group_retention_days: 365
diff --git a/tests/providers/aws/services/iam/lib/policy_test.py b/tests/providers/aws/services/iam/lib/policy_test.py
index fe25b4ca0f..afea8ca658 100644
--- a/tests/providers/aws/services/iam/lib/policy_test.py
+++ b/tests/providers/aws/services/iam/lib/policy_test.py
@@ -13,6 +13,7 @@ from prowler.providers.aws.services.iam.lib.policy import (
is_condition_block_restrictive_organization,
is_condition_block_restrictive_sns_endpoint,
is_condition_restricting_from_private_ip,
+ is_condition_restricting_to_trusted_ips,
is_policy_public,
)
@@ -1982,6 +1983,49 @@ class Test_Policy:
}
assert not is_condition_restricting_from_private_ip(condition_from_invalid_ip)
+ def test_is_condition_restricting_to_trusted_ips_no_trusted_ips(self):
+ condition = {"IpAddress": {"aws:SourceIp": "1.2.3.4"}}
+ assert not is_condition_restricting_to_trusted_ips(condition)
+
+ def test_is_condition_restricting_to_trusted_ips_empty_trusted_ips(self):
+ condition = {"IpAddress": {"aws:SourceIp": "1.2.3.4"}}
+ assert not is_condition_restricting_to_trusted_ips(condition, [])
+
+ def test_is_condition_restricting_to_trusted_ips_matching(self):
+ condition = {"IpAddress": {"aws:SourceIp": "1.2.3.4"}}
+ assert is_condition_restricting_to_trusted_ips(condition, ["1.2.3.4"])
+
+ def test_is_condition_restricting_to_trusted_ips_not_matching(self):
+ condition = {"IpAddress": {"aws:SourceIp": "5.6.7.8"}}
+ assert not is_condition_restricting_to_trusted_ips(condition, ["1.2.3.4"])
+
+ def test_is_condition_restricting_to_trusted_ips_wildcard(self):
+ condition = {"IpAddress": {"aws:SourceIp": "*"}}
+ assert not is_condition_restricting_to_trusted_ips(condition, ["1.2.3.4"])
+
+ def test_is_condition_restricting_to_trusted_ips_open_cidr(self):
+ condition = {"IpAddress": {"aws:SourceIp": "0.0.0.0/0"}}
+ assert not is_condition_restricting_to_trusted_ips(condition, ["1.2.3.4"])
+
+ def test_is_condition_restricting_to_trusted_ips_multiple_ips_all_trusted(self):
+ condition = {"IpAddress": {"aws:SourceIp": ["1.2.3.4", "5.6.7.8"]}}
+ assert is_condition_restricting_to_trusted_ips(
+ condition, ["1.2.3.4", "5.6.7.8"]
+ )
+
+ def test_is_condition_restricting_to_trusted_ips_multiple_ips_partial_trusted(self):
+ condition = {"IpAddress": {"aws:SourceIp": ["1.2.3.4", "9.9.9.9"]}}
+ assert not is_condition_restricting_to_trusted_ips(
+ condition, ["1.2.3.4", "5.6.7.8"]
+ )
+
+ def test_is_condition_restricting_to_trusted_ips_cidr_range(self):
+ condition = {"IpAddress": {"aws:SourceIp": "10.0.0.0/8"}}
+ assert is_condition_restricting_to_trusted_ips(condition, ["10.0.0.0/8"])
+
+ def test_is_condition_restricting_to_trusted_ips_no_condition(self):
+ assert not is_condition_restricting_to_trusted_ips({}, ["1.2.3.4"])
+
def test_is_policy_public_(self):
policy = {
"Statement": [
@@ -2271,6 +2315,48 @@ class Test_Policy:
}
assert is_policy_public(policy, TRUSTED_AWS_ACCOUNT_NUMBER)
+ def test_is_policy_public_with_trusted_ips(self):
+ policy = {
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Principal": {"AWS": "*"},
+ "Action": ["*"],
+ "Condition": {
+ "IpAddress": {"aws:SourceIp": ["1.2.3.4", "5.6.7.8"]}
+ },
+ "Resource": "*",
+ }
+ ],
+ }
+ assert not is_policy_public(
+ policy,
+ TRUSTED_AWS_ACCOUNT_NUMBER,
+ trusted_ips=["1.2.3.4", "5.6.7.8"],
+ )
+
+ def test_is_policy_public_with_trusted_ips_partial_match(self):
+ policy = {
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Principal": {"AWS": "*"},
+ "Action": ["*"],
+ "Condition": {
+ "IpAddress": {"aws:SourceIp": ["1.2.3.4", "9.9.9.9"]}
+ },
+ "Resource": "*",
+ }
+ ],
+ }
+ assert is_policy_public(
+ policy,
+ TRUSTED_AWS_ACCOUNT_NUMBER,
+ trusted_ips=["1.2.3.4", "5.6.7.8"],
+ )
+
def test_check_admin_access(self):
policy = {
"Version": "2012-10-17",
diff --git a/tests/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible_test.py b/tests/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible_test.py
index cdbb87c1a8..8003e9a1b7 100644
--- a/tests/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible_test.py
+++ b/tests/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible_test.py
@@ -74,6 +74,19 @@ policy_data_source_whole_internet = {
],
}
+policy_data_trusted_ip = {
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Principal": {"AWS": "*"},
+ "Action": ["es:ESHttp*"],
+ "Condition": {"IpAddress": {"aws:SourceIp": ["1.2.3.4", "5.6.7.8"]}},
+ "Resource": f"arn:aws:es:us-west-2:{AWS_ACCOUNT_NUMBER}:domain/{domain_name}/*",
+ }
+ ],
+}
+
class Test_opensearch_service_domains_not_publicly_accessible:
@mock_aws
@@ -304,3 +317,87 @@ class Test_opensearch_service_domains_not_publicly_accessible:
assert result[0].resource_arn == domain_arn
assert result[0].region == AWS_REGION_US_WEST_2
assert result[0].resource_tags == []
+
+ @mock_aws
+ def test_policy_data_not_restricted_with_trusted_ips(self):
+ opensearch_client = client("opensearch", region_name=AWS_REGION_US_WEST_2)
+ domain_arn = opensearch_client.create_domain(
+ DomainName=domain_name,
+ AccessPolicies=dumps(policy_data_trusted_ip),
+ )["DomainStatus"]["ARN"]
+
+ aws_provider = set_mocked_aws_provider([AWS_REGION_US_WEST_2])
+ aws_provider._audit_config = {"trusted_ips": ["1.2.3.4", "5.6.7.8"]}
+
+ from prowler.providers.aws.services.opensearch.opensearch_service import (
+ OpenSearchService,
+ )
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=aws_provider,
+ ),
+ mock.patch(
+ "prowler.providers.aws.services.opensearch.opensearch_service_domains_not_publicly_accessible.opensearch_service_domains_not_publicly_accessible.opensearch_client",
+ new=OpenSearchService(aws_provider),
+ ),
+ ):
+ from prowler.providers.aws.services.opensearch.opensearch_service_domains_not_publicly_accessible.opensearch_service_domains_not_publicly_accessible import (
+ opensearch_service_domains_not_publicly_accessible,
+ )
+
+ check = opensearch_service_domains_not_publicly_accessible()
+ result = check.execute()
+ assert len(result) == 1
+ assert result[0].status == "PASS"
+ assert (
+ result[0].status_extended
+ == f"Opensearch domain {domain_name} is not publicly accessible."
+ )
+ assert result[0].resource_id == domain_name
+ assert result[0].resource_arn == domain_arn
+ assert result[0].region == AWS_REGION_US_WEST_2
+ assert result[0].resource_tags == []
+
+ @mock_aws
+ def test_policy_data_not_restricted_with_trusted_ips_partial_match(self):
+ opensearch_client = client("opensearch", region_name=AWS_REGION_US_WEST_2)
+ domain_arn = opensearch_client.create_domain(
+ DomainName=domain_name,
+ AccessPolicies=dumps(policy_data_trusted_ip),
+ )["DomainStatus"]["ARN"]
+
+ aws_provider = set_mocked_aws_provider([AWS_REGION_US_WEST_2])
+ aws_provider._audit_config = {"trusted_ips": ["1.2.3.4"]}
+
+ from prowler.providers.aws.services.opensearch.opensearch_service import (
+ OpenSearchService,
+ )
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=aws_provider,
+ ),
+ mock.patch(
+ "prowler.providers.aws.services.opensearch.opensearch_service_domains_not_publicly_accessible.opensearch_service_domains_not_publicly_accessible.opensearch_client",
+ new=OpenSearchService(aws_provider),
+ ),
+ ):
+ from prowler.providers.aws.services.opensearch.opensearch_service_domains_not_publicly_accessible.opensearch_service_domains_not_publicly_accessible import (
+ opensearch_service_domains_not_publicly_accessible,
+ )
+
+ check = opensearch_service_domains_not_publicly_accessible()
+ result = check.execute()
+ assert len(result) == 1
+ assert result[0].status == "FAIL"
+ assert (
+ result[0].status_extended
+ == f"Opensearch domain {domain_name} is publicly accessible via access policy."
+ )
+ assert result[0].resource_id == domain_name
+ assert result[0].resource_arn == domain_arn
+ assert result[0].region == AWS_REGION_US_WEST_2
+ assert result[0].resource_tags == []
From 344a098ddc41d54528a59db315f6fbadb19073d7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Adri=C3=A1n=20Pe=C3=B1a?=
Date: Tue, 10 Mar 2026 12:20:25 +0100
Subject: [PATCH 011/108] docs: document required permissions for mutelist
features (#10294)
---
docs/user-guide/tutorials/prowler-app-mute-findings.mdx | 5 +++++
docs/user-guide/tutorials/prowler-app-simple-mutelist.mdx | 5 +++++
2 files changed, 10 insertions(+)
diff --git a/docs/user-guide/tutorials/prowler-app-mute-findings.mdx b/docs/user-guide/tutorials/prowler-app-mute-findings.mdx
index 64e9a0c8f4..f46cb308b4 100644
--- a/docs/user-guide/tutorials/prowler-app-mute-findings.mdx
+++ b/docs/user-guide/tutorials/prowler-app-mute-findings.mdx
@@ -24,6 +24,11 @@ Advanced Mutelist enables users to create powerful, pattern-based muting rules u
## Prerequisites
+
+Advanced Mutelist requires the **Manage Account** permission. See [RBAC Administrative Permissions](/user-guide/tutorials/prowler-app-rbac#rbac-administrative-permissions) for details.
+
+
+
Before muting findings, ensure:
- Valid access to Prowler App with appropriate permissions
diff --git a/docs/user-guide/tutorials/prowler-app-simple-mutelist.mdx b/docs/user-guide/tutorials/prowler-app-simple-mutelist.mdx
index d6226f7c5f..ca0a487978 100644
--- a/docs/user-guide/tutorials/prowler-app-simple-mutelist.mdx
+++ b/docs/user-guide/tutorials/prowler-app-simple-mutelist.mdx
@@ -23,6 +23,11 @@ Simple Mutelist creates rules based on the finding's unique identifier (UID). Fo
+
+Simple Mutelist requires the **Manage Scans** permission. See [RBAC Administrative Permissions](/user-guide/tutorials/prowler-app-rbac#rbac-administrative-permissions) for details.
+
+
+
## Accessing the Mutelist Page
To access the Mutelist page:
From c4d692f77b2a69a368f7551b4ac80fd68c2f19b7 Mon Sep 17 00:00:00 2001
From: Michael Wentz <108200175+m-wentz@users.noreply.github.com>
Date: Tue, 10 Mar 2026 05:56:00 -0600
Subject: [PATCH 012/108] feat(guardduty): add org-wide delegated admin check
across all regions (#9867)
Co-authored-by: Daniel Barranquero
---
prowler/CHANGELOG.md | 1 +
.../__init__.py | 0
...ed_admin_enabled_all_regions.metadata.json | 44 ++++
...uty_delegated_admin_enabled_all_regions.py | 76 ++++++
.../services/guardduty/guardduty_service.py | 95 ++++++-
...elegated_admin_enabled_all_regions_test.py | 233 ++++++++++++++++++
6 files changed, 448 insertions(+), 1 deletion(-)
create mode 100644 prowler/providers/aws/services/guardduty/guardduty_delegated_admin_enabled_all_regions/__init__.py
create mode 100644 prowler/providers/aws/services/guardduty/guardduty_delegated_admin_enabled_all_regions/guardduty_delegated_admin_enabled_all_regions.metadata.json
create mode 100644 prowler/providers/aws/services/guardduty/guardduty_delegated_admin_enabled_all_regions/guardduty_delegated_admin_enabled_all_regions.py
create mode 100644 tests/providers/aws/services/guardduty/guardduty_delegated_admin_enabled_all_regions/guardduty_delegated_admin_enabled_all_regions_test.py
diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md
index 925062b178..f64f8f8fde 100644
--- a/prowler/CHANGELOG.md
+++ b/prowler/CHANGELOG.md
@@ -9,6 +9,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- `entra_conditional_access_policy_approved_client_app_required_for_mobile` check for m365 provider [(#10216)](https://github.com/prowler-cloud/prowler/pull/10216)
- `entra_conditional_access_policy_compliant_device_hybrid_joined_device_mfa_required` check for M365 provider [(#10197)](https://github.com/prowler-cloud/prowler/pull/10197)
- Add `trusted_ips` configurable option to `opensearch_service_domains_not_publicly_accessible` check to reduce false positives on IP-restricted policies [(#8631)](https://github.com/prowler-cloud/prowler/pull/8631)
+- `guardduty_delegated_admin_enabled_all_regions` check for AWS provider [(#9867)](https://github.com/prowler-cloud/prowler/pull/9867)
### 🔄 Changed
diff --git a/prowler/providers/aws/services/guardduty/guardduty_delegated_admin_enabled_all_regions/__init__.py b/prowler/providers/aws/services/guardduty/guardduty_delegated_admin_enabled_all_regions/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/prowler/providers/aws/services/guardduty/guardduty_delegated_admin_enabled_all_regions/guardduty_delegated_admin_enabled_all_regions.metadata.json b/prowler/providers/aws/services/guardduty/guardduty_delegated_admin_enabled_all_regions/guardduty_delegated_admin_enabled_all_regions.metadata.json
new file mode 100644
index 0000000000..b08114efa8
--- /dev/null
+++ b/prowler/providers/aws/services/guardduty/guardduty_delegated_admin_enabled_all_regions/guardduty_delegated_admin_enabled_all_regions.metadata.json
@@ -0,0 +1,44 @@
+{
+ "Provider": "aws",
+ "CheckID": "guardduty_delegated_admin_enabled_all_regions",
+ "CheckTitle": "GuardDuty has delegated admin configured and is enabled in all regions with organization auto-enable",
+ "CheckType": [
+ "Software and Configuration Checks/AWS Security Best Practices",
+ "Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices"
+ ],
+ "ServiceName": "guardduty",
+ "SubServiceName": "",
+ "ResourceIdTemplate": "",
+ "Severity": "high",
+ "ResourceType": "AwsGuardDutyDetector",
+ "ResourceGroup": "security",
+ "Description": "**Amazon GuardDuty** has a delegated administrator configured at the organization level, detectors are enabled in all opted-in regions, and organization auto-enable is active for new member accounts.",
+ "Risk": "Without org-wide **Amazon GuardDuty** configuration, gaps can occur where **GuardDuty** may be enabled in some regions but not others. Delegated admin may not be set consistently, and new accounts may not be automatically enrolled. This fragments **threat visibility**, delays **incident response**, and allows adversaries to exploit unmonitored regions or accounts for **lateral movement**, **persistence**, and **data exfiltration**.",
+ "RelatedUrl": "",
+ "AdditionalURLs": [
+ "https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_organizations.html",
+ "https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_multi-account.html",
+ "https://docs.aws.amazon.com/organizations/latest/userguide/services-that-can-integrate-guardduty.html"
+ ],
+ "Remediation": {
+ "Code": {
+ "CLI": "aws guardduty enable-organization-admin-account --admin-account-id && aws guardduty update-organization-configuration --detector-id --auto-enable-organization-members ALL",
+ "NativeIaC": "",
+ "Other": "1. Sign in to the AWS Organizations management account\n2. Open the AWS Organizations console\n3. Navigate to Services > Amazon GuardDuty\n4. Click Register delegated administrator and enter the security account ID\n5. Switch to the delegated admin account\n6. In GuardDuty console, go to Settings > Accounts\n7. Enable auto-enable for all organization members\n8. Repeat detector enablement for all opted-in regions",
+ "Terraform": ""
+ },
+ "Recommendation": {
+ "Text": "Configure a **delegated administrator** for GuardDuty via AWS Organizations. Enable GuardDuty detectors in **all opted-in regions** and configure **auto-enable** to automatically enroll new member accounts. This ensures consistent threat detection coverage across the entire organization.",
+ "Url": "https://hub.prowler.com/check/guardduty_delegated_admin_enabled_all_regions"
+ }
+ },
+ "Categories": [
+ "forensics-ready"
+ ],
+ "DependsOn": [],
+ "RelatedTo": [
+ "guardduty_is_enabled",
+ "guardduty_centrally_managed"
+ ],
+ "Notes": "This check requires execution from the organization management account or delegated administrator account to access organization-level APIs."
+}
diff --git a/prowler/providers/aws/services/guardduty/guardduty_delegated_admin_enabled_all_regions/guardduty_delegated_admin_enabled_all_regions.py b/prowler/providers/aws/services/guardduty/guardduty_delegated_admin_enabled_all_regions/guardduty_delegated_admin_enabled_all_regions.py
new file mode 100644
index 0000000000..23065abd2b
--- /dev/null
+++ b/prowler/providers/aws/services/guardduty/guardduty_delegated_admin_enabled_all_regions/guardduty_delegated_admin_enabled_all_regions.py
@@ -0,0 +1,76 @@
+from prowler.lib.check.models import Check, Check_Report_AWS
+from prowler.providers.aws.services.guardduty.guardduty_client import guardduty_client
+
+
+class guardduty_delegated_admin_enabled_all_regions(Check):
+ """Ensure GuardDuty has a delegated admin and is enabled in all regions.
+
+ This check verifies that:
+ 1. A delegated administrator account is configured for GuardDuty
+ 2. GuardDuty detectors are enabled in each region
+ 3. Organization auto-enable is configured for new member accounts
+ """
+
+ def execute(self) -> list[Check_Report_AWS]:
+ """Execute the check logic.
+
+ Returns:
+ A list of reports containing the result of the check for each region.
+ """
+ findings = []
+
+ # Build a set of regions that have an organization admin account configured
+ regions_with_admin = {
+ admin.region
+ for admin in guardduty_client.organization_admin_accounts
+ if admin.admin_status == "ENABLED"
+ }
+
+ for detector in guardduty_client.detectors:
+ report = Check_Report_AWS(metadata=self.metadata(), resource=detector)
+
+ # Check if this region has a delegated admin
+ has_delegated_admin = detector.region in regions_with_admin
+
+ # Check if detector is enabled
+ detector_enabled = detector.enabled_in_account and detector.status
+
+ # Check if auto-enable is configured for organization members
+ auto_enable_configured = detector.organization_auto_enable_members in (
+ "NEW",
+ "ALL",
+ )
+
+ # Determine overall status
+ issues = []
+ if not has_delegated_admin:
+ issues.append("no delegated administrator configured")
+ if not detector_enabled:
+ issues.append("detector not enabled")
+ if not auto_enable_configured and detector.organization_config_available:
+ # Only report auto-enable issue if org config data is available
+ issues.append("organization auto-enable not configured")
+
+ if issues:
+ report.status = "FAIL"
+ report.status_extended = (
+ f"GuardDuty in region {detector.region} has issues: "
+ f"{', '.join(issues)}."
+ )
+ else:
+ report.status = "PASS"
+ report.status_extended = (
+ f"GuardDuty in region {detector.region} has delegated admin "
+ f"configured with detector enabled and organization auto-enable active."
+ )
+
+ # Support muting non-default regions if configured
+ if report.status == "FAIL" and (
+ guardduty_client.audit_config.get("mute_non_default_regions", False)
+ and detector.region != guardduty_client.region
+ ):
+ report.muted = True
+
+ findings.append(report)
+
+ return findings
diff --git a/prowler/providers/aws/services/guardduty/guardduty_service.py b/prowler/providers/aws/services/guardduty/guardduty_service.py
index c267771209..bde8725454 100644
--- a/prowler/providers/aws/services/guardduty/guardduty_service.py
+++ b/prowler/providers/aws/services/guardduty/guardduty_service.py
@@ -1,5 +1,6 @@
from typing import Optional
+from botocore.exceptions import ClientError
from pydantic.v1 import BaseModel
from prowler.lib.logger import logger
@@ -12,12 +13,17 @@ class GuardDuty(AWSService):
# Call AWSService's __init__
super().__init__(__class__.__name__, provider)
self.detectors = []
+ self.organization_admin_accounts = []
self.__threading_call__(self._list_detectors)
self.__threading_call__(self._get_detector, self.detectors)
self._list_findings()
self._list_members()
self._get_administrator_account()
self._list_tags_for_resource()
+ self.__threading_call__(self._list_organization_admin_accounts)
+ self.__threading_call__(
+ self._describe_organization_configuration, self.detectors
+ )
def _list_detectors(self, regional_client):
logger.info("GuardDuty - listing detectors...")
@@ -216,13 +222,97 @@ class GuardDuty(AWSService):
f"{error.__class__.__name__}:{error.__traceback__.tb_lineno} -- {error}"
)
+ def _list_organization_admin_accounts(self, regional_client):
+ """List GuardDuty delegated administrator accounts for the organization.
+
+ This API is only available to the organization management account or
+ a delegated administrator account.
+ """
+ logger.info("GuardDuty - listing organization admin accounts...")
+ try:
+ paginator = regional_client.get_paginator(
+ "list_organization_admin_accounts"
+ )
+ for page in paginator.paginate():
+ for admin in page.get("AdminAccounts", []):
+ admin_account = OrganizationAdminAccount(
+ admin_account_id=admin.get("AdminAccountId"),
+ admin_status=admin.get("AdminStatus"),
+ region=regional_client.region,
+ )
+ # Avoid duplicates across regions for the same admin account
+ if not any(
+ existing.admin_account_id == admin_account.admin_account_id
+ and existing.region == admin_account.region
+ for existing in self.organization_admin_accounts
+ ):
+ self.organization_admin_accounts.append(admin_account)
+ except ClientError as error:
+ if error.response["Error"]["Code"] in (
+ "AccessDeniedException",
+ "BadRequestException",
+ ):
+ logger.warning(
+ f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
+ )
+ else:
+ logger.error(
+ f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
+ )
+ except Exception as error:
+ logger.error(
+ f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
+ )
+
+ def _describe_organization_configuration(self, detector):
+ """Describe the organization configuration for a GuardDuty detector.
+
+ This provides information about auto-enable settings for the organization.
+ """
+ logger.info("GuardDuty - describing organization configuration...")
+ try:
+ if detector.id and detector.enabled_in_account:
+ regional_client = self.regional_clients[detector.region]
+ org_config = regional_client.describe_organization_configuration(
+ DetectorId=detector.id
+ )
+ detector.organization_auto_enable_members = org_config.get(
+ "AutoEnableOrganizationMembers", "NONE"
+ )
+ detector.organization_config_available = True
+ except ClientError as error:
+ if error.response["Error"]["Code"] in (
+ "AccessDeniedException",
+ "BadRequestException",
+ ):
+ # Expected when not running from management or delegated admin account
+ logger.warning(
+ f"{detector.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
+ )
+ else:
+ logger.error(
+ f"{detector.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
+ )
+ except Exception as error:
+ logger.error(
+ f"{detector.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
+ )
+
+
+class OrganizationAdminAccount(BaseModel):
+ """Represents a GuardDuty delegated administrator account."""
+
+ admin_account_id: str
+ admin_status: str # ENABLED or DISABLE_IN_PROGRESS
+ region: str
+
class Detector(BaseModel):
id: str
arn: str
region: str
enabled_in_account: bool
- status: bool = None
+ status: Optional[bool] = None
findings: list = []
member_accounts: list = []
administrator_account: str = None
@@ -233,3 +323,6 @@ class Detector(BaseModel):
eks_runtime_monitoring: bool = False
lambda_protection: bool = False
ec2_malware_protection: bool = False
+ # Organization configuration fields
+ organization_auto_enable_members: str = "NONE" # NEW, ALL, or NONE
+ organization_config_available: bool = False
diff --git a/tests/providers/aws/services/guardduty/guardduty_delegated_admin_enabled_all_regions/guardduty_delegated_admin_enabled_all_regions_test.py b/tests/providers/aws/services/guardduty/guardduty_delegated_admin_enabled_all_regions/guardduty_delegated_admin_enabled_all_regions_test.py
new file mode 100644
index 0000000000..d171aa2473
--- /dev/null
+++ b/tests/providers/aws/services/guardduty/guardduty_delegated_admin_enabled_all_regions/guardduty_delegated_admin_enabled_all_regions_test.py
@@ -0,0 +1,233 @@
+from unittest.mock import patch
+
+import botocore
+from boto3 import client
+from moto import mock_aws
+
+from tests.providers.aws.utils import (
+ AWS_ACCOUNT_NUMBER,
+ AWS_REGION_EU_WEST_1,
+ set_mocked_aws_provider,
+)
+
+orig = botocore.client.BaseClient._make_api_call
+
+
+def mock_make_api_call_org_admin_and_config(self, operation_name, api_params):
+ """Mock organization admin accounts and configuration APIs."""
+ if operation_name == "ListOrganizationAdminAccounts":
+ return {
+ "AdminAccounts": [
+ {
+ "AdminAccountId": "123456789012",
+ "AdminStatus": "ENABLED",
+ }
+ ]
+ }
+ if operation_name == "DescribeOrganizationConfiguration":
+ return {
+ "AutoEnableOrganizationMembers": "ALL",
+ }
+ return orig(self, operation_name, api_params)
+
+
+def mock_make_api_call_org_admin_no_auto_enable(self, operation_name, api_params):
+ """Mock organization admin configured but auto-enable disabled."""
+ if operation_name == "ListOrganizationAdminAccounts":
+ return {
+ "AdminAccounts": [
+ {
+ "AdminAccountId": "123456789012",
+ "AdminStatus": "ENABLED",
+ }
+ ]
+ }
+ if operation_name == "DescribeOrganizationConfiguration":
+ return {
+ "AutoEnableOrganizationMembers": "NONE",
+ }
+ return orig(self, operation_name, api_params)
+
+
+def mock_make_api_call_no_org_admin(self, operation_name, api_params):
+ """Mock no organization admin configured."""
+ if operation_name == "ListOrganizationAdminAccounts":
+ return {"AdminAccounts": []}
+ if operation_name == "DescribeOrganizationConfiguration":
+ return {
+ "AutoEnableOrganizationMembers": "NONE",
+ }
+ return orig(self, operation_name, api_params)
+
+
+class Test_guardduty_delegated_admin_enabled_all_regions:
+ @mock_aws
+ def test_no_detectors(self):
+ """Test when no GuardDuty detectors exist."""
+ aws_provider = set_mocked_aws_provider()
+
+ from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty
+
+ with (
+ patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=aws_provider,
+ ),
+ patch(
+ "prowler.providers.aws.services.guardduty.guardduty_delegated_admin_enabled_all_regions.guardduty_delegated_admin_enabled_all_regions.guardduty_client",
+ new=GuardDuty(aws_provider),
+ ),
+ ):
+ from prowler.providers.aws.services.guardduty.guardduty_delegated_admin_enabled_all_regions.guardduty_delegated_admin_enabled_all_regions import (
+ guardduty_delegated_admin_enabled_all_regions,
+ )
+
+ check = guardduty_delegated_admin_enabled_all_regions()
+ result = check.execute()
+
+ # Should have findings for each region (with unknown detectors)
+ assert len(result) > 0
+ # All should fail since no detectors are enabled
+ for finding in result:
+ assert finding.status == "FAIL"
+ assert "detector not enabled" in finding.status_extended
+
+ @patch(
+ "botocore.client.BaseClient._make_api_call",
+ new=mock_make_api_call_no_org_admin,
+ )
+ @mock_aws
+ def test_detector_enabled_no_delegated_admin(self):
+ """Test when detector is enabled but no delegated admin is configured."""
+ guardduty_client_boto = client("guardduty", region_name=AWS_REGION_EU_WEST_1)
+ detector_id = guardduty_client_boto.create_detector(Enable=True)["DetectorId"]
+
+ aws_provider = set_mocked_aws_provider()
+
+ from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty
+
+ with (
+ patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=aws_provider,
+ ),
+ patch(
+ "prowler.providers.aws.services.guardduty.guardduty_delegated_admin_enabled_all_regions.guardduty_delegated_admin_enabled_all_regions.guardduty_client",
+ new=GuardDuty(aws_provider),
+ ),
+ ):
+ from prowler.providers.aws.services.guardduty.guardduty_delegated_admin_enabled_all_regions.guardduty_delegated_admin_enabled_all_regions import (
+ guardduty_delegated_admin_enabled_all_regions,
+ )
+
+ check = guardduty_delegated_admin_enabled_all_regions()
+ result = check.execute()
+
+ # Find the result for our region
+ eu_west_1_result = None
+ for finding in result:
+ if finding.region == AWS_REGION_EU_WEST_1:
+ eu_west_1_result = finding
+ break
+
+ assert eu_west_1_result is not None
+ assert eu_west_1_result.status == "FAIL"
+ assert (
+ "no delegated administrator configured"
+ in eu_west_1_result.status_extended
+ )
+ assert eu_west_1_result.resource_id == detector_id
+
+ @patch(
+ "botocore.client.BaseClient._make_api_call",
+ new=mock_make_api_call_org_admin_no_auto_enable,
+ )
+ @mock_aws
+ def test_detector_enabled_with_admin_no_auto_enable(self):
+ """Test when detector is enabled with delegated admin but auto-enable is off."""
+ guardduty_client_boto = client("guardduty", region_name=AWS_REGION_EU_WEST_1)
+ detector_id = guardduty_client_boto.create_detector(Enable=True)["DetectorId"]
+
+ aws_provider = set_mocked_aws_provider()
+
+ from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty
+
+ with (
+ patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=aws_provider,
+ ),
+ patch(
+ "prowler.providers.aws.services.guardduty.guardduty_delegated_admin_enabled_all_regions.guardduty_delegated_admin_enabled_all_regions.guardduty_client",
+ new=GuardDuty(aws_provider),
+ ),
+ ):
+ from prowler.providers.aws.services.guardduty.guardduty_delegated_admin_enabled_all_regions.guardduty_delegated_admin_enabled_all_regions import (
+ guardduty_delegated_admin_enabled_all_regions,
+ )
+
+ check = guardduty_delegated_admin_enabled_all_regions()
+ result = check.execute()
+
+ # Find the result for our region
+ eu_west_1_result = None
+ for finding in result:
+ if finding.region == AWS_REGION_EU_WEST_1:
+ eu_west_1_result = finding
+ break
+
+ assert eu_west_1_result is not None
+ assert eu_west_1_result.status == "FAIL"
+ assert (
+ "organization auto-enable not configured"
+ in eu_west_1_result.status_extended
+ )
+ assert eu_west_1_result.resource_id == detector_id
+
+ @patch(
+ "botocore.client.BaseClient._make_api_call",
+ new=mock_make_api_call_org_admin_and_config,
+ )
+ @mock_aws
+ def test_detector_enabled_with_admin_and_auto_enable(self):
+ """Test when detector is enabled with delegated admin and auto-enable is on (PASS)."""
+ guardduty_client_boto = client("guardduty", region_name=AWS_REGION_EU_WEST_1)
+ detector_id = guardduty_client_boto.create_detector(Enable=True)["DetectorId"]
+
+ aws_provider = set_mocked_aws_provider()
+
+ from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty
+
+ with (
+ patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=aws_provider,
+ ),
+ patch(
+ "prowler.providers.aws.services.guardduty.guardduty_delegated_admin_enabled_all_regions.guardduty_delegated_admin_enabled_all_regions.guardduty_client",
+ new=GuardDuty(aws_provider),
+ ),
+ ):
+ from prowler.providers.aws.services.guardduty.guardduty_delegated_admin_enabled_all_regions.guardduty_delegated_admin_enabled_all_regions import (
+ guardduty_delegated_admin_enabled_all_regions,
+ )
+
+ check = guardduty_delegated_admin_enabled_all_regions()
+ result = check.execute()
+
+ # Find the result for our region
+ eu_west_1_result = None
+ for finding in result:
+ if finding.region == AWS_REGION_EU_WEST_1:
+ eu_west_1_result = finding
+ break
+
+ assert eu_west_1_result is not None
+ assert eu_west_1_result.status == "PASS"
+ assert "delegated admin configured" in eu_west_1_result.status_extended
+ assert "auto-enable active" in eu_west_1_result.status_extended
+ assert eu_west_1_result.resource_id == detector_id
+ assert (
+ eu_west_1_result.resource_arn
+ == f"arn:aws:guardduty:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:detector/{detector_id}"
+ )
From 3288a4a131ce81d01b22d05572bf3813a58922e4 Mon Sep 17 00:00:00 2001
From: Josema Camacho
Date: Tue, 10 Mar 2026 16:47:53 +0100
Subject: [PATCH 013/108] fix(api): add missing logging for Attack Paths query
execution and scan error handling (#10269)
---
api/CHANGELOG.md | 8 ++++
api/src/backend/api/v1/views.py | 44 +++++++++++++++++++
api/src/backend/config/custom_logging.py | 5 +++
.../backend/tasks/jobs/attack_paths/aws.py | 5 ++-
.../backend/tasks/jobs/attack_paths/scan.py | 12 ++---
5 files changed, 67 insertions(+), 7 deletions(-)
diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md
index cfaecfa2f2..9c3f278f79 100644
--- a/api/CHANGELOG.md
+++ b/api/CHANGELOG.md
@@ -10,6 +10,14 @@ All notable changes to the **Prowler API** are documented in this file.
---
+## [1.20.1] (Prowler UNRELEASED)
+
+### 🐞 Fixed
+
+- Attack Paths: Add missing logging for query execution and exception details in scan error handling [(#10269)](https://github.com/prowler-cloud/prowler/pull/10269)
+
+---
+
## [1.20.0] (Prowler v5.19.0)
### 🚀 Added
diff --git a/api/src/backend/api/v1/views.py b/api/src/backend/api/v1/views.py
index b23c589262..75b03e2623 100644
--- a/api/src/backend/api/v1/views.py
+++ b/api/src/backend/api/v1/views.py
@@ -3,6 +3,7 @@ import glob
import json
import logging
import os
+import time
from collections import defaultdict
from copy import deepcopy
@@ -2570,14 +2571,35 @@ class AttackPathsScanViewSet(BaseRLSViewSet):
provider_id,
)
+ start = time.monotonic()
graph = attack_paths_views_helpers.execute_query(
database_name,
query_definition,
parameters,
provider_id,
)
+ query_duration = time.monotonic() - start
graph_database.clear_cache(database_name)
+ result_nodes = len(graph.get("nodes", []))
+ result_relationships = len(graph.get("relationships", []))
+ logger.info(
+ "attack_paths_query_run",
+ extra={
+ "user_id": str(request.user.id),
+ "tenant_id": str(attack_paths_scan.provider.tenant_id),
+ "metadata": {
+ "query_id": query_definition.id,
+ "provider": query_definition.provider,
+ "scan_id": pk,
+ "provider_id": provider_id,
+ "result_nodes": result_nodes,
+ "result_relationships": result_relationships,
+ "query_duration": round(query_duration, 3),
+ },
+ },
+ )
+
status_code = status.HTTP_200_OK
if not graph.get("nodes"):
status_code = status.HTTP_404_NOT_FOUND
@@ -2618,13 +2640,35 @@ class AttackPathsScanViewSet(BaseRLSViewSet):
)
provider_id = str(attack_paths_scan.provider_id)
+ start = time.monotonic()
graph = attack_paths_views_helpers.execute_custom_query(
database_name,
serializer.validated_data["query"],
provider_id,
)
+ query_duration = time.monotonic() - start
graph_database.clear_cache(database_name)
+ query_length = len(serializer.validated_data["query"])
+ result_nodes = len(graph.get("nodes", []))
+ result_relationships = len(graph.get("relationships", []))
+ logger.info(
+ "attack_paths_custom_query_run",
+ extra={
+ "user_id": str(request.user.id),
+ "tenant_id": str(attack_paths_scan.provider.tenant_id),
+ "metadata": {
+ "provider": attack_paths_scan.provider.provider,
+ "scan_id": pk,
+ "provider_id": provider_id,
+ "query_length": query_length,
+ "result_nodes": result_nodes,
+ "result_relationships": result_relationships,
+ "query_duration": round(query_duration, 3),
+ },
+ },
+ )
+
status_code = status.HTTP_200_OK
if not graph.get("nodes"):
status_code = status.HTTP_404_NOT_FOUND
diff --git a/api/src/backend/config/custom_logging.py b/api/src/backend/config/custom_logging.py
index fb04930679..fe2a090ca6 100644
--- a/api/src/backend/config/custom_logging.py
+++ b/api/src/backend/config/custom_logging.py
@@ -2,6 +2,7 @@ import json
import logging
from enum import StrEnum
+
from config.env import env
from django_guid.log_filters import CorrelationId
@@ -62,6 +63,8 @@ class NDJSONFormatter(logging.Formatter):
log_record["duration"] = record.duration
if hasattr(record, "status_code"):
log_record["status_code"] = record.status_code
+ if hasattr(record, "metadata"):
+ log_record["metadata"] = record.metadata
if record.exc_info:
log_record["exc_info"] = self.formatException(record.exc_info)
@@ -107,6 +110,8 @@ class HumanReadableFormatter(logging.Formatter):
log_components.append(f"done in {record.duration}s:")
if hasattr(record, "status_code"):
log_components.append(f"{record.status_code}")
+ if hasattr(record, "metadata"):
+ log_components.append(f"metadata={record.metadata}")
if record.exc_info:
log_components.append(self.formatException(record.exc_info))
diff --git a/api/src/backend/tasks/jobs/attack_paths/aws.py b/api/src/backend/tasks/jobs/attack_paths/aws.py
index 9242946181..160ad6ee3a 100644
--- a/api/src/backend/tasks/jobs/attack_paths/aws.py
+++ b/api/src/backend/tasks/jobs/attack_paths/aws.py
@@ -239,8 +239,9 @@ def sync_aws_account(
failed_syncs[func_name] = exception_message
logger.warning(
- f"Caught exception syncing function {func_name} from AWS account {prowler_api_provider.uid}. We "
- "are continuing on to the next AWS sync function.",
+ f"Caught exception syncing function {func_name} from AWS account {prowler_api_provider.uid}: {e}. "
+ "Continuing to the next AWS sync function.",
+ exc_info=True,
)
continue
diff --git a/api/src/backend/tasks/jobs/attack_paths/scan.py b/api/src/backend/tasks/jobs/attack_paths/scan.py
index cd39700dd4..a7cf60b568 100644
--- a/api/src/backend/tasks/jobs/attack_paths/scan.py
+++ b/api/src/backend/tasks/jobs/attack_paths/scan.py
@@ -212,18 +212,20 @@ def run(tenant_id: str, scan_id: str, task_id: str) -> dict[str, Any]:
try:
graph_database.drop_database(tmp_cartography_config.neo4j_database)
- except Exception:
+ except Exception as e:
logger.error(
- f"Failed to drop temporary Neo4j database {tmp_cartography_config.neo4j_database} during cleanup"
+ f"Failed to drop temporary Neo4j database {tmp_cartography_config.neo4j_database} during cleanup: {e}",
+ exc_info=True,
)
try:
db_utils.finish_attack_paths_scan(
attack_paths_scan, StateChoices.FAILED, ingestion_exceptions
)
- except Exception:
- logger.warning(
- f"Could not mark attack paths scan {attack_paths_scan.id} as FAILED (row may have been deleted)"
+ except Exception as e:
+ logger.error(
+ f"Could not mark attack paths scan {attack_paths_scan.id} as FAILED (row may have been deleted): {e}",
+ exc_info=True,
)
raise
From 57f3920e6660b7228de38c232def3eed21ef0165 Mon Sep 17 00:00:00 2001
From: Josema Camacho
Date: Tue, 10 Mar 2026 16:48:16 +0100
Subject: [PATCH 014/108] refactor(api): migrate Attack Paths network exposure
queries from APOC to openCypher (#10266)
---
api/CHANGELOG.md | 1 +
.../backend/api/attack_paths/queries/aws.py | 60 ++----
skills/prowler-attack-paths-query/SKILL.md | 185 ++++++++++++------
3 files changed, 144 insertions(+), 102 deletions(-)
diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md
index 9c3f278f79..e6dd2cfa70 100644
--- a/api/CHANGELOG.md
+++ b/api/CHANGELOG.md
@@ -6,6 +6,7 @@ All notable changes to the **Prowler API** are documented in this file.
### 🔄 Changed
+- Attack Paths: Migrate network exposure queries from APOC to standard openCypher for Neo4j and Neptune compatibility [(#10266)](https://github.com/prowler-cloud/prowler/pull/10266)
- `POST /api/v1/providers` returns `409 Conflict` if already exists [(#10293)](https://github.com/prowler-cloud/prowler/pull/10293)
---
diff --git a/api/src/backend/api/attack_paths/queries/aws.py b/api/src/backend/api/attack_paths/queries/aws.py
index a54bd664ca..1a442f7bd6 100644
--- a/api/src/backend/api/attack_paths/queries/aws.py
+++ b/api/src/backend/api/attack_paths/queries/aws.py
@@ -16,8 +16,7 @@ AWS_INTERNET_EXPOSED_EC2_SENSITIVE_S3_ACCESS = AttackPathsQueryDefinition(
description="Detect EC2 instances with SSH exposed to the internet that can assume higher-privileged roles to read tagged sensitive S3 buckets despite bucket-level public access blocks.",
provider="aws",
cypher=f"""
- CALL apoc.create.vNode(['Internet'], {{id: 'Internet', name: 'Internet', provider_id: $provider_id}})
- YIELD node AS internet
+ OPTIONAL MATCH (internet:Internet {{_provider_id: $provider_id}})
MATCH path_s3 = (aws:AWSAccount {{id: $provider_uid}})--(s3:S3Bucket)--(t:AWSTag)
WHERE toLower(t.key) = toLower($tag_key) AND toLower(t.value) = toLower($tag_value)
@@ -32,8 +31,7 @@ AWS_INTERNET_EXPOSED_EC2_SENSITIVE_S3_ACCESS = AttackPathsQueryDefinition(
MATCH path_assume_role = (ec2)-[p:STS_ASSUMEROLE_ALLOW*1..9]-(r:AWSRole)
- CALL apoc.create.vRelationship(internet, 'CAN_ACCESS', {{provider_id: $provider_id}}, ec2)
- YIELD rel AS can_access
+ OPTIONAL MATCH (internet)-[can_access:CAN_ACCESS]->(ec2)
UNWIND nodes(path_s3) + nodes(path_ec2) + nodes(path_role) + nodes(path_assume_role) as n
OPTIONAL MATCH (n)-[pfr]-(pf:{PROWLER_FINDING_LABEL} {{status: 'FAIL', provider_uid: $provider_uid}})
@@ -181,14 +179,12 @@ AWS_EC2_INSTANCES_INTERNET_EXPOSED = AttackPathsQueryDefinition(
description="Find EC2 instances flagged as exposed to the internet within the selected account.",
provider="aws",
cypher=f"""
- CALL apoc.create.vNode(['Internet'], {{id: 'Internet', name: 'Internet', provider_id: $provider_id}})
- YIELD node AS internet
+ OPTIONAL MATCH (internet:Internet {{_provider_id: $provider_id}})
MATCH path = (aws:AWSAccount {{id: $provider_uid}})--(ec2:EC2Instance)
WHERE ec2.exposed_internet = true
- CALL apoc.create.vRelationship(internet, 'CAN_ACCESS', {{provider_id: $provider_id}}, ec2)
- YIELD rel AS can_access
+ OPTIONAL MATCH (internet)-[can_access:CAN_ACCESS]->(ec2)
UNWIND nodes(path) as n
OPTIONAL MATCH (n)-[pfr]-(pf:{PROWLER_FINDING_LABEL} {{status: 'FAIL', provider_uid: $provider_uid}})
@@ -205,16 +201,14 @@ AWS_SECURITY_GROUPS_OPEN_INTERNET_FACING = AttackPathsQueryDefinition(
description="Find internet-facing resources associated with security groups that allow inbound access from '0.0.0.0/0'.",
provider="aws",
cypher=f"""
- CALL apoc.create.vNode(['Internet'], {{id: 'Internet', name: 'Internet', provider_id: $provider_id}})
- YIELD node AS internet
+ OPTIONAL MATCH (internet:Internet {{_provider_id: $provider_id}})
// Match EC2 instances that are internet-exposed with open security groups (0.0.0.0/0)
MATCH path_ec2 = (aws:AWSAccount {{id: $provider_uid}})--(ec2:EC2Instance)--(sg:EC2SecurityGroup)--(ipi:IpPermissionInbound)--(ir:IpRange)
WHERE ec2.exposed_internet = true
AND ir.range = "0.0.0.0/0"
- CALL apoc.create.vRelationship(internet, 'CAN_ACCESS', {{provider_id: $provider_id}}, ec2)
- YIELD rel AS can_access
+ OPTIONAL MATCH (internet)-[can_access:CAN_ACCESS]->(ec2)
UNWIND nodes(path_ec2) as n
OPTIONAL MATCH (n)-[pfr]-(pf:{PROWLER_FINDING_LABEL} {{status: 'FAIL', provider_uid: $provider_uid}})
@@ -231,14 +225,12 @@ AWS_CLASSIC_ELB_INTERNET_EXPOSED = AttackPathsQueryDefinition(
description="Find Classic Load Balancers exposed to the internet along with their listeners.",
provider="aws",
cypher=f"""
- CALL apoc.create.vNode(['Internet'], {{id: 'Internet', name: 'Internet', provider_id: $provider_id}})
- YIELD node AS internet
+ OPTIONAL MATCH (internet:Internet {{_provider_id: $provider_id}})
MATCH path = (aws:AWSAccount {{id: $provider_uid}})--(elb:LoadBalancer)--(listener:ELBListener)
WHERE elb.exposed_internet = true
- CALL apoc.create.vRelationship(internet, 'CAN_ACCESS', {{provider_id: $provider_id}}, elb)
- YIELD rel AS can_access
+ OPTIONAL MATCH (internet)-[can_access:CAN_ACCESS]->(elb)
UNWIND nodes(path) as n
OPTIONAL MATCH (n)-[pfr]-(pf:{PROWLER_FINDING_LABEL} {{status: 'FAIL', provider_uid: $provider_uid}})
@@ -255,14 +247,12 @@ AWS_ELBV2_INTERNET_EXPOSED = AttackPathsQueryDefinition(
description="Find ELBv2 load balancers exposed to the internet along with their listeners.",
provider="aws",
cypher=f"""
- CALL apoc.create.vNode(['Internet'], {{id: 'Internet', name: 'Internet', provider_id: $provider_id}})
- YIELD node AS internet
+ OPTIONAL MATCH (internet:Internet {{_provider_id: $provider_id}})
MATCH path = (aws:AWSAccount {{id: $provider_uid}})--(elbv2:LoadBalancerV2)--(listener:ELBV2Listener)
WHERE elbv2.exposed_internet = true
- CALL apoc.create.vRelationship(internet, 'CAN_ACCESS', {{provider_id: $provider_id}}, elbv2)
- YIELD rel AS can_access
+ OPTIONAL MATCH (internet)-[can_access:CAN_ACCESS]->(elbv2)
UNWIND nodes(path) as n
OPTIONAL MATCH (n)-[pfr]-(pf:{PROWLER_FINDING_LABEL} {{status: 'FAIL', provider_uid: $provider_uid}})
@@ -279,31 +269,15 @@ AWS_PUBLIC_IP_RESOURCE_LOOKUP = AttackPathsQueryDefinition(
description="Given a public IP address, find the related AWS resource and its adjacent node within the selected account.",
provider="aws",
cypher=f"""
- CALL apoc.create.vNode(['Internet'], {{id: 'Internet', name: 'Internet', provider_id: $provider_id}})
- YIELD node AS internet
+ OPTIONAL MATCH (internet:Internet {{_provider_id: $provider_id}})
- CALL () {{
- MATCH path = (aws:AWSAccount {{id: $provider_uid}})-[r]-(x:EC2PrivateIp)-[q]-(y)
- WHERE x.public_ip = $ip
- RETURN path, x
+ MATCH path = (aws:AWSAccount {{id: $provider_uid}})-[r]-(x)-[q]-(y)
+ WHERE (x:EC2PrivateIp AND x.public_ip = $ip)
+ OR (x:EC2Instance AND x.publicipaddress = $ip)
+ OR (x:NetworkInterface AND x.public_ip = $ip)
+ OR (x:ElasticIPAddress AND x.public_ip = $ip)
- UNION MATCH path = (aws:AWSAccount {{id: $provider_uid}})-[r]-(x:EC2Instance)-[q]-(y)
- WHERE x.publicipaddress = $ip
- RETURN path, x
-
- UNION MATCH path = (aws:AWSAccount {{id: $provider_uid}})-[r]-(x:NetworkInterface)-[q]-(y)
- WHERE x.public_ip = $ip
- RETURN path, x
-
- UNION MATCH path = (aws:AWSAccount {{id: $provider_uid}})-[r]-(x:ElasticIPAddress)-[q]-(y)
- WHERE x.public_ip = $ip
- RETURN path, x
- }}
-
- WITH path, x, internet
-
- CALL apoc.create.vRelationship(internet, 'CAN_ACCESS', {{provider_id: $provider_id}}, x)
- YIELD rel AS can_access
+ OPTIONAL MATCH (internet)-[can_access:CAN_ACCESS]->(x)
UNWIND nodes(path) as n
OPTIONAL MATCH (n)-[pfr]-(pf:{PROWLER_FINDING_LABEL} {{status: 'FAIL', provider_uid: $provider_uid}})
diff --git a/skills/prowler-attack-paths-query/SKILL.md b/skills/prowler-attack-paths-query/SKILL.md
index 35fb9341d3..78f53b4c0d 100644
--- a/skills/prowler-attack-paths-query/SKILL.md
+++ b/skills/prowler-attack-paths-query/SKILL.md
@@ -7,7 +7,7 @@ description: >
license: Apache-2.0
metadata:
author: prowler-cloud
- version: "1.0"
+ version: "1.1"
scope: [root, api]
auto_invoke:
- "Creating Attack Paths queries"
@@ -80,7 +80,16 @@ api/src/backend/api/attack_paths/queries/{provider}.py
Example: `api/src/backend/api/attack_paths/queries/aws.py`
-### Query Definition Pattern
+### Query parameters for provider scoping
+
+Two parameters exist. Both are injected automatically by the query runner.
+
+| Parameter | Property it matches | Used on | Purpose |
+| --------------- | ------------------- | -------------- | ------------------------------------ |
+| `$provider_uid` | `id` | `AWSAccount` | Scopes to a specific AWS account |
+| `$provider_id` | `_provider_id` | Any other node | Scopes nodes to the provider context |
+
+### Privilege Escalation Query Pattern
```python
from api.attack_paths.queries.types import (
@@ -88,7 +97,6 @@ from api.attack_paths.queries.types import (
AttackPathsQueryDefinition,
AttackPathsQueryParameterDefinition,
)
-from tasks.jobs.attack_paths.config import PROWLER_FINDING_LABEL
# {REFERENCE_ID} (e.g., EC2-001, GLUE-001)
AWS_{QUERY_NAME} = AttackPathsQueryDefinition(
@@ -129,7 +137,7 @@ AWS_{QUERY_NAME} = AttackPathsQueryDefinition(
)
UNWIND nodes(path_principal) + nodes(path_target) as n
- OPTIONAL MATCH (n)-[pfr]-(pf:{PROWLER_FINDING_LABEL} {{status: 'FAIL', provider_uid: $provider_uid}})
+ OPTIONAL MATCH (n)-[pfr]-(pf:ProwlerFinding {{status: 'FAIL', provider_uid: $provider_uid}})
RETURN path_principal, path_target,
collect(DISTINCT pf) as dpf, collect(DISTINCT pfr) as dpfr
@@ -138,6 +146,36 @@ AWS_{QUERY_NAME} = AttackPathsQueryDefinition(
)
```
+### Network Exposure Query Pattern
+
+```python
+AWS_{QUERY_NAME} = AttackPathsQueryDefinition(
+ id="aws-{kebab-case-name}",
+ name="{Human-friendly label}",
+ short_description="{Brief explanation.}",
+ description="{Detailed description.}",
+ provider="aws",
+ cypher=f"""
+ // Match the Internet sentinel node
+ OPTIONAL MATCH (internet:Internet {{_provider_id: $provider_id}})
+
+ // Match exposed resources (MUST chain from `aws`)
+ MATCH path = (aws:AWSAccount {{id: $provider_uid}})--(resource:EC2Instance)
+ WHERE resource.exposed_internet = true
+
+ // Link Internet to resource
+ OPTIONAL MATCH (internet)-[can_access:CAN_ACCESS]->(resource)
+
+ UNWIND nodes(path) as n
+ OPTIONAL MATCH (n)-[pfr]-(pf:ProwlerFinding {{status: 'FAIL', provider_uid: $provider_uid}})
+
+ RETURN path, collect(DISTINCT pf) as dpf, collect(DISTINCT pfr) as dpfr,
+ internet, can_access
+ """,
+ parameters=[],
+)
+```
+
### Register in Query List
Add to the `{PROVIDER}_QUERIES` list at the bottom of the file:
@@ -219,6 +257,7 @@ https://raw.githubusercontent.com/cartography-cncf/cartography/refs/tags/0.126.0
- `ProwlerFinding` - Prowler finding nodes with `status`, `provider_uid` properties
- `ProviderResource` - Generic resource marker
- `{Provider}Resource` - Provider-specific marker (e.g., `AWSResource`)
+- `Internet` - Internet sentinel node with `_provider_id` property (used in network exposure queries)
These are defined in `api/src/backend/tasks/jobs/attack_paths/config.py`.
@@ -234,7 +273,7 @@ This informs query design by showing what data is actually available to query.
### 4. Create Query Definition
-Use the standard pattern (see above) with:
+Use the appropriate pattern (privilege escalation or network exposure) with:
- **id**: Auto-generated as `{provider}-{kebab-case-description}`
- **name**: Short, human-friendly label. No raw IAM permissions. For sourced queries (e.g., pathfinding.cloud), append the reference ID in parentheses: `"EC2 Instance Launch with Privileged Role (EC2-001)"`. If the name already has parentheses, prepend the ID inside them: `"ECS Service Creation with Privileged Role (ECS-003 - Existing Cluster)"`.
@@ -263,7 +302,7 @@ Examples:
- `aws-ec2-privesc-passrole-iam`
- `aws-iam-privesc-attach-role-policy-assume-role`
-- `aws-rds-unencrypted-storage`
+- `aws-ec2-instances-internet-exposed`
### Query Constant Name
@@ -275,7 +314,7 @@ Examples:
- `AWS_EC2_PRIVESC_PASSROLE_IAM`
- `AWS_IAM_PRIVESC_ATTACH_ROLE_POLICY_ASSUME_ROLE`
-- `AWS_RDS_UNENCRYPTED_STORAGE`
+- `AWS_EC2_INSTANCES_INTERNET_EXPOSED`
---
@@ -325,46 +364,91 @@ WHERE any(resource IN stmt.resource WHERE
)
```
+### Match Internet Sentinel Node
+
+Used in network exposure queries. The Internet node is a real graph node, scoped by `_provider_id`:
+
+```cypher
+OPTIONAL MATCH (internet:Internet {_provider_id: $provider_id})
+```
+
+### Link Internet to Exposed Resource
+
+The `CAN_ACCESS` relationship is a real graph relationship linking the Internet node to exposed resources:
+
+```cypher
+OPTIONAL MATCH (internet)-[can_access:CAN_ACCESS]->(resource)
+```
+
+### Multi-label OR (match multiple resource types)
+
+When a query needs to match different resource types in the same position, use label checks in WHERE:
+
+```cypher
+MATCH path = (aws:AWSAccount {id: $provider_uid})-[r]-(x)-[q]-(y)
+WHERE (x:EC2PrivateIp AND x.public_ip = $ip)
+ OR (x:EC2Instance AND x.publicipaddress = $ip)
+ OR (x:NetworkInterface AND x.public_ip = $ip)
+ OR (x:ElasticIPAddress AND x.public_ip = $ip)
+```
+
### Include Prowler Findings
```cypher
UNWIND nodes(path_principal) + nodes(path_target) as n
-OPTIONAL MATCH (n)-[pfr]-(pf:{PROWLER_FINDING_LABEL} {status: 'FAIL', provider_uid: $provider_uid})
+OPTIONAL MATCH (n)-[pfr]-(pf:ProwlerFinding {status: 'FAIL', provider_uid: $provider_uid})
RETURN path_principal, path_target,
collect(DISTINCT pf) as dpf, collect(DISTINCT pfr) as dpfr
```
+For network exposure queries, also return the internet node and relationship:
+
+```cypher
+RETURN path, collect(DISTINCT pf) as dpf, collect(DISTINCT pfr) as dpfr,
+ internet, can_access
+```
+
---
## Common Node Labels by Provider
### AWS
-| Label | Description |
-| -------------------- | ----------------------------------- |
-| `AWSAccount` | AWS account root |
-| `AWSPrincipal` | IAM principal (user, role, service) |
-| `AWSRole` | IAM role |
-| `AWSUser` | IAM user |
-| `AWSPolicy` | IAM policy |
-| `AWSPolicyStatement` | Policy statement |
-| `EC2Instance` | EC2 instance |
-| `EC2SecurityGroup` | Security group |
-| `S3Bucket` | S3 bucket |
-| `RDSInstance` | RDS database instance |
-| `LoadBalancer` | Classic ELB |
-| `LoadBalancerV2` | ALB/NLB |
-| `LaunchTemplate` | EC2 launch template |
+| Label | Description |
+| --------------------- | --------------------------------------- |
+| `AWSAccount` | AWS account root |
+| `AWSPrincipal` | IAM principal (user, role, service) |
+| `AWSRole` | IAM role |
+| `AWSUser` | IAM user |
+| `AWSPolicy` | IAM policy |
+| `AWSPolicyStatement` | Policy statement |
+| `AWSTag` | Resource tag (key/value) |
+| `EC2Instance` | EC2 instance |
+| `EC2SecurityGroup` | Security group |
+| `EC2PrivateIp` | EC2 private IP (has `public_ip`) |
+| `IpPermissionInbound` | Inbound security group rule |
+| `IpRange` | IP range (e.g., `0.0.0.0/0`) |
+| `NetworkInterface` | ENI (has `public_ip`) |
+| `ElasticIPAddress` | Elastic IP (has `public_ip`) |
+| `S3Bucket` | S3 bucket |
+| `RDSInstance` | RDS database instance |
+| `LoadBalancer` | Classic ELB |
+| `LoadBalancerV2` | ALB/NLB |
+| `ELBListener` | Classic ELB listener |
+| `ELBV2Listener` | ALB/NLB listener |
+| `LaunchTemplate` | EC2 launch template |
+| `Internet` | Internet sentinel node (`_provider_id`) |
### Common Relationships
-| Relationship | Description |
-| ---------------------- | ----------------------- |
-| `TRUSTS_AWS_PRINCIPAL` | Role trust relationship |
-| `STS_ASSUMEROLE_ALLOW` | Can assume role |
-| `POLICY` | Has policy attached |
-| `STATEMENT` | Policy has statement |
+| Relationship | Description |
+| ---------------------- | ---------------------------------- |
+| `TRUSTS_AWS_PRINCIPAL` | Role trust relationship |
+| `STS_ASSUMEROLE_ALLOW` | Can assume role |
+| `CAN_ACCESS` | Internet-to-resource exposure link |
+| `POLICY` | Has policy attached |
+| `STATEMENT` | Policy has statement |
---
@@ -393,7 +477,7 @@ parameters=[
## Best Practices
-1. **Always filter by provider_uid**: Use `{id: $provider_uid}` on account nodes and `{provider_uid: $provider_uid}` on ProwlerFinding nodes
+1. **Always scope by provider**: Use `{id: $provider_uid}` on `AWSAccount` nodes. Use `{_provider_id: $provider_id}` on any other node that needs provider scoping (e.g., `Internet`).
2. **Use consistent naming**: Follow existing patterns in the file
@@ -415,6 +499,8 @@ parameters=[
MATCH (aws)--(role:AWSRole) WHERE role.name = 'admin'
```
+ The `Internet` node is an exception: it uses `OPTIONAL MATCH` with `_provider_id` for scoping instead of chaining from `aws`.
+
---
## openCypher Compatibility
@@ -425,23 +511,14 @@ Queries must be written in **openCypher Version 9** to ensure compatibility with
### Avoid These (Not in openCypher spec)
-| Feature | Reason |
-| --------------------------------------------------- | ----------------------------------------------- |
-| APOC procedures (`apoc.*`) | Neo4j-specific plugin, not available in Neptune |
-| Virtual nodes (`apoc.create.vNode`) | APOC-specific |
-| Virtual relationships (`apoc.create.vRelationship`) | APOC-specific |
-| Neptune extensions | Not available in Neo4j |
-| `reduce()` function | Use `UNWIND` + aggregation instead |
-| `FOREACH` clause | Use `WITH` + `UNWIND` + `SET` instead |
-| Regex match operator (`=~`) | Not supported in Neptune |
-
-### CALL Subqueries
-
-Supported with limitations:
-
-- Use `WITH` clause to import variables: `CALL { WITH var ... }`
-- Updates inside CALL subqueries are NOT supported
-- Emitted variables cannot overlap with variables before the CALL
+| Feature | Reason | Use instead |
+| -------------------------- | ----------------------------------------------- | ------------------------------------------------------ |
+| APOC procedures (`apoc.*`) | Neo4j-specific plugin, not available in Neptune | Real nodes and relationships in the graph |
+| Neptune extensions | Not available in Neo4j | Standard openCypher |
+| `reduce()` function | Not in openCypher spec | `UNWIND` + `collect()` |
+| `FOREACH` clause | Not in openCypher spec | `WITH` + `UNWIND` + `SET` |
+| Regex operator (`=~`) | Not supported in Neptune | `toLower()` + exact match, or `CONTAINS`/`STARTS WITH` |
+| `CALL () { UNION }` | Complex, hard to maintain | Multi-label OR in WHERE (see patterns section) |
---
@@ -451,7 +528,7 @@ Supported with limitations:
- **Repository**: https://github.com/DataDog/pathfinding.cloud
- **All paths JSON**: `https://raw.githubusercontent.com/DataDog/pathfinding.cloud/main/docs/paths.json`
-- Use WebFetch to query specific paths or list available services
+- Always use Bash with `curl | jq` to fetch paths (WebFetch truncates the large JSON)
### Cartography Schema
@@ -461,7 +538,6 @@ Supported with limitations:
### openCypher Specification
- **Neptune openCypher compliance** (what Neptune supports): https://docs.aws.amazon.com/neptune/latest/userguide/feature-opencypher-compliance.html
-- **Rewriting Cypher for Neptune** (converting Neo4j-specific syntax): https://docs.aws.amazon.com/neptune/latest/userguide/migration-opencypher-rewrites.html
- **openCypher project** (spec, grammar, TCK): https://github.com/opencypher/openCypher
---
@@ -485,13 +561,4 @@ Use the existing queries to learn:
- How to include Prowler findings
- Comment style
-> **Compatibility Warning**: Some existing queries use Neo4j-specific features
-> (e.g., `apoc.create.vNode`, `apoc.create.vRelationship`, regex `=~`) that are
-> **NOT compatible** with Amazon Neptune. Use these queries to learn general
-> patterns (structure, naming, Prowler findings integration, comment style) but
-> **DO NOT copy APOC procedures or other Neo4j-specific syntax** into new queries.
-> New queries must be pure openCypher Version 9. Refer to the
-> [openCypher Compatibility](#opencypher-compatibility) section for the full list
-> of features to avoid.
-
-**DO NOT** use generic templates. Match the exact style of existing **compatible** queries in the file.
+**DO NOT** use generic templates. Match the exact style of existing queries in the file.
From 2768b7ad4e7472d490dd3cf5ecbcd4e934ffa3fe Mon Sep 17 00:00:00 2001
From: Daniel Barranquero <74871504+danibarranqueroo@users.noreply.github.com>
Date: Tue, 10 Mar 2026 16:58:08 +0100
Subject: [PATCH 015/108] docs: update readme and docs with new providers
(#10295)
---
README.md | 12 +++++++-----
docs/introduction.mdx | 7 ++++---
2 files changed, 11 insertions(+), 8 deletions(-)
diff --git a/README.md b/README.md
index 3d1589f6cd..79b274e05b 100644
--- a/README.md
+++ b/README.md
@@ -109,14 +109,16 @@ Every AWS provider scan will enqueue an Attack Paths ingestion job automatically
| GCP | 100 | 13 | 15 | 11 | Official | UI, API, CLI |
| Kubernetes | 83 | 7 | 7 | 9 | Official | UI, API, CLI |
| GitHub | 21 | 2 | 1 | 2 | Official | UI, API, CLI |
-| M365 | 75 | 7 | 4 | 4 | Official | UI, API, CLI |
-| OCI | 51 | 13 | 3 | 12 | Official | UI, API, CLI |
+| M365 | 89 | 9 | 4 | 5 | Official | UI, API, CLI |
+| OCI | 48 | 13 | 3 | 10 | Official | UI, API, CLI |
| Alibaba Cloud | 61 | 9 | 3 | 9 | Official | UI, API, CLI |
-| Cloudflare | 29 | 2 | 0 | 5 | Official | CLI, API |
+| Cloudflare | 29 | 2 | 0 | 5 | Official | UI, API, CLI |
| IaC | [See `trivy` docs.](https://trivy.dev/latest/docs/coverage/iac/) | N/A | N/A | N/A | Official | UI, API, CLI |
-| MongoDB Atlas | 10 | 3 | 0 | 3 | Official | UI, API, CLI |
+| MongoDB Atlas | 10 | 3 | 0 | 8 | Official | UI, API, CLI |
| LLM | [See `promptfoo` docs.](https://www.promptfoo.dev/docs/red-team/plugins/) | N/A | N/A | N/A | Official | CLI |
-| OpenStack | 1 | 1 | 0 | 2 | Official | CLI |
+| Image | N/A | N/A | N/A | N/A | Official | CLI, API |
+| Google Workspace | 1 | 1 | 0 | 1 | Official | CLI |
+| OpenStack | 27 | 4 | 0 | 8 | Official | UI, API, CLI |
| NHN | 6 | 2 | 1 | 0 | Unofficial | CLI |
> [!Note]
diff --git a/docs/introduction.mdx b/docs/introduction.mdx
index c354f2802d..05de41b157 100644
--- a/docs/introduction.mdx
+++ b/docs/introduction.mdx
@@ -33,12 +33,13 @@ The supported providers right now are:
| [Github](/user-guide/providers/github/getting-started-github) | Official | Organizations / Repositories | UI, API, CLI |
| [Oracle Cloud](/user-guide/providers/oci/getting-started-oci) | Official | Tenancies / Compartments | UI, API, CLI |
| [Alibaba Cloud](/user-guide/providers/alibabacloud/getting-started-alibabacloud) | Official | Accounts | UI, API, CLI |
-| [Cloudflare](/user-guide/providers/cloudflare/getting-started-cloudflare) | Official | Accounts | CLI |
+| [Cloudflare](/user-guide/providers/cloudflare/getting-started-cloudflare) | Official | Accounts | UI, API, CLI |
| [Infra as Code](/user-guide/providers/iac/getting-started-iac) | Official | Repositories | UI, API, CLI |
| [MongoDB Atlas](/user-guide/providers/mongodbatlas/getting-started-mongodbatlas) | Official | Organizations | UI, API, CLI |
-| [OpenStack](/user-guide/providers/openstack/getting-started-openstack) | Official | Projects | CLI |
+| [OpenStack](/user-guide/providers/openstack/getting-started-openstack) | Official | Projects | UI, API, CLI |
| [LLM](/user-guide/providers/llm/getting-started-llm) | Official | Models | CLI |
-| [Image](/user-guide/providers/image/getting-started-image) | Official | Container Images | CLI |
+| [Image](/user-guide/providers/image/getting-started-image) | Official | Container Images | CLI, API |
+| [Google Workspace](/user-guide/providers/googleworkspace/getting-started-googleworkspace) | Official | Domains | CLI |
| **NHN** | Unofficial | Tenants | CLI |
For more information about the checks and compliance of each provider visit [Prowler Hub](https://hub.prowler.com).
From e9855bbf2feb7b9dd94d38542b494f47b8f43bb3 Mon Sep 17 00:00:00 2001
From: Daniel Barranquero <74871504+danibarranqueroo@users.noreply.github.com>
Date: Tue, 10 Mar 2026 16:58:31 +0100
Subject: [PATCH 016/108] docs: update mutelist docs (#10296)
---
.../tutorials/prowler-app-mute-findings.mdx | 10 ++++---
.../tutorials/prowler-app-simple-mutelist.mdx | 26 +++++++++++++++----
2 files changed, 28 insertions(+), 8 deletions(-)
diff --git a/docs/user-guide/tutorials/prowler-app-mute-findings.mdx b/docs/user-guide/tutorials/prowler-app-mute-findings.mdx
index f46cb308b4..82623b54cf 100644
--- a/docs/user-guide/tutorials/prowler-app-mute-findings.mdx
+++ b/docs/user-guide/tutorials/prowler-app-mute-findings.mdx
@@ -77,10 +77,10 @@ If the YAML configuration is invalid, an error message will be displayed
2. Navigate to the Findings page to verify muted findings

-
-The Advanced Mutelist configuration takes effect on subsequent scans. Existing findings are not retroactively muted.
+
+The Advanced Mutelist configuration takes effect on **subsequent scans only**. Existing findings from previous scans are **not** retroactively muted. You must run a new scan after saving your YAML configuration to see its effect. Similarly, removing a pattern from the YAML configuration will only stop muting new findings generated by subsequent scans.
-
+
## YAML Configuration Examples
Below are ready-to-use examples for different cloud providers. For detailed syntax and logic explanation, see [CLI Mutelist documentation](/user-guide/cli/tutorials/mutelist#how-the-mutelist-works).
@@ -421,6 +421,10 @@ Mutelist:
Description: "Mute findings for dev/test environments in alpha project"
```
+### Priority: Advanced vs. Simple Mutelist
+
+When both Advanced Mutelist (YAML) and [Simple Mutelist](/user-guide/tutorials/prowler-app-simple-mutelist) rules match the same finding, the **Advanced Mutelist takes higher priority**. The finding will be muted with the reason "Muted by mutelist". If a finding is not matched by the Advanced Mutelist but matches a Simple Mutelist rule, the Simple rule's custom justification is used instead.
+
### Best Practices
1. **Start Small**: Begin with specific resources and gradually expand
diff --git a/docs/user-guide/tutorials/prowler-app-simple-mutelist.mdx b/docs/user-guide/tutorials/prowler-app-simple-mutelist.mdx
index ca0a487978..c0bd7db9b4 100644
--- a/docs/user-guide/tutorials/prowler-app-simple-mutelist.mdx
+++ b/docs/user-guide/tutorials/prowler-app-simple-mutelist.mdx
@@ -90,10 +90,10 @@ To toggle a mute rule without deleting it:
3. Locate the mute rule
4. Use the toggle switch in the "Enabled" column to enable or disable the rule
-
-Disabled mute rules remain in the system but do not affect findings. Findings associated with disabled rules will appear as unmuted in subsequent scans.
+
+Disabling a mute rule does not retroactively unmute existing findings that were already marked as muted. Those findings retain their muted status as point-in-time historical records. Only **new findings** generated by subsequent scans will appear as unmuted.
-
+
### Editing Mute Rules
@@ -117,7 +117,7 @@ To permanently remove a mute rule:
5. Confirm the deletion
-Deleting a mute rule is permanent. The finding will appear as unmuted in subsequent scans. To temporarily unmute a finding without losing the rule, disable the rule instead of deleting it.
+Deleting a mute rule is permanent and cannot be undone. Existing findings that were already muted retain their muted status as historical records — only **new findings** from subsequent scans will appear as unmuted. To temporarily stop muting new findings without losing the rule, disable the rule instead of deleting it.
@@ -129,9 +129,13 @@ Simple Mutelist creates mute rules based on a finding's unique identifier (UID).
- **Historical findings** with the same UID are also muted
- **Future findings** from subsequent scans are automatically muted if they match the UID
+### Bulk Muting and Grouping
+
+When muting multiple findings at once, a single mute rule is created containing all selected finding UIDs. However, once a rule is created, **additional findings cannot be added to an existing rule**. To mute new findings, create a separate mute rule.
+
### Uniqueness Constraint
-Each finding UID can only have one mute rule. Attempting to create a duplicate mute rule for the same finding displays an error message indicating the rule already exists.
+Each finding UID can only belong to one **enabled** mute rule at a time. Attempting to create a mute rule that includes a finding UID already covered by another enabled rule displays a conflict error. If you need to reorganize mute rules, disable or delete the existing rule first, then create a new one.
## Simple Mutelist vs. Advanced Mutelist
@@ -139,8 +143,12 @@ Each finding UID can only have one mute rule. Attempting to create a duplicate m
| ------------------------ | ----------------------------------------- | ------------------------------------------------------ |
| **Configuration method** | Point-and-click interface | YAML configuration file |
| **Muting scope** | Individual finding UIDs | Patterns based on checks, regions, resources, and tags |
+| **When muting applies** | Immediately (current + historical findings) | On subsequent scans only (not retroactive) |
+| **Unmuting behavior** | Disabling/deleting a rule only affects new findings from subsequent scans | Removing a pattern stops muting on the next scan |
+| **Adding findings later** | Not supported — must create a new rule | Automatic — any finding matching the pattern is muted |
| **Regular expressions** | Not supported | Fully supported |
| **Bulk operations** | Checkbox selection in Findings table | YAML wildcards and patterns |
+| **Priority** | Applied after Advanced Mutelist | Highest priority |
| **Best for** | Quick, ad-hoc muting of specific findings | Complex, policy-driven muting rules |
### When to Use Simple Mutelist
@@ -175,6 +183,14 @@ If an error indicates a mute rule already exists for a finding:
3. Edit the existing rule's justification if needed, or
4. Delete the existing rule and create a new one
+### Finding Still Appears Muted After Disabling or Deleting a Rule
+
+If a finding still appears as muted after disabling or deleting its mute rule:
+
+1. This is expected behavior — existing findings retain their muted status as historical records
+2. Run a new scan to generate new findings that will reflect the updated rule state
+3. New findings with the same UID will appear with their actual status (PASS/FAIL) instead of muted
+
### Finding Still Appears Unmuted
If a muted finding still appears unmuted:
From e40beee3153d162c4c961c724d22c1239b22a8b3 Mon Sep 17 00:00:00 2001
From: Utwo
Date: Wed, 11 Mar 2026 11:07:22 +0200
Subject: [PATCH 017/108] feat: Helm CD (#10079)
Co-authored-by: Pepe Fagoaga
---
.github/workflows/helm-chart-checks.yml | 48 +++++++++++++++++++++
.github/workflows/helm-chart-release.yml | 54 ++++++++++++++++++++++++
contrib/k8s/helm/prowler-app/.gitignore | 1 +
contrib/k8s/helm/prowler-app/Chart.yaml | 2 +
4 files changed, 105 insertions(+)
create mode 100644 .github/workflows/helm-chart-checks.yml
create mode 100644 .github/workflows/helm-chart-release.yml
create mode 100644 contrib/k8s/helm/prowler-app/.gitignore
diff --git a/.github/workflows/helm-chart-checks.yml b/.github/workflows/helm-chart-checks.yml
new file mode 100644
index 0000000000..b1eec36fb5
--- /dev/null
+++ b/.github/workflows/helm-chart-checks.yml
@@ -0,0 +1,48 @@
+name: 'Helm: Chart Checks'
+# DISCLAIMER: This workflow is not maintained by the Prowler team. Refer to contrib/k8s/helm/prowler-app for the source code.
+on:
+ push:
+ branches:
+ - 'master'
+ - 'v5.*'
+ paths:
+ - 'contrib/k8s/helm/prowler-app/**'
+ pull_request:
+ branches:
+ - 'master'
+ - 'v5.*'
+ paths:
+ - 'contrib/k8s/helm/prowler-app/**'
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+env:
+ CHART_PATH: contrib/k8s/helm/prowler-app
+
+jobs:
+ helm-lint:
+ if: github.repository == 'prowler-cloud/prowler'
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ permissions:
+ contents: read
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+ with:
+ persist-credentials: false
+
+ - name: Set up Helm
+ uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1
+
+ - name: Update chart dependencies
+ run: helm dependency update ${{ env.CHART_PATH }}
+
+ - name: Lint Helm chart
+ run: helm lint ${{ env.CHART_PATH }}
+
+ - name: Validate Helm chart template rendering
+ run: helm template prowler ${{ env.CHART_PATH }}
diff --git a/.github/workflows/helm-chart-release.yml b/.github/workflows/helm-chart-release.yml
new file mode 100644
index 0000000000..960e611f43
--- /dev/null
+++ b/.github/workflows/helm-chart-release.yml
@@ -0,0 +1,54 @@
+name: 'Helm: Chart Release'
+# DISCLAIMER: This workflow is not maintained by the Prowler team. Refer to contrib/k8s/helm/prowler-app for the source code.
+
+on:
+ release:
+ types:
+ - 'published'
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: false
+
+env:
+ CHART_PATH: contrib/k8s/helm/prowler-app
+
+jobs:
+ release-helm-chart:
+ if: github.repository == 'prowler-cloud/prowler'
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ permissions:
+ contents: read
+ packages: write
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+ with:
+ persist-credentials: false
+
+ - name: Set up Helm
+ uses: azure/setup-helm@b9e51907a09c216f16ebe8536097933489208112 # v4.3.0
+
+ - name: Set appVersion from release tag
+ run: |
+ RELEASE_TAG="${GITHUB_EVENT_RELEASE_TAG_NAME}"
+ echo "Setting appVersion to ${RELEASE_TAG}"
+ sed -i "s/^appVersion:.*/appVersion: \"${RELEASE_TAG}\"/" ${{ env.CHART_PATH }}/Chart.yaml
+ env:
+ GITHUB_EVENT_RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
+
+ - name: Login to GHCR
+ run: echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io -u ${GITHUB_ACTOR} --password-stdin
+
+ - name: Update chart dependencies
+ run: helm dependency update ${{ env.CHART_PATH }}
+
+ - name: Package Helm chart
+ run: helm package ${{ env.CHART_PATH }} --destination .helm-packages
+
+ - name: Push chart to GHCR
+ run: |
+ PACKAGE=$(ls .helm-packages/*.tgz)
+ helm push "$PACKAGE" oci://ghcr.io/${{ github.repository_owner }}/charts
diff --git a/contrib/k8s/helm/prowler-app/.gitignore b/contrib/k8s/helm/prowler-app/.gitignore
new file mode 100644
index 0000000000..ee3892e879
--- /dev/null
+++ b/contrib/k8s/helm/prowler-app/.gitignore
@@ -0,0 +1 @@
+charts/
diff --git a/contrib/k8s/helm/prowler-app/Chart.yaml b/contrib/k8s/helm/prowler-app/Chart.yaml
index 5397d43569..672e807f10 100644
--- a/contrib/k8s/helm/prowler-app/Chart.yaml
+++ b/contrib/k8s/helm/prowler-app/Chart.yaml
@@ -13,6 +13,8 @@ keywords:
- gcp
- kubernetes
maintainers:
+ - name: Dani
+ email: andre.gomes@promptlyhealth.com
- name: Mihai
email: mihai.legat@gmail.com
dependencies:
From cc0d83de91fad115b97416cb8fe321960f39d6da Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rub=C3=A9n=20De=20la=20Torre=20Vico?=
Date: Wed, 11 Mar 2026 10:11:37 +0100
Subject: [PATCH 018/108] docs(mcp_server): add Attack Paths MCP tools
documentation (#10302)
Co-authored-by: Claude Opus 4.6
---
.../basic-usage/prowler-mcp-tools.mdx | 10 +++++++++-
docs/getting-started/products/prowler-mcp.mdx | 2 ++
.../tutorials/prowler-app-attack-paths.mdx | 17 +++++++++++++++++
3 files changed, 28 insertions(+), 1 deletion(-)
diff --git a/docs/getting-started/basic-usage/prowler-mcp-tools.mdx b/docs/getting-started/basic-usage/prowler-mcp-tools.mdx
index bf0092a069..43cbb12027 100644
--- a/docs/getting-started/basic-usage/prowler-mcp-tools.mdx
+++ b/docs/getting-started/basic-usage/prowler-mcp-tools.mdx
@@ -10,7 +10,7 @@ Complete reference guide for all tools available in the Prowler MCP Server. Tool
|----------|------------|------------------------|
| Prowler Hub | 10 tools | No |
| Prowler Documentation | 2 tools | No |
-| Prowler Cloud/App | 24 tools | Yes |
+| Prowler Cloud/App | 27 tools | Yes |
## Tool Naming Convention
@@ -80,6 +80,14 @@ Tools for managing finding muting, including pattern-based bulk muting (mutelist
- **`prowler_app_update_mute_rule`** - Update a mute rule's name, reason, or enabled status
- **`prowler_app_delete_mute_rule`** - Delete a mute rule from the system
+### Attack Paths Analysis
+
+Tools for analyzing privilege escalation chains and security misconfigurations using graph-based analysis. Attack Paths maps relationships between cloud resources, permissions, and security findings to detect how privileges can be escalated and how misconfigurations can be exploited.
+
+- **`prowler_app_list_attack_paths_scans`** - List Attack Paths scans with filtering by provider, provider type, and scan state (available, scheduled, executing, completed, failed, cancelled)
+- **`prowler_app_list_attack_paths_queries`** - Discover available Attack Paths queries for a completed scan, including query names, descriptions, and required parameters
+- **`prowler_app_run_attack_paths_query`** - Execute an Attack Paths query against a completed scan and retrieve graph results with nodes (cloud resources, findings, virtual nodes) and relationships (access paths, role assumptions, security group memberships)
+
### Compliance Management
Tools for viewing compliance status and framework details across all cloud providers.
diff --git a/docs/getting-started/products/prowler-mcp.mdx b/docs/getting-started/products/prowler-mcp.mdx
index df402f605b..ea6fe8fcc0 100644
--- a/docs/getting-started/products/prowler-mcp.mdx
+++ b/docs/getting-started/products/prowler-mcp.mdx
@@ -24,6 +24,7 @@ Full access to Prowler Cloud platform and self-managed Prowler App for:
- **Scan Orchestration**: Trigger on-demand scans and schedule recurring security assessments
- **Resource Inventory**: Search and view detailed information about your audited resources
- **Muting Management**: Create and manage muting lists/rules to suppress non-relevant findings
+- **Attack Paths Analysis**: Analyze privilege escalation chains and security misconfigurations through graph-based analysis of cloud resource relationships
### 2. Prowler Hub
@@ -61,6 +62,7 @@ The Prowler MCP Server enables powerful workflows through AI assistants:
- "Show me all critical findings from my AWS production accounts"
- "Register my new AWS account in Prowler and run a scheduled scan every day"
- "List all muted findings and detect what findgings are muted by a not enough good reason in relation to their severity"
+- "Run an attack paths query to find EC2 instances exposed to the Internet with access to sensitive S3 buckets"
**Security Research**
- "Explain what the S3 bucket public access Prowler check does"
diff --git a/docs/user-guide/tutorials/prowler-app-attack-paths.mdx b/docs/user-guide/tutorials/prowler-app-attack-paths.mdx
index 41047b8c2c..88d9a3a25d 100644
--- a/docs/user-guide/tutorials/prowler-app-attack-paths.mdx
+++ b/docs/user-guide/tutorials/prowler-app-attack-paths.mdx
@@ -201,3 +201,20 @@ To expand the graph for detailed exploration, click the fullscreen icon in the g
alt="Attack Paths fullscreen mode with graph and node detail side panel"
width="700"
/>
+
+## Using Attack Paths with the MCP Server
+
+Attack Paths capabilities are also available through the [Prowler MCP Server](/getting-started/products/prowler-mcp), enabling interaction with Attack Paths data via AI assistants like Claude Desktop, Cursor, and other MCP clients.
+
+The following MCP tools are available for Attack Paths:
+
+- **`prowler_app_list_attack_paths_scans`** - List and filter Attack Paths scans
+- **`prowler_app_list_attack_paths_queries`** - Discover available queries for a completed scan
+- **`prowler_app_run_attack_paths_query`** - Execute a query and retrieve graph results with nodes and relationships
+
+These tools enable workflows such as:
+- Asking an AI assistant to identify privilege escalation paths in a specific AWS account
+- Automating attack path analysis across multiple scans
+- Combining attack path data with findings and compliance information for comprehensive security reports
+
+For the complete list of MCP tools, see the [Tools Reference](/getting-started/basic-usage/prowler-mcp-tools#attack-paths-analysis).
From e28bde797fbfcbe1fc9a1322a9c5d6afb46e6d45 Mon Sep 17 00:00:00 2001
From: Daniel Barranquero <74871504+danibarranqueroo@users.noreply.github.com>
Date: Wed, 11 Mar 2026 12:00:43 +0100
Subject: [PATCH 019/108] feat(openstack): object storage service with 7 new
checks (#10258)
---
prowler/CHANGELOG.md | 1 +
.../services/objectstorage/__init__.py | 0
.../objectstorage/objectstorage_client.py | 6 +
.../__init__.py | 0
...iner_acl_not_globally_shared.metadata.json | 37 ++
...orage_container_acl_not_globally_shared.py | 29 ++
.../__init__.py | 0
...e_container_listing_disabled.metadata.json | 37 ++
...bjectstorage_container_listing_disabled.py | 32 ++
.../__init__.py | 0
...iner_metadata_sensitive_data.metadata.json | 39 ++
...orage_container_metadata_sensitive_data.py | 64 +++
.../__init__.py | 0
...ner_public_read_acl_disabled.metadata.json | 37 ++
...rage_container_public_read_acl_disabled.py | 29 ++
.../__init__.py | 0
...e_container_sync_not_enabled.metadata.json | 37 ++
...bjectstorage_container_sync_not_enabled.py | 28 ++
.../__init__.py | 0
...container_versioning_enabled.metadata.json | 37 ++
...ectstorage_container_versioning_enabled.py | 30 ++
.../__init__.py | 0
...ntainer_write_acl_restricted.metadata.json | 38 ++
...tstorage_container_write_acl_restricted.py | 29 ++
.../objectstorage/objectstorage_service.py | 92 ++++
..._container_acl_not_globally_shared_test.py | 283 ++++++++++++
...storage_container_listing_disabled_test.py | 334 +++++++++++++++
..._container_metadata_sensitive_data_test.py | 243 +++++++++++
...container_public_read_acl_disabled_test.py | 245 +++++++++++
...storage_container_sync_not_enabled_test.py | 199 +++++++++
...orage_container_versioning_enabled_test.py | 249 +++++++++++
...age_container_write_acl_restricted_test.py | 329 ++++++++++++++
.../openstack_objectstorage_service_test.py | 403 ++++++++++++++++++
33 files changed, 2887 insertions(+)
create mode 100644 prowler/providers/openstack/services/objectstorage/__init__.py
create mode 100644 prowler/providers/openstack/services/objectstorage/objectstorage_client.py
create mode 100644 prowler/providers/openstack/services/objectstorage/objectstorage_container_acl_not_globally_shared/__init__.py
create mode 100644 prowler/providers/openstack/services/objectstorage/objectstorage_container_acl_not_globally_shared/objectstorage_container_acl_not_globally_shared.metadata.json
create mode 100644 prowler/providers/openstack/services/objectstorage/objectstorage_container_acl_not_globally_shared/objectstorage_container_acl_not_globally_shared.py
create mode 100644 prowler/providers/openstack/services/objectstorage/objectstorage_container_listing_disabled/__init__.py
create mode 100644 prowler/providers/openstack/services/objectstorage/objectstorage_container_listing_disabled/objectstorage_container_listing_disabled.metadata.json
create mode 100644 prowler/providers/openstack/services/objectstorage/objectstorage_container_listing_disabled/objectstorage_container_listing_disabled.py
create mode 100644 prowler/providers/openstack/services/objectstorage/objectstorage_container_metadata_sensitive_data/__init__.py
create mode 100644 prowler/providers/openstack/services/objectstorage/objectstorage_container_metadata_sensitive_data/objectstorage_container_metadata_sensitive_data.metadata.json
create mode 100644 prowler/providers/openstack/services/objectstorage/objectstorage_container_metadata_sensitive_data/objectstorage_container_metadata_sensitive_data.py
create mode 100644 prowler/providers/openstack/services/objectstorage/objectstorage_container_public_read_acl_disabled/__init__.py
create mode 100644 prowler/providers/openstack/services/objectstorage/objectstorage_container_public_read_acl_disabled/objectstorage_container_public_read_acl_disabled.metadata.json
create mode 100644 prowler/providers/openstack/services/objectstorage/objectstorage_container_public_read_acl_disabled/objectstorage_container_public_read_acl_disabled.py
create mode 100644 prowler/providers/openstack/services/objectstorage/objectstorage_container_sync_not_enabled/__init__.py
create mode 100644 prowler/providers/openstack/services/objectstorage/objectstorage_container_sync_not_enabled/objectstorage_container_sync_not_enabled.metadata.json
create mode 100644 prowler/providers/openstack/services/objectstorage/objectstorage_container_sync_not_enabled/objectstorage_container_sync_not_enabled.py
create mode 100644 prowler/providers/openstack/services/objectstorage/objectstorage_container_versioning_enabled/__init__.py
create mode 100644 prowler/providers/openstack/services/objectstorage/objectstorage_container_versioning_enabled/objectstorage_container_versioning_enabled.metadata.json
create mode 100644 prowler/providers/openstack/services/objectstorage/objectstorage_container_versioning_enabled/objectstorage_container_versioning_enabled.py
create mode 100644 prowler/providers/openstack/services/objectstorage/objectstorage_container_write_acl_restricted/__init__.py
create mode 100644 prowler/providers/openstack/services/objectstorage/objectstorage_container_write_acl_restricted/objectstorage_container_write_acl_restricted.metadata.json
create mode 100644 prowler/providers/openstack/services/objectstorage/objectstorage_container_write_acl_restricted/objectstorage_container_write_acl_restricted.py
create mode 100644 prowler/providers/openstack/services/objectstorage/objectstorage_service.py
create mode 100644 tests/providers/openstack/services/objectstorage/objectstorage_container_acl_not_globally_shared/objectstorage_container_acl_not_globally_shared_test.py
create mode 100644 tests/providers/openstack/services/objectstorage/objectstorage_container_listing_disabled/objectstorage_container_listing_disabled_test.py
create mode 100644 tests/providers/openstack/services/objectstorage/objectstorage_container_metadata_sensitive_data/objectstorage_container_metadata_sensitive_data_test.py
create mode 100644 tests/providers/openstack/services/objectstorage/objectstorage_container_public_read_acl_disabled/objectstorage_container_public_read_acl_disabled_test.py
create mode 100644 tests/providers/openstack/services/objectstorage/objectstorage_container_sync_not_enabled/objectstorage_container_sync_not_enabled_test.py
create mode 100644 tests/providers/openstack/services/objectstorage/objectstorage_container_versioning_enabled/objectstorage_container_versioning_enabled_test.py
create mode 100644 tests/providers/openstack/services/objectstorage/objectstorage_container_write_acl_restricted/objectstorage_container_write_acl_restricted_test.py
create mode 100644 tests/providers/openstack/services/objectstorage/openstack_objectstorage_service_test.py
diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md
index f64f8f8fde..6ce203d0b2 100644
--- a/prowler/CHANGELOG.md
+++ b/prowler/CHANGELOG.md
@@ -10,6 +10,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- `entra_conditional_access_policy_compliant_device_hybrid_joined_device_mfa_required` check for M365 provider [(#10197)](https://github.com/prowler-cloud/prowler/pull/10197)
- Add `trusted_ips` configurable option to `opensearch_service_domains_not_publicly_accessible` check to reduce false positives on IP-restricted policies [(#8631)](https://github.com/prowler-cloud/prowler/pull/8631)
- `guardduty_delegated_admin_enabled_all_regions` check for AWS provider [(#9867)](https://github.com/prowler-cloud/prowler/pull/9867)
+- OpenStack object storage service with 7 checks [(#10258)](https://github.com/prowler-cloud/prowler/pull/10258)
### 🔄 Changed
diff --git a/prowler/providers/openstack/services/objectstorage/__init__.py b/prowler/providers/openstack/services/objectstorage/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/prowler/providers/openstack/services/objectstorage/objectstorage_client.py b/prowler/providers/openstack/services/objectstorage/objectstorage_client.py
new file mode 100644
index 0000000000..b858f90176
--- /dev/null
+++ b/prowler/providers/openstack/services/objectstorage/objectstorage_client.py
@@ -0,0 +1,6 @@
+from prowler.providers.common.provider import Provider
+from prowler.providers.openstack.services.objectstorage.objectstorage_service import (
+ ObjectStorage,
+)
+
+objectstorage_client = ObjectStorage(Provider.get_global_provider())
diff --git a/prowler/providers/openstack/services/objectstorage/objectstorage_container_acl_not_globally_shared/__init__.py b/prowler/providers/openstack/services/objectstorage/objectstorage_container_acl_not_globally_shared/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/prowler/providers/openstack/services/objectstorage/objectstorage_container_acl_not_globally_shared/objectstorage_container_acl_not_globally_shared.metadata.json b/prowler/providers/openstack/services/objectstorage/objectstorage_container_acl_not_globally_shared/objectstorage_container_acl_not_globally_shared.metadata.json
new file mode 100644
index 0000000000..02d66edfa5
--- /dev/null
+++ b/prowler/providers/openstack/services/objectstorage/objectstorage_container_acl_not_globally_shared/objectstorage_container_acl_not_globally_shared.metadata.json
@@ -0,0 +1,37 @@
+{
+ "Provider": "openstack",
+ "CheckID": "objectstorage_container_acl_not_globally_shared",
+ "CheckTitle": "Object storage container read ACL is not globally shared",
+ "CheckType": [],
+ "ServiceName": "objectstorage",
+ "SubServiceName": "",
+ "ResourceIdTemplate": "",
+ "Severity": "high",
+ "ResourceType": "OS::Swift::Container",
+ "ResourceGroup": "storage",
+ "Description": "**OpenStack Swift containers** are evaluated to verify that the **read ACL** does not use `*:*` (global sharing). The `*:*` ACL grants read access to all authenticated users from any project in the OpenStack deployment, violating project isolation and the principle of least privilege.",
+ "Risk": "Containers with `*:*` read ACL are accessible to **every authenticated user** in the OpenStack deployment, regardless of their project. This breaks **multi-tenant isolation**, exposing data to users in other projects. **Compromised credentials** from any project can access the container's contents.",
+ "RelatedUrl": "",
+ "AdditionalURLs": [
+ "https://docs.openstack.org/swift/latest/overview_acl.html",
+ "https://docs.openstack.org/security-guide/object-storage.html"
+ ],
+ "Remediation": {
+ "Code": {
+ "CLI": "swift post --read-acl ':*'",
+ "NativeIaC": "",
+ "Other": "1. Navigate to **Object Store > Containers**\n2. Select the container with global read ACL\n3. Edit the container metadata\n4. Replace `*:*` with specific project-scoped ACLs\n5. Save changes",
+ "Terraform": ""
+ },
+ "Recommendation": {
+ "Text": "Replace `*:*` read ACLs with **project-scoped ACLs** (e.g., `project-id:*` or `project-id:user-id`). Use the **most restrictive ACL** that meets access requirements. Implement regular ACL audits to detect overly permissive configurations. Consider using **Keystone role-based access control** for fine-grained permissions.",
+ "Url": "https://hub.prowler.com/check/objectstorage_container_acl_not_globally_shared"
+ }
+ },
+ "Categories": [
+ "identity-access"
+ ],
+ "DependsOn": [],
+ "RelatedTo": [],
+ "Notes": "The `*:*` ACL means 'any user in any project'. This is different from `.r:*` which grants anonymous (unauthenticated) access. Both are overly permissive but target different audiences: `*:*` targets all authenticated users while `.r:*` targets everyone including unauthenticated users."
+}
diff --git a/prowler/providers/openstack/services/objectstorage/objectstorage_container_acl_not_globally_shared/objectstorage_container_acl_not_globally_shared.py b/prowler/providers/openstack/services/objectstorage/objectstorage_container_acl_not_globally_shared/objectstorage_container_acl_not_globally_shared.py
new file mode 100644
index 0000000000..e5c21616d8
--- /dev/null
+++ b/prowler/providers/openstack/services/objectstorage/objectstorage_container_acl_not_globally_shared/objectstorage_container_acl_not_globally_shared.py
@@ -0,0 +1,29 @@
+from typing import List
+
+from prowler.lib.check.models import Check, CheckReportOpenStack
+from prowler.providers.openstack.services.objectstorage.objectstorage_client import (
+ objectstorage_client,
+)
+
+
+class objectstorage_container_acl_not_globally_shared(Check):
+ """Ensure object storage container read ACL does not use global sharing."""
+
+ def execute(self) -> List[CheckReportOpenStack]:
+ findings: List[CheckReportOpenStack] = []
+
+ for container in objectstorage_client.containers:
+ report = CheckReportOpenStack(metadata=self.metadata(), resource=container)
+ acl_entries = [entry.strip() for entry in container.read_ACL.split(",")]
+ if "*:*" in acl_entries or "*" in acl_entries:
+ report.status = "FAIL"
+ report.status_extended = f"Container {container.name} has globally shared read ACL (*:*) allowing all authenticated users from any project."
+ else:
+ report.status = "PASS"
+ report.status_extended = (
+ f"Container {container.name} read ACL is not globally shared."
+ )
+
+ findings.append(report)
+
+ return findings
diff --git a/prowler/providers/openstack/services/objectstorage/objectstorage_container_listing_disabled/__init__.py b/prowler/providers/openstack/services/objectstorage/objectstorage_container_listing_disabled/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/prowler/providers/openstack/services/objectstorage/objectstorage_container_listing_disabled/objectstorage_container_listing_disabled.metadata.json b/prowler/providers/openstack/services/objectstorage/objectstorage_container_listing_disabled/objectstorage_container_listing_disabled.metadata.json
new file mode 100644
index 0000000000..446d01a74b
--- /dev/null
+++ b/prowler/providers/openstack/services/objectstorage/objectstorage_container_listing_disabled/objectstorage_container_listing_disabled.metadata.json
@@ -0,0 +1,37 @@
+{
+ "Provider": "openstack",
+ "CheckID": "objectstorage_container_listing_disabled",
+ "CheckTitle": "Object storage container listings are not publicly accessible",
+ "CheckType": [],
+ "ServiceName": "objectstorage",
+ "SubServiceName": "",
+ "ResourceIdTemplate": "",
+ "Severity": "medium",
+ "ResourceType": "OS::Swift::Container",
+ "ResourceGroup": "storage",
+ "Description": "**OpenStack Swift containers** are evaluated to verify that **public container listings** are disabled. The `.rlistings` ACL allows users with read access to list all objects within the container. When combined with `.r:*`, this enables unauthenticated users to enumerate all objects, facilitating targeted data exfiltration.",
+ "Risk": "Containers with `.rlistings` enabled allow attackers to enumerate all object names, sizes, and modification dates. This **metadata exposure** aids **targeted attacks**, reveals application structure, and can expose sensitive file names. Combined with public read access, it enables complete **data exfiltration**.",
+ "RelatedUrl": "",
+ "AdditionalURLs": [
+ "https://docs.openstack.org/swift/latest/overview_acl.html",
+ "https://docs.openstack.org/security-guide/object-storage.html"
+ ],
+ "Remediation": {
+ "Code": {
+ "CLI": "swift post --read-acl ''",
+ "NativeIaC": "",
+ "Other": "1. Navigate to **Object Store > Containers**\n2. Select the container with public listing\n3. Edit the container metadata\n4. Remove `.rlistings` from the Read ACL\n5. Save changes",
+ "Terraform": ""
+ },
+ "Recommendation": {
+ "Text": "Remove `.rlistings` from container read ACLs to prevent **public object enumeration**. Listings should only be available to **authenticated project members**. If external access is needed, implement application-level access control with authenticated APIs.",
+ "Url": "https://hub.prowler.com/check/objectstorage_container_listing_disabled"
+ }
+ },
+ "Categories": [
+ "internet-exposed"
+ ],
+ "DependsOn": [],
+ "RelatedTo": [],
+ "Notes": "The `.rlistings` ACL controls whether container listings (GET on container) are allowed. Even without `.rlistings`, individual objects may still be readable if `.r:*` is set. Both ACLs should be removed for full protection."
+}
diff --git a/prowler/providers/openstack/services/objectstorage/objectstorage_container_listing_disabled/objectstorage_container_listing_disabled.py b/prowler/providers/openstack/services/objectstorage/objectstorage_container_listing_disabled/objectstorage_container_listing_disabled.py
new file mode 100644
index 0000000000..a541b9866e
--- /dev/null
+++ b/prowler/providers/openstack/services/objectstorage/objectstorage_container_listing_disabled/objectstorage_container_listing_disabled.py
@@ -0,0 +1,32 @@
+from typing import List
+
+from prowler.lib.check.models import Check, CheckReportOpenStack
+from prowler.providers.openstack.services.objectstorage.objectstorage_client import (
+ objectstorage_client,
+)
+
+
+class objectstorage_container_listing_disabled(Check):
+ """Ensure object storage container object listings are not publicly accessible."""
+
+ def execute(self) -> List[CheckReportOpenStack]:
+ findings: List[CheckReportOpenStack] = []
+
+ for container in objectstorage_client.containers:
+ report = CheckReportOpenStack(metadata=self.metadata(), resource=container)
+ acl_entries = [entry.strip() for entry in container.read_ACL.split(",")]
+ if ".rlistings" in acl_entries:
+ report.status = "FAIL"
+ report.status_extended = f"Container {container.name} has public listing enabled (.rlistings) allowing anonymous object enumeration."
+ elif "*:*" in acl_entries or "*" in acl_entries:
+ report.status = "FAIL"
+ report.status_extended = f"Container {container.name} has listing enabled via global read ACL (*:*) allowing all authenticated users to list objects."
+ else:
+ report.status = "PASS"
+ report.status_extended = (
+ f"Container {container.name} does not have public listing enabled."
+ )
+
+ findings.append(report)
+
+ return findings
diff --git a/prowler/providers/openstack/services/objectstorage/objectstorage_container_metadata_sensitive_data/__init__.py b/prowler/providers/openstack/services/objectstorage/objectstorage_container_metadata_sensitive_data/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/prowler/providers/openstack/services/objectstorage/objectstorage_container_metadata_sensitive_data/objectstorage_container_metadata_sensitive_data.metadata.json b/prowler/providers/openstack/services/objectstorage/objectstorage_container_metadata_sensitive_data/objectstorage_container_metadata_sensitive_data.metadata.json
new file mode 100644
index 0000000000..b3a39bd3f8
--- /dev/null
+++ b/prowler/providers/openstack/services/objectstorage/objectstorage_container_metadata_sensitive_data/objectstorage_container_metadata_sensitive_data.metadata.json
@@ -0,0 +1,39 @@
+{
+ "Provider": "openstack",
+ "CheckID": "objectstorage_container_metadata_sensitive_data",
+ "CheckTitle": "Object storage container metadata does not contain sensitive data",
+ "CheckType": [],
+ "ServiceName": "objectstorage",
+ "SubServiceName": "",
+ "ResourceIdTemplate": "",
+ "Severity": "critical",
+ "ResourceType": "OS::Swift::Container",
+ "ResourceGroup": "storage",
+ "Description": "**OpenStack Swift container metadata** is evaluated to detect **sensitive data** such as passwords, API keys, secrets, and private keys. Container metadata is accessible to any user with read access to the container. Storing secrets in metadata exposes them to unauthorized access and credential theft.",
+ "Risk": "Container metadata containing **sensitive data** exposes credentials to any user with container read access. Attackers with read permissions can extract **passwords**, **API keys**, and **private keys**. Stolen credentials enable unauthorized access to other systems, **data exfiltration**, and **privilege escalation**.",
+ "RelatedUrl": "",
+ "AdditionalURLs": [
+ "https://docs.openstack.org/swift/latest/overview_acl.html",
+ "https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html"
+ ],
+ "Remediation": {
+ "Code": {
+ "CLI": "swift post --meta ':'",
+ "NativeIaC": "",
+ "Other": "1. Identify containers with sensitive metadata\n2. Remove sensitive metadata keys using CLI\n3. Rotate exposed credentials immediately\n4. Store secrets in Barbican or external secrets manager instead",
+ "Terraform": ""
+ },
+ "Recommendation": {
+ "Text": "Never store secrets in container metadata. Use **Barbican** (OpenStack Key Manager), **Vault**, or external secrets management instead. Remove existing sensitive metadata and **rotate any exposed credentials**. Implement metadata policies to prevent future secret storage.",
+ "Url": "https://hub.prowler.com/check/objectstorage_container_metadata_sensitive_data"
+ }
+ },
+ "Categories": [
+ "threat-detection",
+ "secrets",
+ "encryption"
+ ],
+ "DependsOn": [],
+ "RelatedTo": [],
+ "Notes": "This check uses the detect-secrets library to scan for credentials. May produce false positives on metadata keys containing secret-like keywords. Findings should be reviewed manually. The audit_config allows configuring secrets_ignore_patterns to exclude specific patterns and detect_secrets_plugins to customize detection."
+}
diff --git a/prowler/providers/openstack/services/objectstorage/objectstorage_container_metadata_sensitive_data/objectstorage_container_metadata_sensitive_data.py b/prowler/providers/openstack/services/objectstorage/objectstorage_container_metadata_sensitive_data/objectstorage_container_metadata_sensitive_data.py
new file mode 100644
index 0000000000..94d281bf32
--- /dev/null
+++ b/prowler/providers/openstack/services/objectstorage/objectstorage_container_metadata_sensitive_data/objectstorage_container_metadata_sensitive_data.py
@@ -0,0 +1,64 @@
+import json
+from typing import List
+
+from prowler.lib.check.models import Check, CheckReportOpenStack
+from prowler.lib.utils.utils import detect_secrets_scan
+from prowler.providers.openstack.services.objectstorage.objectstorage_client import (
+ objectstorage_client,
+)
+
+
+class objectstorage_container_metadata_sensitive_data(Check):
+ """Ensure object storage container metadata does not contain sensitive data like passwords or API keys."""
+
+ def execute(self) -> List[CheckReportOpenStack]:
+ findings: List[CheckReportOpenStack] = []
+ secrets_ignore_patterns = objectstorage_client.audit_config.get(
+ "secrets_ignore_patterns", []
+ )
+
+ for container in objectstorage_client.containers:
+ report = CheckReportOpenStack(metadata=self.metadata(), resource=container)
+ report.status = "PASS"
+ report.status_extended = (
+ f"Container {container.name} metadata does not contain sensitive data."
+ )
+
+ if container.metadata:
+ # Build metadata dict and parallel list of keys
+ dump_metadata = {}
+ original_metadata_keys = []
+ for key, value in container.metadata.items():
+ dump_metadata[key] = value
+ original_metadata_keys.append(key)
+
+ # Convert metadata dict to JSON string for detect-secrets scanning
+ metadata_json = json.dumps(dump_metadata, indent=2)
+ detect_secrets_output = detect_secrets_scan(
+ data=metadata_json,
+ excluded_secrets=secrets_ignore_patterns,
+ detect_secrets_plugins=objectstorage_client.audit_config.get(
+ "detect_secrets_plugins"
+ ),
+ )
+
+ if detect_secrets_output:
+ # Map line numbers back to metadata keys using the parallel list
+ # Line numbering: line 1 = "{", line 2 = first key-value, etc.
+ secrets_string = ", ".join(
+ [
+ f"{secret['type']} in metadata key '{original_metadata_keys[secret['line_number'] - 2]}'"
+ for secret in detect_secrets_output
+ if 0
+ <= secret["line_number"] - 2
+ < len(original_metadata_keys)
+ ]
+ )
+ report.status = "FAIL"
+ report.status_extended = f"Container {container.name} metadata contains potential secrets -> {secrets_string}."
+ else:
+ report.status_extended = f"Container {container.name} has no metadata (no sensitive data exposure risk)."
+
+ findings.append(report)
+
+ return findings
diff --git a/prowler/providers/openstack/services/objectstorage/objectstorage_container_public_read_acl_disabled/__init__.py b/prowler/providers/openstack/services/objectstorage/objectstorage_container_public_read_acl_disabled/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/prowler/providers/openstack/services/objectstorage/objectstorage_container_public_read_acl_disabled/objectstorage_container_public_read_acl_disabled.metadata.json b/prowler/providers/openstack/services/objectstorage/objectstorage_container_public_read_acl_disabled/objectstorage_container_public_read_acl_disabled.metadata.json
new file mode 100644
index 0000000000..f15becbcf5
--- /dev/null
+++ b/prowler/providers/openstack/services/objectstorage/objectstorage_container_public_read_acl_disabled/objectstorage_container_public_read_acl_disabled.metadata.json
@@ -0,0 +1,37 @@
+{
+ "Provider": "openstack",
+ "CheckID": "objectstorage_container_public_read_acl_disabled",
+ "CheckTitle": "Object storage containers do not grant anonymous read access",
+ "CheckType": [],
+ "ServiceName": "objectstorage",
+ "SubServiceName": "",
+ "ResourceIdTemplate": "",
+ "Severity": "high",
+ "ResourceType": "OS::Swift::Container",
+ "ResourceGroup": "storage",
+ "Description": "**OpenStack Swift containers** are evaluated to verify that **anonymous read access** is not granted. The `.r:*` ACL allows any unauthenticated user on the internet to read objects in the container. This is a critical exposure that can lead to data leaks, especially when containers hold sensitive information such as backups, logs, or application data.",
+ "Risk": "Containers with `.r:*` in the read ACL are **publicly accessible** without authentication. Attackers can enumerate and download all objects, leading to **data breaches**, **credential exposure**, and **compliance violations**. Public containers are frequently targeted by automated scanners.",
+ "RelatedUrl": "",
+ "AdditionalURLs": [
+ "https://docs.openstack.org/swift/latest/overview_acl.html",
+ "https://docs.openstack.org/security-guide/object-storage.html"
+ ],
+ "Remediation": {
+ "Code": {
+ "CLI": "swift post --read-acl ''",
+ "NativeIaC": "",
+ "Other": "1. Navigate to **Object Store > Containers**\n2. Select the container with public read ACL\n3. Edit the container metadata\n4. Remove the `.r:*` entry from the Read ACL\n5. Save changes",
+ "Terraform": ""
+ },
+ "Recommendation": {
+ "Text": "Remove `.r:*` from container read ACLs to prevent **anonymous access**. Use **project-scoped ACLs** (e.g., `project-id:*`) or specific user ACLs instead. If public access is required, consider using a **CDN** or **signed URLs** with time-limited tokens.",
+ "Url": "https://hub.prowler.com/check/objectstorage_container_public_read_acl_disabled"
+ }
+ },
+ "Categories": [
+ "internet-exposed"
+ ],
+ "DependsOn": [],
+ "RelatedTo": [],
+ "Notes": "Swift ACLs use a specific syntax where `.r:*` grants read access to all referrers (anonymous access). The `.r:` prefix indicates referrer-based ACL. This check specifically looks for the wildcard referrer pattern."
+}
diff --git a/prowler/providers/openstack/services/objectstorage/objectstorage_container_public_read_acl_disabled/objectstorage_container_public_read_acl_disabled.py b/prowler/providers/openstack/services/objectstorage/objectstorage_container_public_read_acl_disabled/objectstorage_container_public_read_acl_disabled.py
new file mode 100644
index 0000000000..6a3b5c03ec
--- /dev/null
+++ b/prowler/providers/openstack/services/objectstorage/objectstorage_container_public_read_acl_disabled/objectstorage_container_public_read_acl_disabled.py
@@ -0,0 +1,29 @@
+from typing import List
+
+from prowler.lib.check.models import Check, CheckReportOpenStack
+from prowler.providers.openstack.services.objectstorage.objectstorage_client import (
+ objectstorage_client,
+)
+
+
+class objectstorage_container_public_read_acl_disabled(Check):
+ """Ensure object storage containers do not grant anonymous read access."""
+
+ def execute(self) -> List[CheckReportOpenStack]:
+ findings: List[CheckReportOpenStack] = []
+
+ for container in objectstorage_client.containers:
+ report = CheckReportOpenStack(metadata=self.metadata(), resource=container)
+ acl_entries = [entry.strip() for entry in container.read_ACL.split(",")]
+ if ".r:*" in acl_entries:
+ report.status = "FAIL"
+ report.status_extended = f"Container {container.name} has public read ACL (.r:*) allowing anonymous access."
+ else:
+ report.status = "PASS"
+ report.status_extended = (
+ f"Container {container.name} does not have public read ACL."
+ )
+
+ findings.append(report)
+
+ return findings
diff --git a/prowler/providers/openstack/services/objectstorage/objectstorage_container_sync_not_enabled/__init__.py b/prowler/providers/openstack/services/objectstorage/objectstorage_container_sync_not_enabled/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/prowler/providers/openstack/services/objectstorage/objectstorage_container_sync_not_enabled/objectstorage_container_sync_not_enabled.metadata.json b/prowler/providers/openstack/services/objectstorage/objectstorage_container_sync_not_enabled/objectstorage_container_sync_not_enabled.metadata.json
new file mode 100644
index 0000000000..35925433c5
--- /dev/null
+++ b/prowler/providers/openstack/services/objectstorage/objectstorage_container_sync_not_enabled/objectstorage_container_sync_not_enabled.metadata.json
@@ -0,0 +1,37 @@
+{
+ "Provider": "openstack",
+ "CheckID": "objectstorage_container_sync_not_enabled",
+ "CheckTitle": "Object storage containers do not have container sync enabled",
+ "CheckType": [],
+ "ServiceName": "objectstorage",
+ "SubServiceName": "",
+ "ResourceIdTemplate": "",
+ "Severity": "medium",
+ "ResourceType": "OS::Swift::Container",
+ "ResourceGroup": "storage",
+ "Description": "**OpenStack Swift containers** are evaluated to verify that **container sync** is not configured. Container sync replicates objects to another Swift cluster, potentially outside the organization's control. Unauthorized sync configurations can be used for data exfiltration to external clusters.",
+ "Risk": "Container sync replicates all objects to a **remote Swift cluster**. If misconfigured or set up by an attacker, sensitive data is continuously **exfiltrated** to an external location. The sync operates automatically and silently, making it difficult to detect ongoing **data theft**.",
+ "RelatedUrl": "",
+ "AdditionalURLs": [
+ "https://docs.openstack.org/swift/latest/overview_container_sync.html",
+ "https://docs.openstack.org/security-guide/object-storage.html"
+ ],
+ "Remediation": {
+ "Code": {
+ "CLI": "swift post -H 'X-Container-Sync-To:' -H 'X-Container-Sync-Key:'",
+ "NativeIaC": "",
+ "Other": "1. Identify containers with sync enabled\n2. Remove sync configuration using CLI\n3. Verify sync is disabled: `swift stat `\n4. Review sync logs for unauthorized data transfers\n5. Investigate who configured the sync and when",
+ "Terraform": ""
+ },
+ "Recommendation": {
+ "Text": "Disable container sync unless explicitly required for **disaster recovery**. If sync is needed, ensure the target cluster is within your organization's **trust boundary**. Monitor sync configurations for unauthorized changes. Implement **RBAC policies** to restrict who can configure container sync.",
+ "Url": "https://hub.prowler.com/check/objectstorage_container_sync_not_enabled"
+ }
+ },
+ "Categories": [
+ "trust-boundaries"
+ ],
+ "DependsOn": [],
+ "RelatedTo": [],
+ "Notes": "Container sync is configured via X-Container-Sync-To (target URL) and X-Container-Sync-Key (shared secret) headers. The sync middleware must be enabled in the Swift pipeline for sync to function. This check flags any container with a non-empty sync_to value."
+}
diff --git a/prowler/providers/openstack/services/objectstorage/objectstorage_container_sync_not_enabled/objectstorage_container_sync_not_enabled.py b/prowler/providers/openstack/services/objectstorage/objectstorage_container_sync_not_enabled/objectstorage_container_sync_not_enabled.py
new file mode 100644
index 0000000000..ac7545e69e
--- /dev/null
+++ b/prowler/providers/openstack/services/objectstorage/objectstorage_container_sync_not_enabled/objectstorage_container_sync_not_enabled.py
@@ -0,0 +1,28 @@
+from typing import List
+
+from prowler.lib.check.models import Check, CheckReportOpenStack
+from prowler.providers.openstack.services.objectstorage.objectstorage_client import (
+ objectstorage_client,
+)
+
+
+class objectstorage_container_sync_not_enabled(Check):
+ """Ensure object storage containers do not have container sync configured."""
+
+ def execute(self) -> List[CheckReportOpenStack]:
+ findings: List[CheckReportOpenStack] = []
+
+ for container in objectstorage_client.containers:
+ report = CheckReportOpenStack(metadata=self.metadata(), resource=container)
+ if container.sync_to:
+ report.status = "FAIL"
+ report.status_extended = f"Container {container.name} has container sync enabled (sync target: {container.sync_to})."
+ else:
+ report.status = "PASS"
+ report.status_extended = (
+ f"Container {container.name} does not have container sync enabled."
+ )
+
+ findings.append(report)
+
+ return findings
diff --git a/prowler/providers/openstack/services/objectstorage/objectstorage_container_versioning_enabled/__init__.py b/prowler/providers/openstack/services/objectstorage/objectstorage_container_versioning_enabled/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/prowler/providers/openstack/services/objectstorage/objectstorage_container_versioning_enabled/objectstorage_container_versioning_enabled.metadata.json b/prowler/providers/openstack/services/objectstorage/objectstorage_container_versioning_enabled/objectstorage_container_versioning_enabled.metadata.json
new file mode 100644
index 0000000000..568e7c252f
--- /dev/null
+++ b/prowler/providers/openstack/services/objectstorage/objectstorage_container_versioning_enabled/objectstorage_container_versioning_enabled.metadata.json
@@ -0,0 +1,37 @@
+{
+ "Provider": "openstack",
+ "CheckID": "objectstorage_container_versioning_enabled",
+ "CheckTitle": "Object storage containers have versioning enabled",
+ "CheckType": [],
+ "ServiceName": "objectstorage",
+ "SubServiceName": "",
+ "ResourceIdTemplate": "",
+ "Severity": "medium",
+ "ResourceType": "OS::Swift::Container",
+ "ResourceGroup": "storage",
+ "Description": "**OpenStack Swift containers** are evaluated to verify that **object versioning** is enabled. Versioning preserves previous versions of objects when they are overwritten or deleted, enabling recovery from accidental modifications, ransomware attacks, and data corruption.",
+ "Risk": "Without versioning, overwritten or deleted objects **cannot be recovered**. Accidental deletions, **ransomware** encrypting objects, or malicious modifications result in **permanent data loss**. Versioning provides a safety net for forensic investigation and disaster recovery.",
+ "RelatedUrl": "",
+ "AdditionalURLs": [
+ "https://docs.openstack.org/swift/latest/overview_object_versioning.html",
+ "https://docs.openstack.org/security-guide/object-storage.html"
+ ],
+ "Remediation": {
+ "Code": {
+ "CLI": "swift post -H 'X-Versions-Location: '",
+ "NativeIaC": "",
+ "Other": "1. Create a versions container: `swift post _versions`\n2. Enable versioning (stack mode): `swift post -H 'X-Versions-Location: _versions'`\n Or enable versioning (history mode): `swift post -H 'X-History-Location: _versions'`\n3. Verify: `swift stat ` shows Versions header",
+ "Terraform": ""
+ },
+ "Recommendation": {
+ "Text": "Enable **object versioning** on all containers storing important data. Create a dedicated versions container and set the `X-Versions-Location` (stack mode) or `X-History-Location` (history mode) header. Implement **lifecycle policies** to manage version retention and storage costs. Combine with container backups for comprehensive data protection.",
+ "Url": "https://hub.prowler.com/check/objectstorage_container_versioning_enabled"
+ }
+ },
+ "Categories": [
+ "forensics-ready"
+ ],
+ "DependsOn": [],
+ "RelatedTo": [],
+ "Notes": "Swift supports two versioning modes: X-Versions-Location (stack mode, stores versions in a separate container) and X-History-Location (history mode). This check verifies that either versions_location or history_location is set, indicating versioning is configured."
+}
diff --git a/prowler/providers/openstack/services/objectstorage/objectstorage_container_versioning_enabled/objectstorage_container_versioning_enabled.py b/prowler/providers/openstack/services/objectstorage/objectstorage_container_versioning_enabled/objectstorage_container_versioning_enabled.py
new file mode 100644
index 0000000000..f2e811093f
--- /dev/null
+++ b/prowler/providers/openstack/services/objectstorage/objectstorage_container_versioning_enabled/objectstorage_container_versioning_enabled.py
@@ -0,0 +1,30 @@
+from typing import List
+
+from prowler.lib.check.models import Check, CheckReportOpenStack
+from prowler.providers.openstack.services.objectstorage.objectstorage_client import (
+ objectstorage_client,
+)
+
+
+class objectstorage_container_versioning_enabled(Check):
+ """Ensure object storage containers have versioning enabled for data protection."""
+
+ def execute(self) -> List[CheckReportOpenStack]:
+ findings: List[CheckReportOpenStack] = []
+
+ for container in objectstorage_client.containers:
+ report = CheckReportOpenStack(metadata=self.metadata(), resource=container)
+ if container.versioning_enabled:
+ report.status = "PASS"
+ location = container.versions_location or container.history_location
+ mode = "versions" if container.versions_location else "history"
+ report.status_extended = f"Container {container.name} has versioning enabled ({mode} location: {location})."
+ else:
+ report.status = "FAIL"
+ report.status_extended = (
+ f"Container {container.name} does not have versioning enabled."
+ )
+
+ findings.append(report)
+
+ return findings
diff --git a/prowler/providers/openstack/services/objectstorage/objectstorage_container_write_acl_restricted/__init__.py b/prowler/providers/openstack/services/objectstorage/objectstorage_container_write_acl_restricted/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/prowler/providers/openstack/services/objectstorage/objectstorage_container_write_acl_restricted/objectstorage_container_write_acl_restricted.metadata.json b/prowler/providers/openstack/services/objectstorage/objectstorage_container_write_acl_restricted/objectstorage_container_write_acl_restricted.metadata.json
new file mode 100644
index 0000000000..fa2af1334a
--- /dev/null
+++ b/prowler/providers/openstack/services/objectstorage/objectstorage_container_write_acl_restricted/objectstorage_container_write_acl_restricted.metadata.json
@@ -0,0 +1,38 @@
+{
+ "Provider": "openstack",
+ "CheckID": "objectstorage_container_write_acl_restricted",
+ "CheckTitle": "Object storage container write ACL is restricted",
+ "CheckType": [],
+ "ServiceName": "objectstorage",
+ "SubServiceName": "",
+ "ResourceIdTemplate": "",
+ "Severity": "critical",
+ "ResourceType": "OS::Swift::Container",
+ "ResourceGroup": "storage",
+ "Description": "**OpenStack Swift containers** are evaluated to verify that **write access** is restricted. The `*:*` write ACL allows any authenticated user from any project to create, modify, or delete objects. The `*` write ACL similarly grants unrestricted write access. Both configurations violate the principle of least privilege.",
+ "Risk": "Containers with **unrestricted write ACLs** allow any authenticated OpenStack user to upload, overwrite, or delete objects. This enables **data tampering**, **malware injection**, **ransomware attacks** (encrypting objects), and **resource abuse** (cryptomining payloads). Compromised credentials from any project can be used to modify data.",
+ "RelatedUrl": "",
+ "AdditionalURLs": [
+ "https://docs.openstack.org/swift/latest/overview_acl.html",
+ "https://docs.openstack.org/security-guide/object-storage.html"
+ ],
+ "Remediation": {
+ "Code": {
+ "CLI": "swift post --write-acl ':'",
+ "NativeIaC": "",
+ "Other": "1. Navigate to **Object Store > Containers**\n2. Select the container with unrestricted write ACL\n3. Edit the container metadata\n4. Replace `*:*` or `*` with specific `project-id:user-id` entries\n5. Save changes",
+ "Terraform": ""
+ },
+ "Recommendation": {
+ "Text": "Restrict write ACLs to specific project and user combinations (e.g., `project-id:user-id`). Never use `*:*` or `*` as write ACLs. Implement **RBAC policies** to control write access. Regularly audit container ACLs to detect **overly permissive configurations**.",
+ "Url": "https://hub.prowler.com/check/objectstorage_container_write_acl_restricted"
+ }
+ },
+ "Categories": [
+ "internet-exposed",
+ "identity-access"
+ ],
+ "DependsOn": [],
+ "RelatedTo": [],
+ "Notes": "The `*:*` ACL means 'any user in any project' while `*` alone also grants unrestricted access. Both are dangerous for write operations as they allow data modification by any authenticated user in the OpenStack deployment."
+}
diff --git a/prowler/providers/openstack/services/objectstorage/objectstorage_container_write_acl_restricted/objectstorage_container_write_acl_restricted.py b/prowler/providers/openstack/services/objectstorage/objectstorage_container_write_acl_restricted/objectstorage_container_write_acl_restricted.py
new file mode 100644
index 0000000000..0693ebedce
--- /dev/null
+++ b/prowler/providers/openstack/services/objectstorage/objectstorage_container_write_acl_restricted/objectstorage_container_write_acl_restricted.py
@@ -0,0 +1,29 @@
+from typing import List
+
+from prowler.lib.check.models import Check, CheckReportOpenStack
+from prowler.providers.openstack.services.objectstorage.objectstorage_client import (
+ objectstorage_client,
+)
+
+
+class objectstorage_container_write_acl_restricted(Check):
+ """Ensure object storage container write ACL does not allow all authenticated users."""
+
+ def execute(self) -> List[CheckReportOpenStack]:
+ findings: List[CheckReportOpenStack] = []
+
+ for container in objectstorage_client.containers:
+ report = CheckReportOpenStack(metadata=self.metadata(), resource=container)
+ acl_entries = [entry.strip() for entry in container.write_ACL.split(",")]
+ if "*:*" in acl_entries or "*" in acl_entries:
+ report.status = "FAIL"
+ report.status_extended = f"Container {container.name} has unrestricted write ACL allowing all authenticated users to write."
+ else:
+ report.status = "PASS"
+ report.status_extended = (
+ f"Container {container.name} has restricted write ACL."
+ )
+
+ findings.append(report)
+
+ return findings
diff --git a/prowler/providers/openstack/services/objectstorage/objectstorage_service.py b/prowler/providers/openstack/services/objectstorage/objectstorage_service.py
new file mode 100644
index 0000000000..6a2a73d5a0
--- /dev/null
+++ b/prowler/providers/openstack/services/objectstorage/objectstorage_service.py
@@ -0,0 +1,92 @@
+from __future__ import annotations
+
+from dataclasses import dataclass
+from typing import Dict, List
+
+from openstack import exceptions as openstack_exceptions
+
+from prowler.lib.logger import logger
+from prowler.providers.openstack.lib.service.service import OpenStackService
+
+
+class ObjectStorage(OpenStackService):
+ """Service wrapper using openstacksdk object-store APIs."""
+
+ def __init__(self, provider) -> None:
+ super().__init__(__class__.__name__, provider)
+ self.containers: List[ObjectStorageContainer] = []
+ self._list_containers()
+
+ def _list_containers(self) -> None:
+ """List all object storage containers across all audited regions."""
+ logger.info("ObjectStorage - Listing containers...")
+ for region, conn in self.regional_connections.items():
+ try:
+ for container in conn.object_store.containers():
+ # The list API only returns name/count/bytes; HEAD each
+ # container to retrieve ACLs, metadata, and versioning info.
+ try:
+ detail = conn.object_store.get_container_metadata(
+ getattr(container, "name", "")
+ )
+ except Exception as head_error:
+ logger.warning(
+ f"Could not HEAD container {getattr(container, 'name', '')}: {head_error}"
+ )
+ detail = container
+
+ metadata = getattr(detail, "metadata", None) or {}
+
+ # Extract versioning info (Swift supports two modes)
+ versions_location = getattr(detail, "versions_location", "") or ""
+ history_location = getattr(detail, "history_location", "") or ""
+ versioning_enabled = bool(versions_location or history_location)
+
+ self.containers.append(
+ ObjectStorageContainer(
+ id=getattr(container, "name", ""),
+ name=getattr(container, "name", ""),
+ region=region,
+ project_id=self.project_id,
+ object_count=getattr(detail, "count", 0),
+ bytes_used=getattr(detail, "bytes", 0),
+ read_ACL=getattr(detail, "read_ACL", "") or "",
+ write_ACL=getattr(detail, "write_ACL", "") or "",
+ versioning_enabled=versioning_enabled,
+ versions_location=versions_location,
+ history_location=history_location,
+ sync_to=getattr(detail, "sync_to", "") or "",
+ sync_key=getattr(detail, "sync_key", "") or "",
+ metadata=metadata if isinstance(metadata, dict) else {},
+ )
+ )
+ except openstack_exceptions.SDKException as error:
+ logger.error(
+ f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- "
+ f"Failed to list object storage containers in region {region}: {error}"
+ )
+ except Exception as error:
+ logger.error(
+ f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- "
+ f"Unexpected error listing object storage containers in region {region}: {error}"
+ )
+
+
+@dataclass
+class ObjectStorageContainer:
+ """Represents an OpenStack Swift container."""
+
+ id: str
+ name: str
+ region: str
+ project_id: str
+ object_count: int
+ bytes_used: int
+ read_ACL: str
+ write_ACL: str
+ versioning_enabled: bool
+ versions_location: str
+ history_location: str
+ sync_to: str
+ sync_key: str
+ metadata: Dict[str, str]
diff --git a/tests/providers/openstack/services/objectstorage/objectstorage_container_acl_not_globally_shared/objectstorage_container_acl_not_globally_shared_test.py b/tests/providers/openstack/services/objectstorage/objectstorage_container_acl_not_globally_shared/objectstorage_container_acl_not_globally_shared_test.py
new file mode 100644
index 0000000000..3e2cdd6f9b
--- /dev/null
+++ b/tests/providers/openstack/services/objectstorage/objectstorage_container_acl_not_globally_shared/objectstorage_container_acl_not_globally_shared_test.py
@@ -0,0 +1,283 @@
+"""Tests for objectstorage_container_acl_not_globally_shared check."""
+
+from unittest import mock
+
+from prowler.providers.openstack.services.objectstorage.objectstorage_service import (
+ ObjectStorageContainer,
+)
+from tests.providers.openstack.openstack_fixtures import (
+ OPENSTACK_PROJECT_ID,
+ OPENSTACK_REGION,
+ set_mocked_openstack_provider,
+)
+
+
+class Test_objectstorage_container_acl_not_globally_shared:
+ """Test suite for objectstorage_container_acl_not_globally_shared check."""
+
+ def test_no_containers(self):
+ """Test when no containers exist."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = []
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_acl_not_globally_shared.objectstorage_container_acl_not_globally_shared.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_acl_not_globally_shared.objectstorage_container_acl_not_globally_shared import (
+ objectstorage_container_acl_not_globally_shared,
+ )
+
+ check = objectstorage_container_acl_not_globally_shared()
+ result = check.execute()
+
+ assert len(result) == 0
+
+ def test_container_not_globally_shared(self):
+ """Test container without global sharing (PASS)."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-1",
+ name="project-scoped",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=10,
+ bytes_used=1024,
+ read_ACL="project-123:*",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ )
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_acl_not_globally_shared.objectstorage_container_acl_not_globally_shared.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_acl_not_globally_shared.objectstorage_container_acl_not_globally_shared import (
+ objectstorage_container_acl_not_globally_shared,
+ )
+
+ check = objectstorage_container_acl_not_globally_shared()
+ result = check.execute()
+
+ assert len(result) == 1
+ assert result[0].status == "PASS"
+ assert (
+ result[0].status_extended
+ == "Container project-scoped read ACL is not globally shared."
+ )
+ assert result[0].resource_id == "container-1"
+ assert result[0].resource_name == "project-scoped"
+ assert result[0].region == OPENSTACK_REGION
+ assert result[0].project_id == OPENSTACK_PROJECT_ID
+
+ def test_container_globally_shared(self):
+ """Test container with global sharing (FAIL)."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-2",
+ name="global-shared",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=5,
+ bytes_used=512,
+ read_ACL="*:*",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ )
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_acl_not_globally_shared.objectstorage_container_acl_not_globally_shared.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_acl_not_globally_shared.objectstorage_container_acl_not_globally_shared import (
+ objectstorage_container_acl_not_globally_shared,
+ )
+
+ check = objectstorage_container_acl_not_globally_shared()
+ result = check.execute()
+
+ assert len(result) == 1
+ assert result[0].status == "FAIL"
+ assert (
+ result[0].status_extended
+ == "Container global-shared has globally shared read ACL (*:*) allowing all authenticated users from any project."
+ )
+ assert result[0].resource_id == "container-2"
+ assert result[0].resource_name == "global-shared"
+ assert result[0].region == OPENSTACK_REGION
+ assert result[0].project_id == OPENSTACK_PROJECT_ID
+
+ def test_container_globally_shared_bare_wildcard(self):
+ """Test container with * (bare wildcard) read ACL (FAIL)."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-3",
+ name="bare-wildcard",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=0,
+ bytes_used=0,
+ read_ACL="*",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ )
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_acl_not_globally_shared.objectstorage_container_acl_not_globally_shared.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_acl_not_globally_shared.objectstorage_container_acl_not_globally_shared import (
+ objectstorage_container_acl_not_globally_shared,
+ )
+
+ check = objectstorage_container_acl_not_globally_shared()
+ result = check.execute()
+
+ assert len(result) == 1
+ assert result[0].status == "FAIL"
+
+ def test_container_star_colon_star_in_multi_entry_acl(self):
+ """Test container with *:* in multi-entry read ACL (FAIL)."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-4",
+ name="multi-entry-global",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=0,
+ bytes_used=0,
+ read_ACL="project-123:user-456,*:*",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ )
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_acl_not_globally_shared.objectstorage_container_acl_not_globally_shared.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_acl_not_globally_shared.objectstorage_container_acl_not_globally_shared import (
+ objectstorage_container_acl_not_globally_shared,
+ )
+
+ check = objectstorage_container_acl_not_globally_shared()
+ result = check.execute()
+
+ assert len(result) == 1
+ assert result[0].status == "FAIL"
+
+ def test_multiple_containers_mixed(self):
+ """Test multiple containers with mixed ACLs."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-pass",
+ name="Pass",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=0,
+ bytes_used=0,
+ read_ACL="",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ ),
+ ObjectStorageContainer(
+ id="container-fail",
+ name="Fail",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=0,
+ bytes_used=0,
+ read_ACL="*:*",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ ),
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_acl_not_globally_shared.objectstorage_container_acl_not_globally_shared.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_acl_not_globally_shared.objectstorage_container_acl_not_globally_shared import (
+ objectstorage_container_acl_not_globally_shared,
+ )
+
+ check = objectstorage_container_acl_not_globally_shared()
+ result = check.execute()
+
+ assert len(result) == 2
+ assert len([r for r in result if r.status == "PASS"]) == 1
+ assert len([r for r in result if r.status == "FAIL"]) == 1
diff --git a/tests/providers/openstack/services/objectstorage/objectstorage_container_listing_disabled/objectstorage_container_listing_disabled_test.py b/tests/providers/openstack/services/objectstorage/objectstorage_container_listing_disabled/objectstorage_container_listing_disabled_test.py
new file mode 100644
index 0000000000..73bd1bdaba
--- /dev/null
+++ b/tests/providers/openstack/services/objectstorage/objectstorage_container_listing_disabled/objectstorage_container_listing_disabled_test.py
@@ -0,0 +1,334 @@
+"""Tests for objectstorage_container_listing_disabled check."""
+
+from unittest import mock
+
+from prowler.providers.openstack.services.objectstorage.objectstorage_service import (
+ ObjectStorageContainer,
+)
+from tests.providers.openstack.openstack_fixtures import (
+ OPENSTACK_PROJECT_ID,
+ OPENSTACK_REGION,
+ set_mocked_openstack_provider,
+)
+
+
+class Test_objectstorage_container_listing_disabled:
+ """Test suite for objectstorage_container_listing_disabled check."""
+
+ def test_no_containers(self):
+ """Test when no containers exist."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = []
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_listing_disabled.objectstorage_container_listing_disabled.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_listing_disabled.objectstorage_container_listing_disabled import (
+ objectstorage_container_listing_disabled,
+ )
+
+ check = objectstorage_container_listing_disabled()
+ result = check.execute()
+
+ assert len(result) == 0
+
+ def test_container_no_listing(self):
+ """Test container without public listing (PASS)."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-1",
+ name="no-listing",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=10,
+ bytes_used=1024,
+ read_ACL="",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ )
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_listing_disabled.objectstorage_container_listing_disabled.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_listing_disabled.objectstorage_container_listing_disabled import (
+ objectstorage_container_listing_disabled,
+ )
+
+ check = objectstorage_container_listing_disabled()
+ result = check.execute()
+
+ assert len(result) == 1
+ assert result[0].status == "PASS"
+ assert (
+ result[0].status_extended
+ == "Container no-listing does not have public listing enabled."
+ )
+ assert result[0].resource_id == "container-1"
+ assert result[0].resource_name == "no-listing"
+ assert result[0].region == OPENSTACK_REGION
+ assert result[0].project_id == OPENSTACK_PROJECT_ID
+
+ def test_container_with_listing(self):
+ """Test container with public listing (FAIL)."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-2",
+ name="public-listing",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=5,
+ bytes_used=512,
+ read_ACL=".r:*,.rlistings",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ )
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_listing_disabled.objectstorage_container_listing_disabled.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_listing_disabled.objectstorage_container_listing_disabled import (
+ objectstorage_container_listing_disabled,
+ )
+
+ check = objectstorage_container_listing_disabled()
+ result = check.execute()
+
+ assert len(result) == 1
+ assert result[0].status == "FAIL"
+ assert (
+ result[0].status_extended
+ == "Container public-listing has public listing enabled (.rlistings) allowing anonymous object enumeration."
+ )
+ assert result[0].resource_id == "container-2"
+ assert result[0].resource_name == "public-listing"
+ assert result[0].region == OPENSTACK_REGION
+ assert result[0].project_id == OPENSTACK_PROJECT_ID
+
+ def test_container_listing_via_global_acl_star_colon_star(self):
+ """Test container with *:* read ACL enabling listing (FAIL)."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-3",
+ name="global-acl-listing",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=5,
+ bytes_used=512,
+ read_ACL="*:*",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ )
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_listing_disabled.objectstorage_container_listing_disabled.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_listing_disabled.objectstorage_container_listing_disabled import (
+ objectstorage_container_listing_disabled,
+ )
+
+ check = objectstorage_container_listing_disabled()
+ result = check.execute()
+
+ assert len(result) == 1
+ assert result[0].status == "FAIL"
+ assert (
+ result[0].status_extended
+ == "Container global-acl-listing has listing enabled via global read ACL (*:*) allowing all authenticated users to list objects."
+ )
+ assert result[0].resource_id == "container-3"
+ assert result[0].resource_name == "global-acl-listing"
+ assert result[0].region == OPENSTACK_REGION
+ assert result[0].project_id == OPENSTACK_PROJECT_ID
+
+ def test_container_listing_via_bare_wildcard(self):
+ """Test container with * read ACL enabling listing (FAIL)."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-4",
+ name="bare-wildcard-listing",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=0,
+ bytes_used=0,
+ read_ACL="*",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ )
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_listing_disabled.objectstorage_container_listing_disabled.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_listing_disabled.objectstorage_container_listing_disabled import (
+ objectstorage_container_listing_disabled,
+ )
+
+ check = objectstorage_container_listing_disabled()
+ result = check.execute()
+
+ assert len(result) == 1
+ assert result[0].status == "FAIL"
+
+ def test_container_rlistings_takes_priority_over_global(self):
+ """Test that .rlistings is reported when both .rlistings and *:* are present."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-5",
+ name="both-patterns",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=0,
+ bytes_used=0,
+ read_ACL=".rlistings,*:*",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ )
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_listing_disabled.objectstorage_container_listing_disabled.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_listing_disabled.objectstorage_container_listing_disabled import (
+ objectstorage_container_listing_disabled,
+ )
+
+ check = objectstorage_container_listing_disabled()
+ result = check.execute()
+
+ assert len(result) == 1
+ assert result[0].status == "FAIL"
+ assert ".rlistings" in result[0].status_extended
+
+ def test_multiple_containers_mixed(self):
+ """Test multiple containers with mixed listing status."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-pass",
+ name="Pass",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=0,
+ bytes_used=0,
+ read_ACL=".r:*",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ ),
+ ObjectStorageContainer(
+ id="container-fail",
+ name="Fail",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=0,
+ bytes_used=0,
+ read_ACL=".r:*,.rlistings",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ ),
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_listing_disabled.objectstorage_container_listing_disabled.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_listing_disabled.objectstorage_container_listing_disabled import (
+ objectstorage_container_listing_disabled,
+ )
+
+ check = objectstorage_container_listing_disabled()
+ result = check.execute()
+
+ assert len(result) == 2
+ assert len([r for r in result if r.status == "PASS"]) == 1
+ assert len([r for r in result if r.status == "FAIL"]) == 1
diff --git a/tests/providers/openstack/services/objectstorage/objectstorage_container_metadata_sensitive_data/objectstorage_container_metadata_sensitive_data_test.py b/tests/providers/openstack/services/objectstorage/objectstorage_container_metadata_sensitive_data/objectstorage_container_metadata_sensitive_data_test.py
new file mode 100644
index 0000000000..6cae6432c7
--- /dev/null
+++ b/tests/providers/openstack/services/objectstorage/objectstorage_container_metadata_sensitive_data/objectstorage_container_metadata_sensitive_data_test.py
@@ -0,0 +1,243 @@
+"""Tests for objectstorage_container_metadata_sensitive_data check."""
+
+from unittest import mock
+
+from prowler.providers.openstack.services.objectstorage.objectstorage_service import (
+ ObjectStorageContainer,
+)
+from tests.providers.openstack.openstack_fixtures import (
+ OPENSTACK_PROJECT_ID,
+ OPENSTACK_REGION,
+ set_mocked_openstack_provider,
+)
+
+
+class Test_objectstorage_container_metadata_sensitive_data:
+ """Test suite for objectstorage_container_metadata_sensitive_data check."""
+
+ def test_no_containers(self):
+ """Test when no containers exist."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = []
+ objectstorage_client.audit_config = {}
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_metadata_sensitive_data.objectstorage_container_metadata_sensitive_data.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_metadata_sensitive_data.objectstorage_container_metadata_sensitive_data import (
+ objectstorage_container_metadata_sensitive_data,
+ )
+
+ check = objectstorage_container_metadata_sensitive_data()
+ result = check.execute()
+
+ assert len(result) == 0
+
+ def test_container_no_metadata(self):
+ """Test container with no metadata (PASS)."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.audit_config = {}
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-1",
+ name="no-metadata",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=10,
+ bytes_used=1024,
+ read_ACL="",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ )
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_metadata_sensitive_data.objectstorage_container_metadata_sensitive_data.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_metadata_sensitive_data.objectstorage_container_metadata_sensitive_data import (
+ objectstorage_container_metadata_sensitive_data,
+ )
+
+ check = objectstorage_container_metadata_sensitive_data()
+ result = check.execute()
+
+ assert len(result) == 1
+ assert result[0].status == "PASS"
+ assert (
+ result[0].status_extended
+ == "Container no-metadata has no metadata (no sensitive data exposure risk)."
+ )
+ assert result[0].resource_id == "container-1"
+ assert result[0].resource_name == "no-metadata"
+ assert result[0].region == OPENSTACK_REGION
+ assert result[0].project_id == OPENSTACK_PROJECT_ID
+
+ def test_container_safe_metadata(self):
+ """Test container with safe metadata (PASS)."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.audit_config = {}
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-2",
+ name="safe-metadata",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=5,
+ bytes_used=512,
+ read_ACL="",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={"environment": "production", "application": "web-app"},
+ )
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_metadata_sensitive_data.objectstorage_container_metadata_sensitive_data.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_metadata_sensitive_data.objectstorage_container_metadata_sensitive_data import (
+ objectstorage_container_metadata_sensitive_data,
+ )
+
+ check = objectstorage_container_metadata_sensitive_data()
+ result = check.execute()
+
+ assert len(result) == 1
+ assert result[0].status == "PASS"
+ assert (
+ result[0].status_extended
+ == "Container safe-metadata metadata does not contain sensitive data."
+ )
+
+ def test_container_password_in_metadata(self):
+ """Test container with password in metadata (FAIL)."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.audit_config = {}
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-3",
+ name="password-metadata",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=0,
+ bytes_used=0,
+ read_ACL="",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={"db_password": "supersecret123"},
+ )
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_metadata_sensitive_data.objectstorage_container_metadata_sensitive_data.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_metadata_sensitive_data.objectstorage_container_metadata_sensitive_data import (
+ objectstorage_container_metadata_sensitive_data,
+ )
+
+ check = objectstorage_container_metadata_sensitive_data()
+ result = check.execute()
+
+ assert len(result) == 1
+ assert result[0].status == "FAIL"
+ assert "contains potential secrets" in result[0].status_extended
+
+ def test_multiple_containers_mixed(self):
+ """Test multiple containers with mixed metadata."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.audit_config = {}
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-pass",
+ name="Safe",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=0,
+ bytes_used=0,
+ read_ACL="",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={"tier": "web"},
+ ),
+ ObjectStorageContainer(
+ id="container-fail",
+ name="Unsafe",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=0,
+ bytes_used=0,
+ read_ACL="",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={"admin_password": "secret123"},
+ ),
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_metadata_sensitive_data.objectstorage_container_metadata_sensitive_data.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_metadata_sensitive_data.objectstorage_container_metadata_sensitive_data import (
+ objectstorage_container_metadata_sensitive_data,
+ )
+
+ check = objectstorage_container_metadata_sensitive_data()
+ result = check.execute()
+
+ assert len(result) == 2
+ assert len([r for r in result if r.status == "PASS"]) == 1
+ assert len([r for r in result if r.status == "FAIL"]) == 1
diff --git a/tests/providers/openstack/services/objectstorage/objectstorage_container_public_read_acl_disabled/objectstorage_container_public_read_acl_disabled_test.py b/tests/providers/openstack/services/objectstorage/objectstorage_container_public_read_acl_disabled/objectstorage_container_public_read_acl_disabled_test.py
new file mode 100644
index 0000000000..a8b484b8ec
--- /dev/null
+++ b/tests/providers/openstack/services/objectstorage/objectstorage_container_public_read_acl_disabled/objectstorage_container_public_read_acl_disabled_test.py
@@ -0,0 +1,245 @@
+"""Tests for objectstorage_container_public_read_acl_disabled check."""
+
+from unittest import mock
+
+from prowler.providers.openstack.services.objectstorage.objectstorage_service import (
+ ObjectStorageContainer,
+)
+from tests.providers.openstack.openstack_fixtures import (
+ OPENSTACK_PROJECT_ID,
+ OPENSTACK_REGION,
+ set_mocked_openstack_provider,
+)
+
+
+class Test_objectstorage_container_public_read_acl_disabled:
+ """Test suite for objectstorage_container_public_read_acl_disabled check."""
+
+ def test_no_containers(self):
+ """Test when no containers exist."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = []
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_public_read_acl_disabled.objectstorage_container_public_read_acl_disabled.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_public_read_acl_disabled.objectstorage_container_public_read_acl_disabled import (
+ objectstorage_container_public_read_acl_disabled,
+ )
+
+ check = objectstorage_container_public_read_acl_disabled()
+ result = check.execute()
+
+ assert len(result) == 0
+
+ def test_container_no_public_read(self):
+ """Test container without public read ACL (PASS)."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-1",
+ name="private-container",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=10,
+ bytes_used=1024,
+ read_ACL="",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ )
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_public_read_acl_disabled.objectstorage_container_public_read_acl_disabled.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_public_read_acl_disabled.objectstorage_container_public_read_acl_disabled import (
+ objectstorage_container_public_read_acl_disabled,
+ )
+
+ check = objectstorage_container_public_read_acl_disabled()
+ result = check.execute()
+
+ assert len(result) == 1
+ assert result[0].status == "PASS"
+ assert (
+ result[0].status_extended
+ == "Container private-container does not have public read ACL."
+ )
+ assert result[0].resource_id == "container-1"
+ assert result[0].resource_name == "private-container"
+ assert result[0].region == OPENSTACK_REGION
+ assert result[0].project_id == OPENSTACK_PROJECT_ID
+
+ def test_container_public_read(self):
+ """Test container with public read ACL (FAIL)."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-2",
+ name="public-container",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=5,
+ bytes_used=512,
+ read_ACL=".r:*,.rlistings",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ )
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_public_read_acl_disabled.objectstorage_container_public_read_acl_disabled.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_public_read_acl_disabled.objectstorage_container_public_read_acl_disabled import (
+ objectstorage_container_public_read_acl_disabled,
+ )
+
+ check = objectstorage_container_public_read_acl_disabled()
+ result = check.execute()
+
+ assert len(result) == 1
+ assert result[0].status == "FAIL"
+ assert (
+ result[0].status_extended
+ == "Container public-container has public read ACL (.r:*) allowing anonymous access."
+ )
+ assert result[0].resource_id == "container-2"
+ assert result[0].resource_name == "public-container"
+ assert result[0].region == OPENSTACK_REGION
+ assert result[0].project_id == OPENSTACK_PROJECT_ID
+
+ def test_container_domain_restricted_referrer_not_flagged(self):
+ """Test container with domain-restricted referrer ACL is not flagged (PASS)."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-3",
+ name="domain-restricted",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=5,
+ bytes_used=512,
+ read_ACL=".r:*.example.com,.rlistings",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ )
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_public_read_acl_disabled.objectstorage_container_public_read_acl_disabled.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_public_read_acl_disabled.objectstorage_container_public_read_acl_disabled import (
+ objectstorage_container_public_read_acl_disabled,
+ )
+
+ check = objectstorage_container_public_read_acl_disabled()
+ result = check.execute()
+
+ assert len(result) == 1
+ assert result[0].status == "PASS"
+ assert (
+ result[0].status_extended
+ == "Container domain-restricted does not have public read ACL."
+ )
+
+ def test_multiple_containers_mixed(self):
+ """Test multiple containers with mixed ACLs."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-pass",
+ name="Pass",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=0,
+ bytes_used=0,
+ read_ACL="",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ ),
+ ObjectStorageContainer(
+ id="container-fail",
+ name="Fail",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=0,
+ bytes_used=0,
+ read_ACL=".r:*",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ ),
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_public_read_acl_disabled.objectstorage_container_public_read_acl_disabled.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_public_read_acl_disabled.objectstorage_container_public_read_acl_disabled import (
+ objectstorage_container_public_read_acl_disabled,
+ )
+
+ check = objectstorage_container_public_read_acl_disabled()
+ result = check.execute()
+
+ assert len(result) == 2
+ assert len([r for r in result if r.status == "PASS"]) == 1
+ assert len([r for r in result if r.status == "FAIL"]) == 1
diff --git a/tests/providers/openstack/services/objectstorage/objectstorage_container_sync_not_enabled/objectstorage_container_sync_not_enabled_test.py b/tests/providers/openstack/services/objectstorage/objectstorage_container_sync_not_enabled/objectstorage_container_sync_not_enabled_test.py
new file mode 100644
index 0000000000..24e4cdfbec
--- /dev/null
+++ b/tests/providers/openstack/services/objectstorage/objectstorage_container_sync_not_enabled/objectstorage_container_sync_not_enabled_test.py
@@ -0,0 +1,199 @@
+"""Tests for objectstorage_container_sync_not_enabled check."""
+
+from unittest import mock
+
+from prowler.providers.openstack.services.objectstorage.objectstorage_service import (
+ ObjectStorageContainer,
+)
+from tests.providers.openstack.openstack_fixtures import (
+ OPENSTACK_PROJECT_ID,
+ OPENSTACK_REGION,
+ set_mocked_openstack_provider,
+)
+
+
+class Test_objectstorage_container_sync_not_enabled:
+ """Test suite for objectstorage_container_sync_not_enabled check."""
+
+ def test_no_containers(self):
+ """Test when no containers exist."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = []
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_sync_not_enabled.objectstorage_container_sync_not_enabled.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_sync_not_enabled.objectstorage_container_sync_not_enabled import (
+ objectstorage_container_sync_not_enabled,
+ )
+
+ check = objectstorage_container_sync_not_enabled()
+ result = check.execute()
+
+ assert len(result) == 0
+
+ def test_container_no_sync(self):
+ """Test container without sync (PASS)."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-1",
+ name="no-sync",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=10,
+ bytes_used=1024,
+ read_ACL="",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ )
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_sync_not_enabled.objectstorage_container_sync_not_enabled.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_sync_not_enabled.objectstorage_container_sync_not_enabled import (
+ objectstorage_container_sync_not_enabled,
+ )
+
+ check = objectstorage_container_sync_not_enabled()
+ result = check.execute()
+
+ assert len(result) == 1
+ assert result[0].status == "PASS"
+ assert (
+ result[0].status_extended
+ == "Container no-sync does not have container sync enabled."
+ )
+ assert result[0].resource_id == "container-1"
+ assert result[0].resource_name == "no-sync"
+ assert result[0].region == OPENSTACK_REGION
+ assert result[0].project_id == OPENSTACK_PROJECT_ID
+
+ def test_container_with_sync(self):
+ """Test container with sync enabled (FAIL)."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-2",
+ name="synced-container",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=5,
+ bytes_used=512,
+ read_ACL="",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="https://other-cluster/v1/AUTH_test/container-2",
+ sync_key="shared-secret",
+ metadata={},
+ )
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_sync_not_enabled.objectstorage_container_sync_not_enabled.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_sync_not_enabled.objectstorage_container_sync_not_enabled import (
+ objectstorage_container_sync_not_enabled,
+ )
+
+ check = objectstorage_container_sync_not_enabled()
+ result = check.execute()
+
+ assert len(result) == 1
+ assert result[0].status == "FAIL"
+ assert (
+ result[0].status_extended
+ == "Container synced-container has container sync enabled (sync target: https://other-cluster/v1/AUTH_test/container-2)."
+ )
+ assert result[0].resource_id == "container-2"
+ assert result[0].resource_name == "synced-container"
+ assert result[0].region == OPENSTACK_REGION
+ assert result[0].project_id == OPENSTACK_PROJECT_ID
+
+ def test_multiple_containers_mixed(self):
+ """Test multiple containers with mixed sync status."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-pass",
+ name="Pass",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=0,
+ bytes_used=0,
+ read_ACL="",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ ),
+ ObjectStorageContainer(
+ id="container-fail",
+ name="Fail",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=0,
+ bytes_used=0,
+ read_ACL="",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="https://external/v1/AUTH_test/container",
+ sync_key="key",
+ metadata={},
+ ),
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_sync_not_enabled.objectstorage_container_sync_not_enabled.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_sync_not_enabled.objectstorage_container_sync_not_enabled import (
+ objectstorage_container_sync_not_enabled,
+ )
+
+ check = objectstorage_container_sync_not_enabled()
+ result = check.execute()
+
+ assert len(result) == 2
+ assert len([r for r in result if r.status == "PASS"]) == 1
+ assert len([r for r in result if r.status == "FAIL"]) == 1
diff --git a/tests/providers/openstack/services/objectstorage/objectstorage_container_versioning_enabled/objectstorage_container_versioning_enabled_test.py b/tests/providers/openstack/services/objectstorage/objectstorage_container_versioning_enabled/objectstorage_container_versioning_enabled_test.py
new file mode 100644
index 0000000000..5e66f27c6d
--- /dev/null
+++ b/tests/providers/openstack/services/objectstorage/objectstorage_container_versioning_enabled/objectstorage_container_versioning_enabled_test.py
@@ -0,0 +1,249 @@
+"""Tests for objectstorage_container_versioning_enabled check."""
+
+from unittest import mock
+
+from prowler.providers.openstack.services.objectstorage.objectstorage_service import (
+ ObjectStorageContainer,
+)
+from tests.providers.openstack.openstack_fixtures import (
+ OPENSTACK_PROJECT_ID,
+ OPENSTACK_REGION,
+ set_mocked_openstack_provider,
+)
+
+
+class Test_objectstorage_container_versioning_enabled:
+ """Test suite for objectstorage_container_versioning_enabled check."""
+
+ def test_no_containers(self):
+ """Test when no containers exist."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = []
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_versioning_enabled.objectstorage_container_versioning_enabled.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_versioning_enabled.objectstorage_container_versioning_enabled import (
+ objectstorage_container_versioning_enabled,
+ )
+
+ check = objectstorage_container_versioning_enabled()
+ result = check.execute()
+
+ assert len(result) == 0
+
+ def test_container_versioning_enabled_versions_location(self):
+ """Test container with versioning enabled via X-Versions-Location (PASS)."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-1",
+ name="versioned-container",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=10,
+ bytes_used=1024,
+ read_ACL="",
+ write_ACL="",
+ versioning_enabled=True,
+ versions_location="versioned-container_versions",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ )
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_versioning_enabled.objectstorage_container_versioning_enabled.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_versioning_enabled.objectstorage_container_versioning_enabled import (
+ objectstorage_container_versioning_enabled,
+ )
+
+ check = objectstorage_container_versioning_enabled()
+ result = check.execute()
+
+ assert len(result) == 1
+ assert result[0].status == "PASS"
+ assert (
+ result[0].status_extended
+ == "Container versioned-container has versioning enabled (versions location: versioned-container_versions)."
+ )
+ assert result[0].resource_id == "container-1"
+ assert result[0].resource_name == "versioned-container"
+ assert result[0].region == OPENSTACK_REGION
+ assert result[0].project_id == OPENSTACK_PROJECT_ID
+
+ def test_container_versioning_enabled_history_location(self):
+ """Test container with versioning enabled via X-History-Location (PASS)."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-1",
+ name="history-container",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=10,
+ bytes_used=1024,
+ read_ACL="",
+ write_ACL="",
+ versioning_enabled=True,
+ versions_location="",
+ history_location="history-container_versions",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ )
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_versioning_enabled.objectstorage_container_versioning_enabled.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_versioning_enabled.objectstorage_container_versioning_enabled import (
+ objectstorage_container_versioning_enabled,
+ )
+
+ check = objectstorage_container_versioning_enabled()
+ result = check.execute()
+
+ assert len(result) == 1
+ assert result[0].status == "PASS"
+ assert (
+ result[0].status_extended
+ == "Container history-container has versioning enabled (history location: history-container_versions)."
+ )
+ assert result[0].resource_id == "container-1"
+ assert result[0].resource_name == "history-container"
+ assert result[0].region == OPENSTACK_REGION
+ assert result[0].project_id == OPENSTACK_PROJECT_ID
+
+ def test_container_versioning_disabled(self):
+ """Test container without versioning (FAIL)."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-2",
+ name="no-versioning",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=5,
+ bytes_used=512,
+ read_ACL="",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ )
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_versioning_enabled.objectstorage_container_versioning_enabled.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_versioning_enabled.objectstorage_container_versioning_enabled import (
+ objectstorage_container_versioning_enabled,
+ )
+
+ check = objectstorage_container_versioning_enabled()
+ result = check.execute()
+
+ assert len(result) == 1
+ assert result[0].status == "FAIL"
+ assert (
+ result[0].status_extended
+ == "Container no-versioning does not have versioning enabled."
+ )
+ assert result[0].resource_id == "container-2"
+ assert result[0].resource_name == "no-versioning"
+ assert result[0].region == OPENSTACK_REGION
+ assert result[0].project_id == OPENSTACK_PROJECT_ID
+
+ def test_multiple_containers_mixed(self):
+ """Test multiple containers with mixed versioning status."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-pass",
+ name="Pass",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=0,
+ bytes_used=0,
+ read_ACL="",
+ write_ACL="",
+ versioning_enabled=True,
+ versions_location="Pass_versions",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ ),
+ ObjectStorageContainer(
+ id="container-fail",
+ name="Fail",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=0,
+ bytes_used=0,
+ read_ACL="",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ ),
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_versioning_enabled.objectstorage_container_versioning_enabled.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_versioning_enabled.objectstorage_container_versioning_enabled import (
+ objectstorage_container_versioning_enabled,
+ )
+
+ check = objectstorage_container_versioning_enabled()
+ result = check.execute()
+
+ assert len(result) == 2
+ assert len([r for r in result if r.status == "PASS"]) == 1
+ assert len([r for r in result if r.status == "FAIL"]) == 1
diff --git a/tests/providers/openstack/services/objectstorage/objectstorage_container_write_acl_restricted/objectstorage_container_write_acl_restricted_test.py b/tests/providers/openstack/services/objectstorage/objectstorage_container_write_acl_restricted/objectstorage_container_write_acl_restricted_test.py
new file mode 100644
index 0000000000..dc55902b78
--- /dev/null
+++ b/tests/providers/openstack/services/objectstorage/objectstorage_container_write_acl_restricted/objectstorage_container_write_acl_restricted_test.py
@@ -0,0 +1,329 @@
+"""Tests for objectstorage_container_write_acl_restricted check."""
+
+from unittest import mock
+
+from prowler.providers.openstack.services.objectstorage.objectstorage_service import (
+ ObjectStorageContainer,
+)
+from tests.providers.openstack.openstack_fixtures import (
+ OPENSTACK_PROJECT_ID,
+ OPENSTACK_REGION,
+ set_mocked_openstack_provider,
+)
+
+
+class Test_objectstorage_container_write_acl_restricted:
+ """Test suite for objectstorage_container_write_acl_restricted check."""
+
+ def test_no_containers(self):
+ """Test when no containers exist."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = []
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_write_acl_restricted.objectstorage_container_write_acl_restricted.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_write_acl_restricted.objectstorage_container_write_acl_restricted import (
+ objectstorage_container_write_acl_restricted,
+ )
+
+ check = objectstorage_container_write_acl_restricted()
+ result = check.execute()
+
+ assert len(result) == 0
+
+ def test_container_restricted_write(self):
+ """Test container with restricted write ACL (PASS)."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-1",
+ name="restricted-write",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=10,
+ bytes_used=1024,
+ read_ACL="",
+ write_ACL="project-123:user-456",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ )
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_write_acl_restricted.objectstorage_container_write_acl_restricted.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_write_acl_restricted.objectstorage_container_write_acl_restricted import (
+ objectstorage_container_write_acl_restricted,
+ )
+
+ check = objectstorage_container_write_acl_restricted()
+ result = check.execute()
+
+ assert len(result) == 1
+ assert result[0].status == "PASS"
+ assert (
+ result[0].status_extended
+ == "Container restricted-write has restricted write ACL."
+ )
+ assert result[0].resource_id == "container-1"
+ assert result[0].resource_name == "restricted-write"
+ assert result[0].region == OPENSTACK_REGION
+ assert result[0].project_id == OPENSTACK_PROJECT_ID
+
+ def test_container_unrestricted_write_star_colon_star(self):
+ """Test container with *:* write ACL (FAIL)."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-2",
+ name="unrestricted-write",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=5,
+ bytes_used=512,
+ read_ACL="",
+ write_ACL="*:*",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ )
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_write_acl_restricted.objectstorage_container_write_acl_restricted.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_write_acl_restricted.objectstorage_container_write_acl_restricted import (
+ objectstorage_container_write_acl_restricted,
+ )
+
+ check = objectstorage_container_write_acl_restricted()
+ result = check.execute()
+
+ assert len(result) == 1
+ assert result[0].status == "FAIL"
+ assert (
+ result[0].status_extended
+ == "Container unrestricted-write has unrestricted write ACL allowing all authenticated users to write."
+ )
+ assert result[0].resource_id == "container-2"
+ assert result[0].resource_name == "unrestricted-write"
+ assert result[0].region == OPENSTACK_REGION
+ assert result[0].project_id == OPENSTACK_PROJECT_ID
+
+ def test_container_unrestricted_write_star_only(self):
+ """Test container with * write ACL (FAIL)."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-3",
+ name="star-write",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=0,
+ bytes_used=0,
+ read_ACL="",
+ write_ACL="*",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ )
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_write_acl_restricted.objectstorage_container_write_acl_restricted.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_write_acl_restricted.objectstorage_container_write_acl_restricted import (
+ objectstorage_container_write_acl_restricted,
+ )
+
+ check = objectstorage_container_write_acl_restricted()
+ result = check.execute()
+
+ assert len(result) == 1
+ assert result[0].status == "FAIL"
+
+ def test_container_star_in_multi_entry_acl(self):
+ """Test container with * in multi-entry write ACL (FAIL)."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-4",
+ name="star-multi-entry",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=0,
+ bytes_used=0,
+ read_ACL="",
+ write_ACL="*,project-123:user-456",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ )
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_write_acl_restricted.objectstorage_container_write_acl_restricted.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_write_acl_restricted.objectstorage_container_write_acl_restricted import (
+ objectstorage_container_write_acl_restricted,
+ )
+
+ check = objectstorage_container_write_acl_restricted()
+ result = check.execute()
+
+ assert len(result) == 1
+ assert result[0].status == "FAIL"
+ assert (
+ result[0].status_extended
+ == "Container star-multi-entry has unrestricted write ACL allowing all authenticated users to write."
+ )
+
+ def test_container_star_colon_star_in_multi_entry_acl(self):
+ """Test container with *:* in multi-entry write ACL (FAIL)."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-5",
+ name="star-colon-multi",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=0,
+ bytes_used=0,
+ read_ACL="",
+ write_ACL="project-123:user-456,*:*",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ )
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_write_acl_restricted.objectstorage_container_write_acl_restricted.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_write_acl_restricted.objectstorage_container_write_acl_restricted import (
+ objectstorage_container_write_acl_restricted,
+ )
+
+ check = objectstorage_container_write_acl_restricted()
+ result = check.execute()
+
+ assert len(result) == 1
+ assert result[0].status == "FAIL"
+
+ def test_multiple_containers_mixed(self):
+ """Test multiple containers with mixed write ACLs."""
+ objectstorage_client = mock.MagicMock()
+ objectstorage_client.containers = [
+ ObjectStorageContainer(
+ id="container-pass",
+ name="Pass",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=0,
+ bytes_used=0,
+ read_ACL="",
+ write_ACL="",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ ),
+ ObjectStorageContainer(
+ id="container-fail",
+ name="Fail",
+ region=OPENSTACK_REGION,
+ project_id=OPENSTACK_PROJECT_ID,
+ object_count=0,
+ bytes_used=0,
+ read_ACL="",
+ write_ACL="*:*",
+ versioning_enabled=False,
+ versions_location="",
+ history_location="",
+ sync_to="",
+ sync_key="",
+ metadata={},
+ ),
+ ]
+
+ with (
+ mock.patch(
+ "prowler.providers.common.provider.Provider.get_global_provider",
+ return_value=set_mocked_openstack_provider(),
+ ),
+ mock.patch(
+ "prowler.providers.openstack.services.objectstorage.objectstorage_container_write_acl_restricted.objectstorage_container_write_acl_restricted.objectstorage_client",
+ new=objectstorage_client,
+ ),
+ ):
+ from prowler.providers.openstack.services.objectstorage.objectstorage_container_write_acl_restricted.objectstorage_container_write_acl_restricted import (
+ objectstorage_container_write_acl_restricted,
+ )
+
+ check = objectstorage_container_write_acl_restricted()
+ result = check.execute()
+
+ assert len(result) == 2
+ assert len([r for r in result if r.status == "PASS"]) == 1
+ assert len([r for r in result if r.status == "FAIL"]) == 1
diff --git a/tests/providers/openstack/services/objectstorage/openstack_objectstorage_service_test.py b/tests/providers/openstack/services/objectstorage/openstack_objectstorage_service_test.py
new file mode 100644
index 0000000000..a7e07c97f9
--- /dev/null
+++ b/tests/providers/openstack/services/objectstorage/openstack_objectstorage_service_test.py
@@ -0,0 +1,403 @@
+"""Tests for OpenStack ObjectStorage service."""
+
+from unittest.mock import MagicMock, patch
+
+from openstack import exceptions as openstack_exceptions
+
+from prowler.providers.openstack.services.objectstorage.objectstorage_service import (
+ ObjectStorage,
+ ObjectStorageContainer,
+)
+from tests.providers.openstack.openstack_fixtures import (
+ OPENSTACK_PROJECT_ID,
+ OPENSTACK_REGION,
+ set_mocked_openstack_provider,
+)
+
+
+class TestObjectStorageService:
+ """Test suite for ObjectStorage service."""
+
+ def test_objectstorage_service_initialization(self):
+ """Test ObjectStorage service initializes correctly."""
+ provider = set_mocked_openstack_provider()
+
+ with patch.object(
+ ObjectStorage, "_list_containers", return_value=[]
+ ) as mock_list:
+ service = ObjectStorage(provider)
+
+ assert service.service_name == "ObjectStorage"
+ assert service.provider == provider
+ assert service.connection == provider.connection
+ assert service.regional_connections == provider.regional_connections
+ assert service.audited_regions == [OPENSTACK_REGION]
+ assert service.region == OPENSTACK_REGION
+ assert service.project_id == OPENSTACK_PROJECT_ID
+ assert service.containers == []
+ mock_list.assert_called_once()
+
+ def test_objectstorage_list_containers_success(self):
+ """Test listing containers successfully."""
+ provider = set_mocked_openstack_provider()
+
+ mock_container1 = MagicMock()
+ mock_container1.name = "container-1"
+ mock_container1.count = 10
+ mock_container1.bytes = 1024
+ mock_container1.read_ACL = ".r:*,.rlistings"
+ mock_container1.write_ACL = "*:*"
+ mock_container1.versions_location = "container-1_versions"
+ mock_container1.history_location = ""
+ mock_container1.sync_to = "https://other-cluster/v1/AUTH_test/container-1"
+ mock_container1.sync_key = "shared-secret"
+ mock_container1.metadata = {"environment": "production"}
+
+ mock_container2 = MagicMock()
+ mock_container2.name = "container-2"
+ mock_container2.count = 0
+ mock_container2.bytes = 0
+ mock_container2.read_ACL = ""
+ mock_container2.write_ACL = ""
+ mock_container2.versions_location = ""
+ mock_container2.history_location = ""
+ mock_container2.sync_to = ""
+ mock_container2.sync_key = ""
+ mock_container2.metadata = {}
+
+ provider.connection.object_store.containers.return_value = [
+ mock_container1,
+ mock_container2,
+ ]
+
+ # get_container_metadata returns the detailed mock for each container
+ def mock_get_metadata(name):
+ return {"container-1": mock_container1, "container-2": mock_container2}[
+ name
+ ]
+
+ provider.connection.object_store.get_container_metadata.side_effect = (
+ mock_get_metadata
+ )
+
+ service = ObjectStorage(provider)
+
+ assert len(service.containers) == 2
+ assert isinstance(service.containers[0], ObjectStorageContainer)
+ assert service.containers[0].id == "container-1"
+ assert service.containers[0].name == "container-1"
+ assert service.containers[0].region == OPENSTACK_REGION
+ assert service.containers[0].project_id == OPENSTACK_PROJECT_ID
+ assert service.containers[0].object_count == 10
+ assert service.containers[0].bytes_used == 1024
+ assert service.containers[0].read_ACL == ".r:*,.rlistings"
+ assert service.containers[0].write_ACL == "*:*"
+ assert service.containers[0].versioning_enabled is True
+ assert service.containers[0].versions_location == "container-1_versions"
+ assert service.containers[0].history_location == ""
+ assert (
+ service.containers[0].sync_to
+ == "https://other-cluster/v1/AUTH_test/container-1"
+ )
+ assert service.containers[0].sync_key == "shared-secret"
+ assert service.containers[0].metadata == {"environment": "production"}
+
+ assert service.containers[1].id == "container-2"
+ assert service.containers[1].versioning_enabled is False
+ assert service.containers[1].sync_to == ""
+ assert service.containers[1].metadata == {}
+
+ def test_objectstorage_list_containers_empty(self):
+ """Test listing containers when none exist."""
+ provider = set_mocked_openstack_provider()
+ provider.connection.object_store.containers.return_value = []
+
+ service = ObjectStorage(provider)
+
+ assert service.containers == []
+
+ def test_objectstorage_list_containers_missing_attributes(self):
+ """Test listing containers with missing attributes uses fallback to list data."""
+ provider = set_mocked_openstack_provider()
+
+ mock_container = MagicMock()
+ mock_container.name = "container-1"
+ del mock_container.count
+ del mock_container.bytes
+ del mock_container.read_ACL
+ del mock_container.write_ACL
+ del mock_container.versions_location
+ del mock_container.history_location
+ del mock_container.sync_to
+ del mock_container.sync_key
+ del mock_container.metadata
+
+ provider.connection.object_store.containers.return_value = [mock_container]
+
+ # HEAD also returns missing attributes (same mock)
+ provider.connection.object_store.get_container_metadata.return_value = (
+ mock_container
+ )
+
+ service = ObjectStorage(provider)
+
+ assert len(service.containers) == 1
+ assert service.containers[0].id == "container-1"
+ assert service.containers[0].name == "container-1"
+ assert service.containers[0].object_count == 0
+ assert service.containers[0].bytes_used == 0
+ assert service.containers[0].read_ACL == ""
+ assert service.containers[0].write_ACL == ""
+ assert service.containers[0].versioning_enabled is False
+ assert service.containers[0].versions_location == ""
+ assert service.containers[0].history_location == ""
+ assert service.containers[0].sync_to == ""
+ assert service.containers[0].sync_key == ""
+ assert service.containers[0].metadata == {}
+
+ def test_objectstorage_list_containers_head_failure_falls_back(self):
+ """Test that HEAD failure falls back to list data gracefully."""
+ provider = set_mocked_openstack_provider()
+
+ mock_container = MagicMock()
+ mock_container.name = "container-1"
+ mock_container.count = 5
+ mock_container.bytes = 256
+ mock_container.read_ACL = None
+ mock_container.write_ACL = None
+ mock_container.versions_location = None
+ mock_container.history_location = None
+ mock_container.sync_to = None
+ mock_container.sync_key = None
+ mock_container.metadata = {}
+
+ provider.connection.object_store.containers.return_value = [mock_container]
+ provider.connection.object_store.get_container_metadata.side_effect = Exception(
+ "HEAD failed"
+ )
+
+ service = ObjectStorage(provider)
+
+ # Should still create the container using list data as fallback
+ assert len(service.containers) == 1
+ assert service.containers[0].name == "container-1"
+ assert service.containers[0].object_count == 5
+ assert service.containers[0].bytes_used == 256
+
+ def test_objectstorage_list_containers_sdk_exception(self):
+ """Test handling SDKException when listing containers."""
+ provider = set_mocked_openstack_provider()
+ provider.connection.object_store.containers.side_effect = (
+ openstack_exceptions.SDKException("API error")
+ )
+
+ service = ObjectStorage(provider)
+
+ assert service.containers == []
+
+ def test_objectstorage_list_containers_generic_exception(self):
+ """Test handling generic exception when listing containers."""
+ provider = set_mocked_openstack_provider()
+ provider.connection.object_store.containers.side_effect = Exception(
+ "Unexpected error"
+ )
+
+ service = ObjectStorage(provider)
+
+ assert service.containers == []
+
+ def test_objectstorage_container_dataclass_attributes(self):
+ """Test ObjectStorageContainer dataclass has all required attributes."""
+ container = ObjectStorageContainer(
+ id="container-1",
+ name="container-1",
+ region="RegionOne",
+ project_id="project-1",
+ object_count=10,
+ bytes_used=1024,
+ read_ACL=".r:*",
+ write_ACL="*:*",
+ versioning_enabled=True,
+ versions_location="container-1_versions",
+ history_location="",
+ sync_to="https://other-cluster/v1/AUTH_test/container-1",
+ sync_key="shared-secret",
+ metadata={"environment": "production"},
+ )
+
+ assert container.id == "container-1"
+ assert container.name == "container-1"
+ assert container.region == "RegionOne"
+ assert container.project_id == "project-1"
+ assert container.object_count == 10
+ assert container.bytes_used == 1024
+ assert container.read_ACL == ".r:*"
+ assert container.write_ACL == "*:*"
+ assert container.versioning_enabled is True
+ assert container.versions_location == "container-1_versions"
+ assert container.history_location == ""
+ assert container.sync_to == "https://other-cluster/v1/AUTH_test/container-1"
+ assert container.sync_key == "shared-secret"
+ assert container.metadata == {"environment": "production"}
+
+ def test_objectstorage_service_inherits_from_base(self):
+ """Test ObjectStorage service inherits from OpenStackService."""
+ provider = set_mocked_openstack_provider()
+
+ with patch.object(ObjectStorage, "_list_containers", return_value=[]):
+ service = ObjectStorage(provider)
+
+ assert hasattr(service, "service_name")
+ assert hasattr(service, "provider")
+ assert hasattr(service, "connection")
+ assert hasattr(service, "regional_connections")
+ assert hasattr(service, "audited_regions")
+ assert hasattr(service, "session")
+ assert hasattr(service, "region")
+ assert hasattr(service, "project_id")
+ assert hasattr(service, "identity")
+ assert hasattr(service, "audit_config")
+ assert hasattr(service, "fixer_config")
+
+ def test_objectstorage_list_containers_multi_region(self):
+ """Test listing containers across multiple regions."""
+ provider = set_mocked_openstack_provider()
+
+ # Create two mock connections for two regions
+ mock_conn_uk1 = MagicMock()
+ mock_conn_de1 = MagicMock()
+
+ provider.regional_connections = {"UK1": mock_conn_uk1, "DE1": mock_conn_de1}
+
+ mock_container_uk = MagicMock()
+ mock_container_uk.name = "container-uk"
+ mock_container_uk.count = 5
+ mock_container_uk.bytes = 512
+ mock_container_uk.read_ACL = ""
+ mock_container_uk.write_ACL = ""
+ mock_container_uk.versions_location = ""
+ mock_container_uk.history_location = ""
+ mock_container_uk.sync_to = ""
+ mock_container_uk.sync_key = ""
+ mock_container_uk.metadata = {}
+
+ mock_container_de = MagicMock()
+ mock_container_de.name = "container-de"
+ mock_container_de.count = 10
+ mock_container_de.bytes = 1024
+ mock_container_de.read_ACL = ".r:*"
+ mock_container_de.write_ACL = ""
+ mock_container_de.versions_location = ""
+ mock_container_de.history_location = ""
+ mock_container_de.sync_to = ""
+ mock_container_de.sync_key = ""
+ mock_container_de.metadata = {}
+
+ mock_conn_uk1.object_store.containers.return_value = [mock_container_uk]
+ mock_conn_uk1.object_store.get_container_metadata.return_value = (
+ mock_container_uk
+ )
+ mock_conn_de1.object_store.containers.return_value = [mock_container_de]
+ mock_conn_de1.object_store.get_container_metadata.return_value = (
+ mock_container_de
+ )
+
+ service = ObjectStorage(provider)
+
+ assert len(service.containers) == 2
+ uk_container = next(c for c in service.containers if c.id == "container-uk")
+ de_container = next(c for c in service.containers if c.id == "container-de")
+ assert uk_container.region == "UK1"
+ assert de_container.region == "DE1"
+
+ def test_objectstorage_list_containers_multi_region_partial_failure(self):
+ """Test that a failing region doesn't prevent other regions from being listed."""
+ provider = set_mocked_openstack_provider()
+
+ mock_conn_ok = MagicMock()
+ mock_conn_fail = MagicMock()
+
+ provider.regional_connections = {"UK1": mock_conn_ok, "DE1": mock_conn_fail}
+
+ mock_container = MagicMock()
+ mock_container.name = "container-uk"
+ mock_container.count = 5
+ mock_container.bytes = 512
+ mock_container.read_ACL = ""
+ mock_container.write_ACL = ""
+ mock_container.versions_location = ""
+ mock_container.history_location = ""
+ mock_container.sync_to = ""
+ mock_container.sync_key = ""
+ mock_container.metadata = {}
+
+ mock_conn_ok.object_store.containers.return_value = [mock_container]
+ mock_conn_ok.object_store.get_container_metadata.return_value = mock_container
+ mock_conn_fail.object_store.containers.side_effect = (
+ openstack_exceptions.SDKException("API error in DE1")
+ )
+
+ service = ObjectStorage(provider)
+
+ assert len(service.containers) == 1
+ assert service.containers[0].id == "container-uk"
+ assert service.containers[0].region == "UK1"
+
+ def test_objectstorage_list_containers_multi_region_one_empty(self):
+ """Test multi-region where one region has containers and the other is empty."""
+ provider = set_mocked_openstack_provider()
+
+ mock_conn_uk1 = MagicMock()
+ mock_conn_de1 = MagicMock()
+
+ provider.regional_connections = {"UK1": mock_conn_uk1, "DE1": mock_conn_de1}
+
+ mock_container = MagicMock()
+ mock_container.name = "container-uk"
+ mock_container.count = 5
+ mock_container.bytes = 512
+ mock_container.read_ACL = ""
+ mock_container.write_ACL = ""
+ mock_container.versions_location = ""
+ mock_container.history_location = ""
+ mock_container.sync_to = ""
+ mock_container.sync_key = ""
+ mock_container.metadata = {}
+
+ mock_conn_uk1.object_store.containers.return_value = [mock_container]
+ mock_conn_uk1.object_store.get_container_metadata.return_value = mock_container
+ mock_conn_de1.object_store.containers.return_value = []
+
+ service = ObjectStorage(provider)
+
+ assert len(service.containers) == 1
+ assert service.containers[0].id == "container-uk"
+ assert service.containers[0].region == "UK1"
+
+ def test_objectstorage_list_containers_history_location_versioning(self):
+ """Test that history_location (X-History-Location) enables versioning."""
+ provider = set_mocked_openstack_provider()
+
+ mock_container = MagicMock()
+ mock_container.name = "history-container"
+ mock_container.count = 3
+ mock_container.bytes = 256
+ mock_container.read_ACL = ""
+ mock_container.write_ACL = ""
+ mock_container.versions_location = ""
+ mock_container.history_location = "history-container_versions"
+ mock_container.sync_to = ""
+ mock_container.sync_key = ""
+ mock_container.metadata = {}
+
+ provider.connection.object_store.containers.return_value = [mock_container]
+ provider.connection.object_store.get_container_metadata.return_value = (
+ mock_container
+ )
+
+ service = ObjectStorage(provider)
+
+ assert len(service.containers) == 1
+ assert service.containers[0].versioning_enabled is True
+ assert service.containers[0].versions_location == ""
+ assert service.containers[0].history_location == "history-container_versions"
From 65a709810409a61785320f8d2950a10abe1d8333 Mon Sep 17 00:00:00 2001
From: lydiavilchez <114735608+lydiavilchez@users.noreply.github.com>
Date: Wed, 11 Mar 2026 12:06:30 +0100
Subject: [PATCH 020/108] feat(api): add Google Workspace provider API
integration (#10247)
---
api/CHANGELOG.md | 1 +
.../0084_googleworkspace_provider.py | 39 +++
api/src/backend/api/models.py | 10 +
api/src/backend/api/specs/v1.yaml | 250 ++++++++++++++----
api/src/backend/api/tests/test_utils.py | 8 +
api/src/backend/api/tests/test_views.py | 67 ++++-
api/src/backend/api/utils.py | 15 +-
.../api/v1/serializer_utils/providers.py | 16 ++
api/src/backend/api/v1/serializers.py | 10 +
api/src/backend/conftest.py | 7 +
prowler/CHANGELOG.md | 1 +
.../googleworkspace/exceptions/exceptions.py | 11 +
.../googleworkspace_provider.py | 14 +-
13 files changed, 396 insertions(+), 53 deletions(-)
create mode 100644 api/src/backend/api/migrations/0084_googleworkspace_provider.py
diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md
index e6dd2cfa70..1de35a8999 100644
--- a/api/CHANGELOG.md
+++ b/api/CHANGELOG.md
@@ -28,6 +28,7 @@ All notable changes to the **Prowler API** are documented in this file.
- PDF report for the CSA CCM compliance framework [(#10088)](https://github.com/prowler-cloud/prowler/pull/10088)
- `image` provider support for container image scanning [(#10128)](https://github.com/prowler-cloud/prowler/pull/10128)
- Attack Paths: Custom query and Cartography schema endpoints (temporarily blocked) [(#10149)](https://github.com/prowler-cloud/prowler/pull/10149)
+- `googleworkspace` provider support [(#10247)](https://github.com/prowler-cloud/prowler/pull/10247)
### 🔄 Changed
diff --git a/api/src/backend/api/migrations/0084_googleworkspace_provider.py b/api/src/backend/api/migrations/0084_googleworkspace_provider.py
new file mode 100644
index 0000000000..eb704bb6b5
--- /dev/null
+++ b/api/src/backend/api/migrations/0084_googleworkspace_provider.py
@@ -0,0 +1,39 @@
+from django.db import migrations
+
+import api.db_utils
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("api", "0083_image_provider"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="provider",
+ name="provider",
+ field=api.db_utils.ProviderEnumField(
+ choices=[
+ ("aws", "AWS"),
+ ("azure", "Azure"),
+ ("gcp", "GCP"),
+ ("kubernetes", "Kubernetes"),
+ ("m365", "M365"),
+ ("github", "GitHub"),
+ ("mongodbatlas", "MongoDB Atlas"),
+ ("iac", "IaC"),
+ ("oraclecloud", "Oracle Cloud Infrastructure"),
+ ("alibabacloud", "Alibaba Cloud"),
+ ("cloudflare", "Cloudflare"),
+ ("openstack", "OpenStack"),
+ ("image", "Image"),
+ ("googleworkspace", "Google Workspace"),
+ ],
+ default="aws",
+ ),
+ ),
+ migrations.RunSQL(
+ "ALTER TYPE provider ADD VALUE IF NOT EXISTS 'googleworkspace';",
+ reverse_sql=migrations.RunSQL.noop,
+ ),
+ ]
diff --git a/api/src/backend/api/models.py b/api/src/backend/api/models.py
index 1f82c4a7da..e736ef403a 100644
--- a/api/src/backend/api/models.py
+++ b/api/src/backend/api/models.py
@@ -293,6 +293,7 @@ class Provider(RowLevelSecurityProtectedModel):
CLOUDFLARE = "cloudflare", _("Cloudflare")
OPENSTACK = "openstack", _("OpenStack")
IMAGE = "image", _("Image")
+ GOOGLEWORKSPACE = "googleworkspace", _("Google Workspace")
@staticmethod
def validate_aws_uid(value):
@@ -342,6 +343,15 @@ class Provider(RowLevelSecurityProtectedModel):
pointer="/data/attributes/uid",
)
+ @staticmethod
+ def validate_googleworkspace_uid(value):
+ if not re.match(r"^C[0-9a-zA-Z]+$", value):
+ raise ModelValidationError(
+ detail="Google Workspace Customer ID must start with 'C' followed by one or more alphanumeric characters (e.g., C01234abc, C12345678).",
+ code="googleworkspace-uid",
+ pointer="/data/attributes/uid",
+ )
+
@staticmethod
def validate_kubernetes_uid(value):
if not re.match(
diff --git a/api/src/backend/api/specs/v1.yaml b/api/src/backend/api/specs/v1.yaml
index 0cabb2becf..2eedf6dba0 100644
--- a/api/src/backend/api/specs/v1.yaml
+++ b/api/src/backend/api/specs/v1.yaml
@@ -356,7 +356,7 @@ paths:
name: filter[provider_type]
schema:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -364,6 +364,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -385,13 +386,14 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
- in: query
name: filter[provider_type__in]
schema:
type: array
items:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -399,6 +401,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -422,6 +425,7 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
explode: false
style: form
- in: query
@@ -723,7 +727,7 @@ paths:
$ref: '#/components/schemas/OpenApiResponseResponse'
text/plain:
schema:
- type: string
+ $ref: '#/components/schemas/AttackPathsQueryResult'
description: ''
'403':
description: Read-only queries are enforced
@@ -769,7 +773,7 @@ paths:
$ref: '#/components/schemas/OpenApiResponseResponse'
text/plain:
schema:
- type: string
+ $ref: '#/components/schemas/AttackPathsQueryResult'
description: ''
'400':
description: Bad request (e.g., Unknown Attack Paths query for the selected
@@ -1331,7 +1335,7 @@ paths:
name: filter[provider_type]
schema:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -1339,6 +1343,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -1360,6 +1365,7 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
- in: query
name: filter[provider_type__in]
schema:
@@ -1805,7 +1811,7 @@ paths:
name: filter[provider_type]
schema:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -1813,6 +1819,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -1834,13 +1841,14 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
- in: query
name: filter[provider_type__in]
schema:
type: array
items:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -1848,6 +1856,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -1871,6 +1880,7 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
explode: false
style: form
- in: query
@@ -2403,7 +2413,7 @@ paths:
name: filter[provider_type]
schema:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -2411,6 +2421,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -2432,13 +2443,14 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
- in: query
name: filter[provider_type__in]
schema:
type: array
items:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -2446,6 +2458,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -2469,6 +2482,7 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
explode: false
style: form
- in: query
@@ -2909,7 +2923,7 @@ paths:
name: filter[provider_type]
schema:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -2917,6 +2931,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -2938,13 +2953,14 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
- in: query
name: filter[provider_type__in]
schema:
type: array
items:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -2952,6 +2968,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -2975,6 +2992,7 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
explode: false
style: form
- in: query
@@ -3413,7 +3431,7 @@ paths:
name: filter[provider_type]
schema:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -3421,6 +3439,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -3442,13 +3461,14 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
- in: query
name: filter[provider_type__in]
schema:
type: array
items:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -3456,6 +3476,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -3479,6 +3500,7 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
explode: false
style: form
- in: query
@@ -3905,7 +3927,7 @@ paths:
name: filter[provider_type]
schema:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -3913,6 +3935,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -3934,13 +3957,14 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
- in: query
name: filter[provider_type__in]
schema:
type: array
items:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -3948,6 +3972,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -3971,6 +3996,7 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
explode: false
style: form
- in: query
@@ -5738,7 +5764,7 @@ paths:
name: filter[provider_type]
schema:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -5746,6 +5772,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -5767,13 +5794,14 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
- in: query
name: filter[provider_type__in]
schema:
type: array
items:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -5781,6 +5809,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -5804,6 +5833,7 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
explode: false
style: form
- name: filter[search]
@@ -5909,7 +5939,7 @@ paths:
name: filter[provider_type]
schema:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -5917,6 +5947,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -5938,13 +5969,14 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
- in: query
name: filter[provider_type__in]
schema:
type: array
items:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -5952,6 +5984,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -5975,6 +6008,7 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
explode: false
style: form
- name: filter[search]
@@ -6077,6 +6111,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -6098,6 +6133,7 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
- in: query
name: filter[provider_type__in]
schema:
@@ -6111,6 +6147,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -6134,6 +6171,7 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
explode: false
style: form
- name: filter[search]
@@ -6260,7 +6298,7 @@ paths:
name: filter[provider_type]
schema:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -6268,6 +6306,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -6289,13 +6328,14 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
- in: query
name: filter[provider_type__in]
schema:
type: array
items:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -6303,6 +6343,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -6326,6 +6367,7 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
explode: false
style: form
- in: query
@@ -6465,7 +6507,7 @@ paths:
name: filter[provider_type]
schema:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -6473,6 +6515,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -6494,13 +6537,14 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
- in: query
name: filter[provider_type__in]
schema:
type: array
items:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -6508,6 +6552,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -6531,6 +6576,7 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
explode: false
style: form
- in: query
@@ -6672,6 +6718,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -6693,6 +6740,7 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
- in: query
name: filter[provider_type__in]
schema:
@@ -6706,6 +6754,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -6729,6 +6778,7 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
explode: false
style: form
- name: filter[search]
@@ -6904,7 +6954,7 @@ paths:
name: filter[provider_type]
schema:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -6912,6 +6962,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -6933,13 +6984,14 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
- in: query
name: filter[provider_type__in]
schema:
type: array
items:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -6947,6 +6999,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -6970,6 +7023,7 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
explode: false
style: form
- in: query
@@ -7074,7 +7128,7 @@ paths:
name: filter[provider_type]
schema:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -7082,6 +7136,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -7103,13 +7158,14 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
- in: query
name: filter[provider_type__in]
schema:
type: array
items:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -7117,6 +7173,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -7140,6 +7197,7 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
explode: false
style: form
- in: query
@@ -7268,7 +7326,7 @@ paths:
name: filter[provider_type]
schema:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -7276,6 +7334,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -7297,13 +7356,14 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
- in: query
name: filter[provider_type__in]
schema:
type: array
items:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -7311,6 +7371,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -7334,6 +7395,7 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
explode: false
style: form
- in: query
@@ -8103,7 +8165,7 @@ paths:
name: filter[provider]
schema:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -8111,6 +8173,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -8132,13 +8195,14 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
- in: query
name: filter[provider__in]
schema:
type: array
items:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -8146,6 +8210,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -8169,13 +8234,14 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
explode: false
style: form
- in: query
name: filter[provider_type]
schema:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -8183,6 +8249,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -8204,13 +8271,14 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
- in: query
name: filter[provider_type__in]
schema:
type: array
items:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -8218,6 +8286,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -8241,6 +8310,7 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
explode: false
style: form
- name: filter[search]
@@ -8894,7 +8964,7 @@ paths:
name: filter[provider_type]
schema:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -8902,6 +8972,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -8923,13 +8994,14 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
- in: query
name: filter[provider_type__in]
schema:
type: array
items:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -8937,6 +9009,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -8960,6 +9033,7 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
explode: false
style: form
- in: query
@@ -9437,7 +9511,7 @@ paths:
name: filter[provider_type]
schema:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -9445,6 +9519,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -9466,13 +9541,14 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
- in: query
name: filter[provider_type__in]
schema:
type: array
items:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -9480,6 +9556,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -9503,6 +9580,7 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
explode: false
style: form
- in: query
@@ -9793,7 +9871,7 @@ paths:
name: filter[provider_type]
schema:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -9801,6 +9879,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -9822,13 +9901,14 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
- in: query
name: filter[provider_type__in]
schema:
type: array
items:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -9836,6 +9916,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -9859,6 +9940,7 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
explode: false
style: form
- in: query
@@ -10155,7 +10237,7 @@ paths:
name: filter[provider_type]
schema:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -10163,6 +10245,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -10184,13 +10267,14 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
- in: query
name: filter[provider_type__in]
schema:
type: array
items:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -10198,6 +10282,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -10221,6 +10306,7 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
explode: false
style: form
- in: query
@@ -11027,7 +11113,7 @@ paths:
name: filter[provider_type]
schema:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -11035,6 +11121,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -11056,13 +11143,14 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
- in: query
name: filter[provider_type__in]
schema:
type: array
items:
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
enum:
- alibabacloud
- aws
@@ -11070,6 +11158,7 @@ paths:
- cloudflare
- gcp
- github
+ - googleworkspace
- iac
- image
- kubernetes
@@ -11093,6 +11182,7 @@ paths:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
explode: false
style: form
- in: query
@@ -18189,6 +18279,22 @@ components:
description: The service account key for GCP.
required:
- service_account_key
+ - type: object
+ title: Google Workspace Service Account
+ properties:
+ credentials_content:
+ type: string
+ description: The service account JSON credentials content
+ for Google Workspace API access with domain-wide delegation
+ enabled.
+ delegated_user:
+ type: string
+ format: email
+ description: The email address of the Google Workspace super
+ admin user to impersonate for domain-wide delegation.
+ required:
+ - credentials_content
+ - delegated_user
- type: object
title: Kubernetes Static Credentials
properties:
@@ -19358,6 +19464,7 @@ components:
- cloudflare
- openstack
- image
+ - googleworkspace
type: string
description: |-
* `aws` - AWS
@@ -19373,7 +19480,8 @@ components:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
- x-spec-enum-id: 7ae539a3796eb30a
+ * `googleworkspace` - Google Workspace
+ x-spec-enum-id: c0d56cad8ab9abe5
uid:
type: string
title: Unique identifier for the provider, set by the provider
@@ -19492,8 +19600,9 @@ components:
- cloudflare
- openstack
- image
+ - googleworkspace
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
description: |-
Type of provider to create.
@@ -19510,6 +19619,7 @@ components:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
uid:
type: string
title: Unique identifier for the provider, set by the provider
@@ -19560,8 +19670,9 @@ components:
- cloudflare
- openstack
- image
+ - googleworkspace
type: string
- x-spec-enum-id: 7ae539a3796eb30a
+ x-spec-enum-id: c0d56cad8ab9abe5
description: |-
Type of provider to create.
@@ -19578,6 +19689,7 @@ components:
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
* `image` - Image
+ * `googleworkspace` - Google Workspace
uid:
type: string
minLength: 3
@@ -20249,6 +20361,21 @@ components:
description: The service account key for GCP.
required:
- service_account_key
+ - type: object
+ title: Google Workspace Service Account
+ properties:
+ credentials_content:
+ type: string
+ description: The service account JSON credentials content for
+ Google Workspace API access with domain-wide delegation enabled.
+ delegated_user:
+ type: string
+ format: email
+ description: The email address of the Google Workspace super admin
+ user to impersonate for domain-wide delegation.
+ required:
+ - credentials_content
+ - delegated_user
- type: object
title: Kubernetes Static Credentials
properties:
@@ -20644,6 +20771,22 @@ components:
description: The service account key for GCP.
required:
- service_account_key
+ - type: object
+ title: Google Workspace Service Account
+ properties:
+ credentials_content:
+ type: string
+ description: The service account JSON credentials content
+ for Google Workspace API access with domain-wide delegation
+ enabled.
+ delegated_user:
+ type: string
+ format: email
+ description: The email address of the Google Workspace super
+ admin user to impersonate for domain-wide delegation.
+ required:
+ - credentials_content
+ - delegated_user
- type: object
title: Kubernetes Static Credentials
properties:
@@ -21060,6 +21203,21 @@ components:
description: The service account key for GCP.
required:
- service_account_key
+ - type: object
+ title: Google Workspace Service Account
+ properties:
+ credentials_content:
+ type: string
+ description: The service account JSON credentials content for
+ Google Workspace API access with domain-wide delegation enabled.
+ delegated_user:
+ type: string
+ format: email
+ description: The email address of the Google Workspace super admin
+ user to impersonate for domain-wide delegation.
+ required:
+ - credentials_content
+ - delegated_user
- type: object
title: Kubernetes Static Credentials
properties:
diff --git a/api/src/backend/api/tests/test_utils.py b/api/src/backend/api/tests/test_utils.py
index 24c051717d..a3b1bf5736 100644
--- a/api/src/backend/api/tests/test_utils.py
+++ b/api/src/backend/api/tests/test_utils.py
@@ -23,6 +23,9 @@ from prowler.providers.azure.azure_provider import AzureProvider
from prowler.providers.cloudflare.cloudflare_provider import CloudflareProvider
from prowler.providers.gcp.gcp_provider import GcpProvider
from prowler.providers.github.github_provider import GithubProvider
+from prowler.providers.googleworkspace.googleworkspace_provider import (
+ GoogleworkspaceProvider,
+)
from prowler.providers.iac.iac_provider import IacProvider
from prowler.providers.image.image_provider import ImageProvider
from prowler.providers.kubernetes.kubernetes_provider import KubernetesProvider
@@ -113,6 +116,7 @@ class TestReturnProwlerProvider:
[
(Provider.ProviderChoices.AWS.value, AwsProvider),
(Provider.ProviderChoices.GCP.value, GcpProvider),
+ (Provider.ProviderChoices.GOOGLEWORKSPACE.value, GoogleworkspaceProvider),
(Provider.ProviderChoices.AZURE.value, AzureProvider),
(Provider.ProviderChoices.KUBERNETES.value, KubernetesProvider),
(Provider.ProviderChoices.M365.value, M365Provider),
@@ -248,6 +252,10 @@ class TestGetProwlerProviderKwargs:
Provider.ProviderChoices.GCP.value,
{"project_ids": ["provider_uid"]},
),
+ (
+ Provider.ProviderChoices.GOOGLEWORKSPACE.value,
+ {},
+ ),
(
Provider.ProviderChoices.KUBERNETES.value,
{"context": "provider_uid"},
diff --git a/api/src/backend/api/tests/test_views.py b/api/src/backend/api/tests/test_views.py
index 1631b4878d..6f48d5cf41 100644
--- a/api/src/backend/api/tests/test_views.py
+++ b/api/src/backend/api/tests/test_views.py
@@ -1190,6 +1190,26 @@ class TestProviderViewSet:
"uid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"alias": "OpenStack Project",
},
+ {
+ "provider": "googleworkspace",
+ "uid": "C01234abc",
+ "alias": "Google Workspace Customer",
+ },
+ {
+ "provider": "googleworkspace",
+ "uid": "C12345678",
+ "alias": "Google Workspace All Digits",
+ },
+ {
+ "provider": "googleworkspace",
+ "uid": "CABCDEF123",
+ "alias": "Google Workspace Uppercase",
+ },
+ {
+ "provider": "googleworkspace",
+ "uid": "C12",
+ "alias": "Google Workspace Minimum Length",
+ },
]
),
)
@@ -1638,6 +1658,36 @@ class TestProviderViewSet:
"min_length",
"uid",
),
+ # Google Workspace UID validation - missing 'C' prefix
+ (
+ {
+ "provider": "googleworkspace",
+ "uid": "01234abc",
+ "alias": "test",
+ },
+ "googleworkspace-uid",
+ "uid",
+ ),
+ # Google Workspace UID validation - contains special characters
+ (
+ {
+ "provider": "googleworkspace",
+ "uid": "C0123-abc",
+ "alias": "test",
+ },
+ "googleworkspace-uid",
+ "uid",
+ ),
+ # Google Workspace UID validation - lowercase 'c' prefix
+ (
+ {
+ "provider": "googleworkspace",
+ "uid": "c12345678",
+ "alias": "test",
+ },
+ "googleworkspace-uid",
+ "uid",
+ ),
]
),
)
@@ -1811,21 +1861,21 @@ class TestProviderViewSet:
(
"uid.icontains",
"1",
- 10,
+ 11,
),
("alias", "aws_testing_1", 1),
("alias.icontains", "aws", 2),
- ("inserted_at", TODAY, 11),
+ ("inserted_at", TODAY, 12),
(
"inserted_at.gte",
"2024-01-01",
- 11,
+ 12,
),
("inserted_at.lte", "2024-01-01", 0),
(
"updated_at.gte",
"2024-01-01",
- 11,
+ 12,
),
("updated_at.lte", "2024-01-01", 0),
]
@@ -2441,6 +2491,15 @@ class TestProviderSecretViewSet:
"clouds_yaml_cloud": "mycloud",
},
),
+ # Google Workspace with service account credentials
+ (
+ Provider.ProviderChoices.GOOGLEWORKSPACE.value,
+ ProviderSecret.TypeChoices.STATIC,
+ {
+ "credentials_content": '{"type": "service_account", "project_id": "test-project", "private_key_id": "key123", "private_key": "-----BEGIN PRIVATE KEY-----\\ntest\\n-----END PRIVATE KEY-----\\n", "client_email": "test@test-project.iam.gserviceaccount.com", "client_id": "123456789"}',
+ "delegated_user": "admin@example.com",
+ },
+ ),
],
)
def test_provider_secrets_create_valid(
diff --git a/api/src/backend/api/utils.py b/api/src/backend/api/utils.py
index d856bd5e6a..6d4102af55 100644
--- a/api/src/backend/api/utils.py
+++ b/api/src/backend/api/utils.py
@@ -27,6 +27,9 @@ if TYPE_CHECKING:
from prowler.providers.cloudflare.cloudflare_provider import CloudflareProvider
from prowler.providers.gcp.gcp_provider import GcpProvider
from prowler.providers.github.github_provider import GithubProvider
+ from prowler.providers.googleworkspace.googleworkspace_provider import (
+ GoogleworkspaceProvider,
+ )
from prowler.providers.iac.iac_provider import IacProvider
from prowler.providers.image.image_provider import ImageProvider
from prowler.providers.kubernetes.kubernetes_provider import KubernetesProvider
@@ -83,6 +86,7 @@ def return_prowler_provider(
| CloudflareProvider
| GcpProvider
| GithubProvider
+ | GoogleworkspaceProvider
| IacProvider
| ImageProvider
| KubernetesProvider
@@ -97,7 +101,7 @@ def return_prowler_provider(
provider (Provider): The provider object containing the provider type and associated secrets.
Returns:
- AlibabacloudProvider | AwsProvider | AzureProvider | CloudflareProvider | GcpProvider | GithubProvider | IacProvider | ImageProvider | KubernetesProvider | M365Provider | MongodbatlasProvider | OpenstackProvider | OraclecloudProvider: The corresponding provider class.
+ AlibabacloudProvider | AwsProvider | AzureProvider | CloudflareProvider | GcpProvider | GithubProvider | GoogleworkspaceProvider | IacProvider | ImageProvider | KubernetesProvider | M365Provider | MongodbatlasProvider | OpenstackProvider | OraclecloudProvider: The corresponding provider class.
Raises:
ValueError: If the provider type specified in `provider.provider` is not supported.
@@ -111,6 +115,12 @@ def return_prowler_provider(
from prowler.providers.gcp.gcp_provider import GcpProvider
prowler_provider = GcpProvider
+ case Provider.ProviderChoices.GOOGLEWORKSPACE.value:
+ from prowler.providers.googleworkspace.googleworkspace_provider import (
+ GoogleworkspaceProvider,
+ )
+
+ prowler_provider = GoogleworkspaceProvider
case Provider.ProviderChoices.AZURE.value:
from prowler.providers.azure.azure_provider import AzureProvider
@@ -263,6 +273,7 @@ def initialize_prowler_provider(
| CloudflareProvider
| GcpProvider
| GithubProvider
+ | GoogleworkspaceProvider
| IacProvider
| ImageProvider
| KubernetesProvider
@@ -278,7 +289,7 @@ def initialize_prowler_provider(
mutelist_processor (Processor): The mutelist processor object containing the mutelist configuration.
Returns:
- AlibabacloudProvider | AwsProvider | AzureProvider | CloudflareProvider | GcpProvider | GithubProvider | IacProvider | ImageProvider | KubernetesProvider | M365Provider | MongodbatlasProvider | OpenstackProvider | OraclecloudProvider: An instance of the corresponding provider class
+ AlibabacloudProvider | AwsProvider | AzureProvider | CloudflareProvider | GcpProvider | GithubProvider | GoogleworkspaceProvider | IacProvider | ImageProvider | KubernetesProvider | M365Provider | MongodbatlasProvider | OpenstackProvider | OraclecloudProvider: An instance of the corresponding provider class
initialized with the provider's secrets.
"""
prowler_provider = return_prowler_provider(provider)
diff --git a/api/src/backend/api/v1/serializer_utils/providers.py b/api/src/backend/api/v1/serializer_utils/providers.py
index 83ea942793..034bc79cb2 100644
--- a/api/src/backend/api/v1/serializer_utils/providers.py
+++ b/api/src/backend/api/v1/serializer_utils/providers.py
@@ -191,6 +191,22 @@ from rest_framework_json_api import serializers
},
"required": ["service_account_key"],
},
+ {
+ "type": "object",
+ "title": "Google Workspace Service Account",
+ "properties": {
+ "credentials_content": {
+ "type": "string",
+ "description": "The service account JSON credentials content for Google Workspace API access with domain-wide delegation enabled.",
+ },
+ "delegated_user": {
+ "type": "string",
+ "format": "email",
+ "description": "The email address of the Google Workspace super admin user to impersonate for domain-wide delegation.",
+ },
+ },
+ "required": ["credentials_content", "delegated_user"],
+ },
{
"type": "object",
"title": "Kubernetes Static Credentials",
diff --git a/api/src/backend/api/v1/serializers.py b/api/src/backend/api/v1/serializers.py
index 3ed623d365..cfa7ec6c37 100644
--- a/api/src/backend/api/v1/serializers.py
+++ b/api/src/backend/api/v1/serializers.py
@@ -1541,6 +1541,8 @@ class BaseWriteProviderSecretSerializer(BaseWriteSerializer):
serializer = AzureProviderSecret(data=secret)
elif provider_type == Provider.ProviderChoices.GCP.value:
serializer = GCPProviderSecret(data=secret)
+ elif provider_type == Provider.ProviderChoices.GOOGLEWORKSPACE.value:
+ serializer = GoogleWorkspaceProviderSecret(data=secret)
elif provider_type == Provider.ProviderChoices.GITHUB.value:
serializer = GithubProviderSecret(data=secret)
elif provider_type == Provider.ProviderChoices.IAC.value:
@@ -1676,6 +1678,14 @@ class GCPServiceAccountProviderSecret(serializers.Serializer):
resource_name = "provider-secrets"
+class GoogleWorkspaceProviderSecret(serializers.Serializer):
+ credentials_content = serializers.CharField()
+ delegated_user = serializers.EmailField()
+
+ class Meta:
+ resource_name = "provider-secrets"
+
+
class MongoDBAtlasProviderSecret(serializers.Serializer):
atlas_public_key = serializers.CharField()
atlas_private_key = serializers.CharField()
diff --git a/api/src/backend/conftest.py b/api/src/backend/conftest.py
index 209292ffad..6535706cf7 100644
--- a/api/src/backend/conftest.py
+++ b/api/src/backend/conftest.py
@@ -543,6 +543,12 @@ def providers_fixture(tenants_fixture):
alias="openstack_testing",
tenant_id=tenant.id,
)
+ provider12 = Provider.objects.create(
+ provider="googleworkspace",
+ uid="C12345678",
+ alias="googleworkspace_testing",
+ tenant_id=tenant.id,
+ )
return (
provider1,
@@ -556,6 +562,7 @@ def providers_fixture(tenants_fixture):
provider9,
provider10,
provider11,
+ provider12,
)
diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md
index 6ce203d0b2..d7b603649d 100644
--- a/prowler/CHANGELOG.md
+++ b/prowler/CHANGELOG.md
@@ -110,6 +110,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
### 🐞 Fixed
+- Google Workspace provider `test_connection()` missing `provider_id` parameter for API integration [(#10247)](https://github.com/prowler-cloud/prowler/pull/10247)
- Update AWS checks metadata URLs to replace deprecated Trend Micro CloudOne Conformity (EOL July 2026) with Vision One and remove docs.prowler.com references [(#10068)](https://github.com/prowler-cloud/prowler/pull/10068)
- Standardize resource_id values across Azure checks to use actual Azure resource IDs and prevent duplicate resource entries [(#9994)](https://github.com/prowler-cloud/prowler/pull/9994)
- VPC endpoint service collection filtering third-party services that caused AccessDenied errors on `DescribeVpcEndpointServicePermissions` [(#10152)](https://github.com/prowler-cloud/prowler/pull/10152)
diff --git a/prowler/providers/googleworkspace/exceptions/exceptions.py b/prowler/providers/googleworkspace/exceptions/exceptions.py
index f38f6481c2..9e4cd2fea7 100644
--- a/prowler/providers/googleworkspace/exceptions/exceptions.py
+++ b/prowler/providers/googleworkspace/exceptions/exceptions.py
@@ -38,6 +38,10 @@ class GoogleWorkspaceBaseException(ProwlerException):
"message": "Service Account does not have required OAuth scopes",
"remediation": "Ensure the Service Account has the required scopes configured in Domain-Wide Delegation settings.",
},
+ (12008, "GoogleWorkspaceInvalidProviderIdError"): {
+ "message": "The provided provider_id does not match the credentials customer ID",
+ "remediation": "Check the provider_id (Customer ID) and ensure it matches the Google Workspace organization for the given credentials.",
+ },
}
def __init__(self, code, file=None, original_exception=None, message=None):
@@ -117,3 +121,10 @@ class GoogleWorkspaceInsufficientScopesError(GoogleWorkspaceCredentialsError):
super().__init__(
12007, file=file, original_exception=original_exception, message=message
)
+
+
+class GoogleWorkspaceInvalidProviderIdError(GoogleWorkspaceBaseException):
+ def __init__(self, file=None, original_exception=None, message=None):
+ super().__init__(
+ 12008, file=file, original_exception=original_exception, message=message
+ )
diff --git a/prowler/providers/googleworkspace/googleworkspace_provider.py b/prowler/providers/googleworkspace/googleworkspace_provider.py
index 61f85f2f24..83beee0d3e 100644
--- a/prowler/providers/googleworkspace/googleworkspace_provider.py
+++ b/prowler/providers/googleworkspace/googleworkspace_provider.py
@@ -21,6 +21,7 @@ from prowler.providers.googleworkspace.exceptions.exceptions import (
GoogleWorkspaceImpersonationError,
GoogleWorkspaceInsufficientScopesError,
GoogleWorkspaceInvalidCredentialsError,
+ GoogleWorkspaceInvalidProviderIdError,
GoogleWorkspaceMissingDelegatedUserError,
GoogleWorkspaceNoCredentialsError,
GoogleWorkspaceSetUpIdentityError,
@@ -479,6 +480,7 @@ class GoogleworkspaceProvider(Provider):
credentials_content: str = None,
delegated_user: str = None,
raise_on_exception: bool = True,
+ provider_id: str = None,
) -> Connection:
"""Test connection to Google Workspace.
@@ -489,6 +491,7 @@ class GoogleworkspaceProvider(Provider):
credentials_content (str): Service Account JSON credentials as a string.
delegated_user (str): Email of the user to impersonate via Domain-Wide Delegation.
raise_on_exception (bool): Flag indicating whether to raise an exception if the connection fails.
+ provider_id (str): The provider ID (Customer ID). Optional, not used in connection test.
Returns:
Connection: Connection object with success status or error information.
@@ -515,7 +518,16 @@ class GoogleworkspaceProvider(Provider):
)
# Set up the identity to test the connection
- GoogleworkspaceProvider.setup_identity(session, resolved_delegated_user)
+ identity = GoogleworkspaceProvider.setup_identity(
+ session, resolved_delegated_user
+ )
+
+ # Validate provider_id matches the customer_id from credentials
+ if provider_id and provider_id != identity.customer_id:
+ raise GoogleWorkspaceInvalidProviderIdError(
+ file=os.path.basename(__file__),
+ message=f"The provider ID {provider_id} does not match the credentials customer ID {identity.customer_id}",
+ )
return Connection(is_connected=True)
except Exception as error:
From db7554c8fbc1c5c712b7d191f25c775c029a0d84 Mon Sep 17 00:00:00 2001
From: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com>
Date: Wed, 11 Mar 2026 13:13:28 +0100
Subject: [PATCH 021/108] feat(ui): redesign providers page with modern table
and cloud recursion (#10292)
---
ui/CHANGELOG.md | 5 +-
ui/actions/manage-groups/manage-groups.ts | 15 +-
.../organizations/organizations.test.ts | 66 ++
ui/actions/organizations/organizations.ts | 74 +-
.../_components/provider-type-selector.tsx | 6 +-
ui/app/(prowler)/manage-groups/page.tsx | 191 +----
.../providers/account-groups-content.tsx | 162 ++++
ui/app/(prowler)/providers/page.test.ts | 22 +
ui/app/(prowler)/providers/page.tsx | 155 ++--
.../providers/provider-page-tabs.shared.ts | 21 +
.../providers/provider-page-tabs.test.tsx | 83 ++
.../providers/provider-page-tabs.tsx | 59 ++
.../providers/providers-page.utils.test.ts | 806 ++++++++++++++++++
.../providers/providers-page.utils.ts | 632 ++++++++++++++
.../providers-badge/iac-provider-badge.tsx | 33 +-
.../manage-groups/forms/delete-group-form.tsx | 2 +-
.../manage-groups/forms/edit-group-form.tsx | 4 +-
.../manage-groups/manage-groups-button.tsx | 2 +-
.../table/data-table-row-actions.tsx | 8 +-
.../providers/add-provider-button.tsx | 7 +-
ui/components/providers/index.ts | 2 +
ui/components/providers/link-to-scans.tsx | 18 +-
ui/components/providers/provider-info.tsx | 54 +-
.../providers/providers-accounts-table.tsx | 84 ++
ui/components/providers/providers-filters.tsx | 153 ++++
.../providers/table/column-providers.tsx | 466 +++++++---
.../table/data-table-row-actions.test.tsx | 103 +++
.../table/data-table-row-actions.tsx | 183 +++-
.../table/skeleton-table-provider.tsx | 151 +++-
.../wizard/steps/credentials-step.test.tsx | 13 +
.../workflow/forms/test-connection-form.tsx | 83 +-
ui/components/shadcn/select/multiselect.tsx | 2 +-
.../ui/table/data-table-animated-row.tsx | 19 +-
.../ui/table/data-table-expand-all-toggle.tsx | 2 +-
.../ui/table/data-table-expand-toggle.tsx | 25 +-
.../ui/table/data-table-expandable-cell.tsx | 33 +-
ui/components/ui/table/data-table.tsx | 7 +-
ui/lib/helper-filters.ts | 14 +-
ui/lib/menu-list.ts | 2 -
ui/lib/provider-helpers.ts | 53 ++
ui/tests/providers/providers-page.ts | 4 +-
ui/types/filters.ts | 9 +-
ui/types/index.ts | 1 +
ui/types/organizations.ts | 50 ++
ui/types/providers-table.ts | 98 +++
ui/types/providers.ts | 5 +
46 files changed, 3361 insertions(+), 626 deletions(-)
create mode 100644 ui/app/(prowler)/providers/account-groups-content.tsx
create mode 100644 ui/app/(prowler)/providers/provider-page-tabs.shared.ts
create mode 100644 ui/app/(prowler)/providers/provider-page-tabs.test.tsx
create mode 100644 ui/app/(prowler)/providers/provider-page-tabs.tsx
create mode 100644 ui/app/(prowler)/providers/providers-page.utils.test.ts
create mode 100644 ui/app/(prowler)/providers/providers-page.utils.ts
create mode 100644 ui/components/providers/providers-accounts-table.tsx
create mode 100644 ui/components/providers/providers-filters.tsx
create mode 100644 ui/components/providers/table/data-table-row-actions.test.tsx
create mode 100644 ui/types/providers-table.ts
diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md
index bff954f997..564b77e6a9 100644
--- a/ui/CHANGELOG.md
+++ b/ui/CHANGELOG.md
@@ -8,14 +8,15 @@ All notable changes to the **Prowler UI** are documented in this file.
- Attack Paths: Improved error handling for server errors (5xx) and network failures with user-friendly messages instead of raw internal errors and layout changes. [(#10249)](https://github.com/prowler-cloud/prowler/pull/10249)
- Refactor simple providers with new components and styles.[(#10259)](https://github.com/prowler-cloud/prowler/pull/10259)
+- Providers page redesigned with cloud organization hierarchy, HeroUI-to-shadcn migration, organization and account group filters, and row selection for bulk actions [(#10292)](https://github.com/prowler-cloud/prowler/pull/10292)
- AWS Organizations onboarding now uses a clearer 3-step flow: deploy the ProwlerScan role in the management account via CloudFormation Stack, deploy to member accounts via StackSet with a copyable template URL, and confirm with the Role ARN [(#10274)](https://github.com/prowler-cloud/prowler/pull/10274)
----
-
### 🔐 Security
- npm transitive dependencies patched to resolve 11 Dependabot alerts (6 HIGH, 4 MEDIUM, 1 LOW): hono, @hono/node-server, fast-xml-parser, serialize-javascript, minimatch [(#10267)](https://github.com/prowler-cloud/prowler/pull/10267)
+---
+
## [1.19.1] (Prowler v5.19.1 UNRELEASED)
### 🐞 Fixed
diff --git a/ui/actions/manage-groups/manage-groups.ts b/ui/actions/manage-groups/manage-groups.ts
index 8899029668..8758b66f68 100644
--- a/ui/actions/manage-groups/manage-groups.ts
+++ b/ui/actions/manage-groups/manage-groups.ts
@@ -22,7 +22,8 @@ export const getProviderGroups = async ({
}): Promise => {
const headers = await getAuthHeaders({ contentType: false });
- if (isNaN(Number(page)) || page < 1) redirect("/manage-groups");
+ if (isNaN(Number(page)) || page < 1)
+ redirect("/providers?tab=account-groups");
const url = new URL(`${apiBaseUrl}/provider-groups`);
@@ -43,7 +44,7 @@ export const getProviderGroups = async ({
headers,
});
- return handleApiResponse(response);
+ return await handleApiResponse(response);
} catch (error) {
console.error("Error fetching provider groups:", error);
return undefined;
@@ -60,7 +61,7 @@ export const getProviderGroupInfoById = async (providerGroupId: string) => {
headers,
});
- return handleApiResponse(response);
+ return await handleApiResponse(response);
} catch (error) {
handleApiError(error);
}
@@ -111,7 +112,7 @@ export const createProviderGroup = async (formData: FormData) => {
body,
});
- return handleApiResponse(response, "/manage-groups");
+ return await handleApiResponse(response, "/providers?tab=account-groups");
} catch (error) {
handleApiError(error);
}
@@ -156,7 +157,7 @@ export const updateProviderGroup = async (
body: JSON.stringify(payload),
});
- return handleApiResponse(response);
+ return await handleApiResponse(response);
} catch (error) {
handleApiError(error);
}
@@ -168,7 +169,7 @@ export const deleteProviderGroup = async (formData: FormData) => {
if (!providerGroupId) {
return {
- errors: [{ detail: "Provider Group ID is required." }],
+ errors: [{ detail: "Account Group ID is required." }],
};
}
@@ -196,7 +197,7 @@ export const deleteProviderGroup = async (formData: FormData) => {
data = await response.json();
}
- revalidatePath("/manage-groups");
+ revalidatePath("/providers");
return data || { success: true };
} catch (error) {
console.error("Error deleting provider group:", error);
diff --git a/ui/actions/organizations/organizations.test.ts b/ui/actions/organizations/organizations.test.ts
index 52a4b82eb3..be1eb6c404 100644
--- a/ui/actions/organizations/organizations.test.ts
+++ b/ui/actions/organizations/organizations.test.ts
@@ -31,6 +31,10 @@ vi.mock("@/lib/server-actions-helper", () => ({
import {
applyDiscovery,
getDiscovery,
+ listOrganizations,
+ listOrganizationsSafe,
+ listOrganizationUnits,
+ listOrganizationUnitsSafe,
triggerDiscovery,
updateOrganizationSecret,
} from "./organizations";
@@ -137,4 +141,66 @@ describe("organizations actions", () => {
expect(revalidatePathMock).toHaveBeenCalledTimes(1);
expect(revalidatePathMock).toHaveBeenCalledWith("/providers");
});
+
+ it("lists organizations with the expected filters", async () => {
+ // Given
+ handleApiResponseMock.mockResolvedValue({ data: [] });
+
+ // When
+ await listOrganizations();
+
+ // Then
+ expect(fetchMock).toHaveBeenCalledTimes(1);
+ expect(fetchMock.mock.calls[0]?.[0]).toBe(
+ "https://api.example.com/api/v1/organizations?filter%5Borg_type%5D=aws",
+ );
+ });
+
+ it("lists organization units from the dedicated endpoint", async () => {
+ // Given
+ handleApiResponseMock.mockResolvedValue({ data: [] });
+
+ // When
+ await listOrganizationUnits();
+
+ // Then
+ expect(fetchMock).toHaveBeenCalledTimes(1);
+ expect(fetchMock.mock.calls[0]?.[0]).toBe(
+ "https://api.example.com/api/v1/organizational-units",
+ );
+ });
+
+ it("returns an empty organizations payload when the safe organizations request fails", async () => {
+ // Given
+ fetchMock.mockResolvedValue(
+ new Response("Internal Server Error", {
+ status: 500,
+ }),
+ );
+
+ // When
+ const result = await listOrganizationsSafe();
+
+ // Then
+ expect(result).toEqual({ data: [] });
+ expect(handleApiResponseMock).not.toHaveBeenCalled();
+ expect(handleApiErrorMock).not.toHaveBeenCalled();
+ });
+
+ it("returns an empty organization units payload when the safe request fails", async () => {
+ // Given
+ fetchMock.mockResolvedValue(
+ new Response("Internal Server Error", {
+ status: 500,
+ }),
+ );
+
+ // When
+ const result = await listOrganizationUnitsSafe();
+
+ // Then
+ expect(result).toEqual({ data: [] });
+ expect(handleApiResponseMock).not.toHaveBeenCalled();
+ expect(handleApiErrorMock).not.toHaveBeenCalled();
+ });
});
diff --git a/ui/actions/organizations/organizations.ts b/ui/actions/organizations/organizations.ts
index 16ddd7593d..6d0d0e6afd 100644
--- a/ui/actions/organizations/organizations.ts
+++ b/ui/actions/organizations/organizations.ts
@@ -4,6 +4,10 @@ import { revalidatePath } from "next/cache";
import { apiBaseUrl, getAuthHeaders } from "@/lib";
import { handleApiError, handleApiResponse } from "@/lib/server-actions-helper";
+import {
+ OrganizationListResponse,
+ OrganizationUnitListResponse,
+} from "@/types";
const PATH_IDENTIFIER_PATTERN = /^[A-Za-z0-9_-]+$/;
@@ -37,6 +41,24 @@ function hasActionError(result: unknown): result is { error: unknown } {
);
}
+async function fetchOptionalCollection(
+ url: URL,
+): Promise {
+ const headers = await getAuthHeaders({ contentType: false });
+
+ try {
+ const response = await fetch(url.toString(), { headers });
+
+ if (!response.ok) {
+ return { data: [] } as unknown as T;
+ }
+
+ return (await handleApiResponse(response)) as T;
+ } catch {
+ return { data: [] } as unknown as T;
+ }
+}
+
/**
* Creates an AWS Organization resource.
* POST /api/v1/organizations
@@ -82,12 +104,62 @@ export const listOrganizationsByExternalId = async (externalId: string) => {
try {
const response = await fetch(url.toString(), { headers });
- return handleApiResponse(response);
+ return await handleApiResponse(response);
} catch (error) {
return handleApiError(error);
}
};
+/**
+ * Lists AWS organizations available for the current tenant.
+ * GET /api/v1/organizations?filter[org_type]=aws
+ */
+export const listOrganizations = async () => {
+ const headers = await getAuthHeaders({ contentType: false });
+ const url = new URL(`${apiBaseUrl}/organizations`);
+ url.searchParams.set("filter[org_type]", "aws");
+
+ try {
+ const response = await fetch(url.toString(), { headers });
+ return await handleApiResponse(response);
+ } catch (error) {
+ return handleApiError(error);
+ }
+};
+
+export const listOrganizationsSafe =
+ async (): Promise => {
+ const url = new URL(`${apiBaseUrl}/organizations`);
+ url.searchParams.set("filter[org_type]", "aws");
+ url.searchParams.set("page[size]", "100");
+
+ return fetchOptionalCollection(url);
+ };
+
+/**
+ * Lists organization units available for the current tenant.
+ * GET /api/v1/organizational-units
+ */
+export const listOrganizationUnits = async () => {
+ const headers = await getAuthHeaders({ contentType: false });
+ const url = new URL(`${apiBaseUrl}/organizational-units`);
+
+ try {
+ const response = await fetch(url.toString(), { headers });
+ return await handleApiResponse(response);
+ } catch (error) {
+ return handleApiError(error);
+ }
+};
+
+export const listOrganizationUnitsSafe =
+ async (): Promise => {
+ const url = new URL(`${apiBaseUrl}/organizational-units`);
+ url.searchParams.set("page[size]", "100");
+
+ return fetchOptionalCollection(url);
+ };
+
/**
* Creates an organization secret (role-based credentials).
* POST /api/v1/organization-secrets
diff --git a/ui/app/(prowler)/_overview/_components/provider-type-selector.tsx b/ui/app/(prowler)/_overview/_components/provider-type-selector.tsx
index a1aaeaa6e5..881bc54969 100644
--- a/ui/app/(prowler)/_overview/_components/provider-type-selector.tsx
+++ b/ui/app/(prowler)/_overview/_components/provider-type-selector.tsx
@@ -186,14 +186,14 @@ export const ProviderTypeSelector = ({
if (selectedTypes.length === 1) {
const providerType = selectedTypes[0] as ProviderType;
return (
-
+
{renderIcon(providerType)}
- {PROVIDER_DATA[providerType].label}
+ {PROVIDER_DATA[providerType].label}
);
}
return (
-
+
{selectedTypes.length} providers selected
);
diff --git a/ui/app/(prowler)/manage-groups/page.tsx b/ui/app/(prowler)/manage-groups/page.tsx
index dc035c014e..8eb286f77a 100644
--- a/ui/app/(prowler)/manage-groups/page.tsx
+++ b/ui/app/(prowler)/manage-groups/page.tsx
@@ -1,20 +1,6 @@
-import { Divider } from "@heroui/divider";
-import { Spacer } from "@heroui/spacer";
import { redirect } from "next/navigation";
-import React, { Suspense } from "react";
-import {
- getProviderGroupInfoById,
- getProviderGroups,
-} from "@/actions/manage-groups/manage-groups";
-import { getProviders } from "@/actions/providers";
-import { getRoles } from "@/actions/roles";
-import { FilterControls } from "@/components/filters/filter-controls";
-import { AddGroupForm, EditGroupForm } from "@/components/manage-groups/forms";
-import { SkeletonManageGroups } from "@/components/manage-groups/skeleton-manage-groups";
-import { ColumnGroups } from "@/components/manage-groups/table";
-import { DataTable } from "@/components/ui/table";
-import { ProviderProps, Role, SearchParamsProps } from "@/types";
+import { SearchParamsProps } from "@/types";
export default async function ManageGroupsPage({
searchParams,
@@ -22,176 +8,11 @@ export default async function ManageGroupsPage({
searchParams: Promise;
}) {
const resolvedSearchParams = await searchParams;
- const searchParamsKey = JSON.stringify(resolvedSearchParams);
- const providerGroupId = resolvedSearchParams.groupId;
+ const groupId = resolvedSearchParams.groupId;
- return (
-
-
- }>
- {providerGroupId ? (
-
- ) : (
-
-
- Create a new provider group
-
-
- Create a new provider group to manage the providers and roles.
-