diff --git a/README.md b/README.md index 3519305248..dc44e85a45 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,7 @@ prowler dashboard | GitHub | 17 | 2 | 1 | 0 | Official | Stable | UI, API, CLI | | M365 | 70 | 7 | 3 | 2 | Official | UI, API, CLI | | OCI | 51 | 13 | 1 | 10 | Official | UI, API, CLI | +| Alibaba Cloud | 61 | 9 | 1 | 9 | Official | 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 | 0 | Official | UI, API, CLI | | LLM | [See `promptfoo` docs.](https://www.promptfoo.dev/docs/red-team/plugins/) | N/A | N/A | N/A | Official | CLI | diff --git a/dashboard/assets/images/providers/alibabacloud_provider.png b/dashboard/assets/images/providers/alibabacloud_provider.png new file mode 100644 index 0000000000..7b14b0c33b Binary files /dev/null and b/dashboard/assets/images/providers/alibabacloud_provider.png differ diff --git a/dashboard/compliance/cis_2_0_alibabacloud.py b/dashboard/compliance/cis_2_0_alibabacloud.py new file mode 100644 index 0000000000..94558f33ad --- /dev/null +++ b/dashboard/compliance/cis_2_0_alibabacloud.py @@ -0,0 +1,24 @@ +import warnings + +from dashboard.common_methods import get_section_containers_cis + +warnings.filterwarnings("ignore") + + +def get_table(data): + aux = data[ + [ + "REQUIREMENTS_ID", + "REQUIREMENTS_DESCRIPTION", + "REQUIREMENTS_ATTRIBUTES_SECTION", + "CHECKID", + "STATUS", + "REGION", + "ACCOUNTID", + "RESOURCEID", + ] + ].copy() + + return get_section_containers_cis( + aux, "REQUIREMENTS_ID", "REQUIREMENTS_ATTRIBUTES_SECTION" + ) diff --git a/dashboard/lib/layouts.py b/dashboard/lib/layouts.py index e054731684..9df7cefb16 100644 --- a/dashboard/lib/layouts.py +++ b/dashboard/lib/layouts.py @@ -61,6 +61,7 @@ def create_layout_overview( html.Div(className="flex", id="gcp_card", n_clicks=0), html.Div(className="flex", id="k8s_card", n_clicks=0), html.Div(className="flex", id="m365_card", n_clicks=0), + html.Div(className="flex", id="alibabacloud_card", n_clicks=0), ], className=f"grid gap-x-4 mb-[30px] sm:grid-cols-2 lg:grid-cols-{amount_providers}", ), diff --git a/dashboard/pages/compliance.py b/dashboard/pages/compliance.py index 92c6825a3b..c46dc2bb8a 100644 --- a/dashboard/pages/compliance.py +++ b/dashboard/pages/compliance.py @@ -76,8 +76,10 @@ def load_csv_files(csv_files): result = result.replace("_AZURE", " - AZURE") if "KUBERNETES" in result: result = result.replace("_KUBERNETES", " - KUBERNETES") - if "M365" in result: - result = result.replace("_M365", " - M365") + if "M65" in result: + result = result.replace("_M65", " - M65") + if "ALIBABACLOUD" in result: + result = result.replace("_ALIBABACLOUD", " - ALIBABACLOUD") results.append(result) unique_results = set(results) @@ -278,6 +280,10 @@ def display_data( data["REQUIREMENTS_ATTRIBUTES_PROFILE"] = data[ "REQUIREMENTS_ATTRIBUTES_PROFILE" ].apply(lambda x: x.split(" - ")[0]) + + # Rename the column LOCATION to REGION for Alibaba Cloud + if "alibabacloud" in analytics_input: + data = data.rename(columns={"LOCATION": "REGION"}) # Filter the chosen level of the CIS if is_level_1: data = data[data["REQUIREMENTS_ATTRIBUTES_PROFILE"].str.contains("Level 1")] diff --git a/dashboard/pages/overview.py b/dashboard/pages/overview.py index 4ce749a9e4..9bf44ac8ac 100644 --- a/dashboard/pages/overview.py +++ b/dashboard/pages/overview.py @@ -79,6 +79,9 @@ ks8_provider_logo = html.Img( m365_provider_logo = html.Img( src="assets/images/providers/m365_provider.png", alt="m365 provider" ) +alibabacloud_provider_logo = html.Img( + src="assets/images/providers/alibabacloud_provider.png", alt="alibabacloud provider" +) def load_csv_files(csv_files): @@ -253,6 +256,8 @@ else: accounts.append(account + " - AWS") if "kubernetes" in list(data[data["ACCOUNT_UID"] == account]["PROVIDER"]): accounts.append(account + " - K8S") + if "alibabacloud" in list(data[data["ACCOUNT_UID"] == account]["PROVIDER"]): + accounts.append(account + " - ALIBABACLOUD") account_dropdown = create_account_dropdown(accounts) @@ -298,6 +303,8 @@ else: services.append(service + " - GCP") if "m365" in list(data[data["SERVICE_NAME"] == service]["PROVIDER"]): services.append(service + " - M365") + if "alibabacloud" in list(data[data["SERVICE_NAME"] == service]["PROVIDER"]): + services.append(service + " - ALIBABACLOUD") services = ["All"] + services services = [ @@ -520,6 +527,7 @@ else: Output("gcp_card", "children"), Output("k8s_card", "children"), Output("m365_card", "children"), + Output("alibabacloud_card", "children"), Output("subscribe_card", "children"), Output("info-file-over", "title"), Output("severity-filter", "value"), @@ -537,6 +545,7 @@ else: Output("gcp_card", "n_clicks"), Output("k8s_card", "n_clicks"), Output("m365_card", "n_clicks"), + Output("alibabacloud_card", "n_clicks"), ], Input("cloud-account-filter", "value"), Input("region-filter", "value"), @@ -560,6 +569,7 @@ else: Input("sort_button_region", "n_clicks"), Input("sort_button_service", "n_clicks"), Input("sort_button_account", "n_clicks"), + Input("alibabacloud_card", "n_clicks"), ) def filter_data( cloud_account_values, @@ -584,6 +594,7 @@ def filter_data( sort_button_region, sort_button_service, sort_button_account, + alibabacloud_clicks, ): # Use n_clicks for vulture n_clicks_csv = n_clicks_csv @@ -599,6 +610,7 @@ def filter_data( gcp_clicks = 0 k8s_clicks = 0 m365_clicks = 0 + alibabacloud_clicks = 0 if azure_clicks > 0: filtered_data = data.copy() if azure_clicks % 2 != 0 and "azure" in list(data["PROVIDER"]): @@ -607,6 +619,7 @@ def filter_data( gcp_clicks = 0 k8s_clicks = 0 m365_clicks = 0 + alibabacloud_clicks = 0 if gcp_clicks > 0: filtered_data = data.copy() if gcp_clicks % 2 != 0 and "gcp" in list(data["PROVIDER"]): @@ -615,6 +628,7 @@ def filter_data( azure_clicks = 0 k8s_clicks = 0 m365_clicks = 0 + alibabacloud_clicks = 0 if k8s_clicks > 0: filtered_data = data.copy() if k8s_clicks % 2 != 0 and "kubernetes" in list(data["PROVIDER"]): @@ -623,6 +637,7 @@ def filter_data( azure_clicks = 0 gcp_clicks = 0 m365_clicks = 0 + alibabacloud_clicks = 0 if m365_clicks > 0: filtered_data = data.copy() if m365_clicks % 2 != 0 and "m365" in list(data["PROVIDER"]): @@ -631,7 +646,16 @@ def filter_data( azure_clicks = 0 gcp_clicks = 0 k8s_clicks = 0 - + alibabacloud_clicks = 0 + if alibabacloud_clicks > 0: + filtered_data = data.copy() + if alibabacloud_clicks % 2 != 0 and "alibabacloud" in list(data["PROVIDER"]): + filtered_data = filtered_data[filtered_data["PROVIDER"] == "alibabacloud"] + aws_clicks = 0 + azure_clicks = 0 + gcp_clicks = 0 + k8s_clicks = 0 + m365_clicks = 0 # For all the data, we will add to the status column the value 'MUTED (FAIL)' and 'MUTED (PASS)' depending on the value of the column 'STATUS' and 'MUTED' if "MUTED" in filtered_data.columns: filtered_data["STATUS"] = filtered_data.apply( @@ -723,6 +747,8 @@ def filter_data( all_account_ids.append(account) if "kubernetes" in list(data[data["ACCOUNT_UID"] == account]["PROVIDER"]): all_account_ids.append(account) + if "alibabacloud" in list(data[data["ACCOUNT_UID"] == account]["PROVIDER"]): + all_account_ids.append(account) all_account_names = [] if "ACCOUNT_NAME" in filtered_data.columns: @@ -745,6 +771,10 @@ def filter_data( cloud_accounts_options.append(item + " - AWS") if "kubernetes" in list(data[data["ACCOUNT_UID"] == item]["PROVIDER"]): cloud_accounts_options.append(item + " - K8S") + if "alibabacloud" in list( + data[data["ACCOUNT_UID"] == item]["PROVIDER"] + ): + cloud_accounts_options.append(item + " - ALIBABACLOUD") if "ACCOUNT_NAME" in filtered_data.columns: if "azure" in list(data[data["ACCOUNT_NAME"] == item]["PROVIDER"]): cloud_accounts_options.append(item + " - AZURE") @@ -873,6 +903,10 @@ def filter_data( filtered_data[filtered_data["SERVICE_NAME"] == item]["PROVIDER"] ): service_filter_options.append(item + " - M365") + if "alibabacloud" in list( + filtered_data[filtered_data["SERVICE_NAME"] == item]["PROVIDER"] + ): + service_filter_options.append(item + " - ALIBABACLOUD") # Filter Service if service_values == ["All"]: @@ -1324,6 +1358,12 @@ def filter_data( filtered_data.loc[ filtered_data["ACCOUNT_UID"] == account, "ACCOUNT_UID" ] = (account + " - M365") + if "alibabacloud" in list( + data[data["ACCOUNT_UID"] == account]["PROVIDER"] + ): + filtered_data.loc[ + filtered_data["ACCOUNT_UID"] == account, "ACCOUNT_UID" + ] = (account + " - ALIBABACLOUD") table_collapsible = [] for item in filtered_data.to_dict("records"): @@ -1410,6 +1450,13 @@ def filter_data( else: m365_card = None + if "alibabacloud" in list(data["PROVIDER"].unique()): + alibabacloud_card = create_provider_card( + "alibabacloud", alibabacloud_provider_logo, "Accounts", full_filtered_data + ) + else: + alibabacloud_card = None + # Subscribe to Prowler Cloud card subscribe_card = [ html.Div( @@ -1454,6 +1501,7 @@ def filter_data( gcp_card, k8s_card, m365_card, + alibabacloud_card, subscribe_card, list_files, severity_values, @@ -1469,6 +1517,7 @@ def filter_data( gcp_clicks, k8s_clicks, m365_clicks, + alibabacloud_clicks, ) else: return ( @@ -1487,6 +1536,7 @@ def filter_data( gcp_card, k8s_card, m365_card, + alibabacloud_card, subscribe_card, list_files, severity_values, @@ -1504,6 +1554,7 @@ def filter_data( gcp_clicks, k8s_clicks, m365_clicks, + alibabacloud_clicks, ) diff --git a/docs/docs.json b/docs/docs.json index 51b3960333..d45a05158d 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -198,6 +198,13 @@ "user-guide/providers/gcp/retry-configuration" ] }, + { + "group": "Alibaba Cloud", + "pages": [ + "user-guide/providers/alibabacloud/getting-started-alibabacloud", + "user-guide/providers/alibabacloud/authentication" + ] + }, { "group": "Kubernetes", "pages": [ diff --git a/docs/user-guide/providers/alibabacloud/authentication.mdx b/docs/user-guide/providers/alibabacloud/authentication.mdx new file mode 100644 index 0000000000..0baa160e69 --- /dev/null +++ b/docs/user-guide/providers/alibabacloud/authentication.mdx @@ -0,0 +1,112 @@ +--- +title: 'Alibaba Cloud Authentication in Prowler' +--- + +Prowler requires Alibaba Cloud credentials to perform security checks. Authentication is supported via multiple methods, prioritized as follows: + +1. **Credentials URI** +2. **OIDC Role Authentication** +3. **ECS RAM Role** +4. **RAM Role Assumption** +5. **STS Temporary Credentials** +6. **Permanent Access Keys** +7. **Default Credential Chain** + +## Authentication Methods + +### Credentials URI (Recommended for Centralized Services) + +If `--credentials-uri` is provided (or `ALIBABA_CLOUD_CREDENTIALS_URI` environment variable), Prowler will retrieve credentials from the specified external URI endpoint. The URI must return credentials in the standard JSON format. + +```bash +export ALIBABA_CLOUD_CREDENTIALS_URI="http://localhost:8080/credentials" +prowler alibabacloud +``` + +### OIDC Role Authentication (Recommended for ACK/Kubernetes) + +If OIDC environment variables are set, Prowler will use OIDC authentication to assume the specified role. This is the most secure method for containerized applications running in ACK (Alibaba Container Service for Kubernetes) with RRSA enabled. + +Required environment variables: +- `ALIBABA_CLOUD_ROLE_ARN` +- `ALIBABA_CLOUD_OIDC_PROVIDER_ARN` +- `ALIBABA_CLOUD_OIDC_TOKEN_FILE` + +```bash +export ALIBABA_CLOUD_ROLE_ARN="acs:ram::123456789012:role/YourRole" +export ALIBABA_CLOUD_OIDC_PROVIDER_ARN="acs:ram::123456789012:oidc-provider/ack-rrsa-provider" +export ALIBABA_CLOUD_OIDC_TOKEN_FILE="/var/run/secrets/tokens/oidc-token" +prowler alibabacloud +``` + +### ECS RAM Role (Recommended for ECS Instances) + +When running on an ECS instance with an attached RAM role, Prowler can obtain credentials from the ECS instance metadata service. + +```bash +# Using CLI argument +prowler alibabacloud --ecs-ram-role RoleName + +# Or using environment variable +export ALIBABA_CLOUD_ECS_METADATA="RoleName" +prowler alibabacloud +``` + +### RAM Role Assumption (Recommended for Cross-Account) + +For cross-account access, use RAM role assumption. You must provide the initial credentials (access keys) and the target role ARN. + +```bash +export ALIBABA_CLOUD_ACCESS_KEY_ID="your-access-key-id" +export ALIBABA_CLOUD_ACCESS_KEY_SECRET="your-access-key-secret" +export ALIBABA_CLOUD_ROLE_ARN="acs:ram::123456789012:role/ProwlerAuditRole" +prowler alibabacloud +``` + +### STS Temporary Credentials + +If you already have temporary STS credentials, you can provide them via environment variables. + +```bash +export ALIBABA_CLOUD_ACCESS_KEY_ID="your-sts-access-key-id" +export ALIBABA_CLOUD_ACCESS_KEY_SECRET="your-sts-access-key-secret" +export ALIBABA_CLOUD_SECURITY_TOKEN="your-sts-security-token" +prowler alibabacloud +``` + +### Permanent Access Keys + +You can use standard permanent access keys via environment variables. + +```bash +export ALIBABA_CLOUD_ACCESS_KEY_ID="your-access-key-id" +export ALIBABA_CLOUD_ACCESS_KEY_SECRET="your-access-key-secret" +prowler alibabacloud +``` + +## Required Permissions + +The credentials used by Prowler should have the minimum required permissions to audit the resources. At a minimum, the following permissions are recommended: + +- `ram:GetUser` +- `ram:ListUsers` +- `ram:GetPasswordPolicy` +- `ram:GetAccountSummary` +- `ram:ListVirtualMFADevices` +- `ram:ListGroups` +- `ram:ListPolicies` +- `ram:ListAccessKeys` +- `ram:GetLoginProfile` +- `ram:ListPoliciesForUser` +- `ram:ListGroupsForUser` +- `actiontrail:DescribeTrails` +- `oss:GetBucketLogging` +- `oss:GetBucketAcl` +- `rds:DescribeDBInstances` +- `rds:DescribeDBInstanceAttribute` +- `ecs:DescribeInstances` +- `vpc:DescribeVpcs` +- `sls:ListProject` +- `sls:ListAlerts` +- `sls:ListLogStores` +- `sls:GetLogStore` diff --git a/docs/user-guide/providers/alibabacloud/getting-started-alibabacloud.mdx b/docs/user-guide/providers/alibabacloud/getting-started-alibabacloud.mdx new file mode 100644 index 0000000000..469f026b5a --- /dev/null +++ b/docs/user-guide/providers/alibabacloud/getting-started-alibabacloud.mdx @@ -0,0 +1,132 @@ +--- +title: 'Getting Started With Alibaba Cloud on Prowler' +--- + +## Prowler CLI + +### Configure Alibaba Cloud Credentials + +Prowler requires Alibaba Cloud credentials to perform security checks. Authentication is available through the following methods (in order of priority): + +1. **Credentials URI** (Recommended for centralized credential services) +2. **OIDC Role Authentication** (Recommended for ACK/Kubernetes) +3. **ECS RAM Role** (Recommended for ECS instances) +4. **RAM Role Assumption** (Recommended for cross-account access) +5. **STS Temporary Credentials** +6. **Permanent Access Keys** +7. **Default Credential Chain** + + +Prowler does not accept credentials through command-line arguments. Provide credentials through environment variables or the Alibaba Cloud credential chain. + + + +#### Option 1: Environment Variables (Permanent Credentials) + +```bash +export ALIBABA_CLOUD_ACCESS_KEY_ID="your-access-key-id" +export ALIBABA_CLOUD_ACCESS_KEY_SECRET="your-access-key-secret" +prowler alibabacloud +``` + +#### Option 2: Environment Variables (STS Temporary Credentials) + +```bash +export ALIBABA_CLOUD_ACCESS_KEY_ID="your-sts-access-key-id" +export ALIBABA_CLOUD_ACCESS_KEY_SECRET="your-sts-access-key-secret" +export ALIBABA_CLOUD_SECURITY_TOKEN="your-sts-security-token" +prowler alibabacloud +``` + +#### Option 3: RAM Role Assumption (Environment Variables) + +```bash +export ALIBABA_CLOUD_ACCESS_KEY_ID="your-access-key-id" +export ALIBABA_CLOUD_ACCESS_KEY_SECRET="your-access-key-secret" +export ALIBABA_CLOUD_ROLE_ARN="acs:ram::123456789012:role/ProwlerAuditRole" +export ALIBABA_CLOUD_ROLE_SESSION_NAME="ProwlerAssessmentSession" # Optional +prowler alibabacloud +``` + +#### Option 4: RAM Role Assumption (CLI + Environment Variables) + +```bash +# Set credentials via environment variables +export ALIBABA_CLOUD_ACCESS_KEY_ID="your-access-key-id" +export ALIBABA_CLOUD_ACCESS_KEY_SECRET="your-access-key-secret" +# Specify role via CLI argument +prowler alibabacloud --role-arn acs:ram::123456789012:role/ProwlerAuditRole --role-session-name ProwlerAssessmentSession +``` + +#### Option 5: ECS Instance Metadata (ECS RAM Role) + +```bash +# When running on an ECS instance with an attached RAM role +prowler alibabacloud --ecs-ram-role RoleName + +# Or using environment variable +export ALIBABA_CLOUD_ECS_METADATA="RoleName" +prowler alibabacloud +``` + +#### Option 6: OIDC Role Authentication (for ACK/Kubernetes) + +```bash +# For applications running in ACK (Alibaba Container Service for Kubernetes) with RRSA enabled +export ALIBABA_CLOUD_ROLE_ARN="acs:ram::123456789012:role/YourRole" +export ALIBABA_CLOUD_OIDC_PROVIDER_ARN="acs:ram::123456789012:oidc-provider/ack-rrsa-provider" +export ALIBABA_CLOUD_OIDC_TOKEN_FILE="/var/run/secrets/tokens/oidc-token" +export ALIBABA_CLOUD_ROLE_SESSION_NAME="ProwlerOIDCSession" # Optional +prowler alibabacloud + +# Or using CLI argument +prowler alibabacloud --oidc-role-arn acs:ram::123456789012:role/YourRole +``` + +#### Option 7: Credentials URI (External Credential Service) + +```bash +# Retrieve credentials from an external URI endpoint +export ALIBABA_CLOUD_CREDENTIALS_URI="http://localhost:8080/credentials" +prowler alibabacloud + +# Or using CLI argument +prowler alibabacloud --credentials-uri http://localhost:8080/credentials +``` + +#### Option 8: Default Credential Chain + +The SDK automatically checks credentials in the following order: +1. Environment variables (`ALIBABA_CLOUD_*` or `ALIYUN_*`) +2. OIDC authentication (if OIDC environment variables are set) +3. Configuration file (`~/.aliyun/config.json`) +4. ECS instance metadata (if running on ECS) +5. Credentials URI (if `ALIBABA_CLOUD_CREDENTIALS_URI` is set) + +```bash +prowler alibabacloud +``` + +### Specify Regions + +To run checks only in specific regions: + +```bash +prowler alibabacloud --regions cn-hangzhou cn-shanghai +``` + +### Run Specific Checks + +To run specific checks: + +```bash +prowler alibabacloud --checks ram_no_root_access_key ram_user_mfa_enabled_console_access +``` + +### Run Compliance Framework + +To run a specific compliance framework: + +```bash +prowler alibabacloud --compliance cis_2.0_alibabacloud +``` diff --git a/poetry.lock b/poetry.lock index ffcdeda73e..6829248202 100644 --- a/poetry.lock +++ b/poetry.lock @@ -12,6 +12,18 @@ files = [ {file = "about_time-4.2.1-py3-none-any.whl", hash = "sha256:8bbf4c75fe13cbd3d72f49a03b02c5c7dca32169b6d49117c257e7eb3eaee341"}, ] +[[package]] +name = "aiofiles" +version = "24.1.0" +description = "File support for asyncio." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5"}, + {file = "aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c"}, +] + [[package]] name = "aiohappyeyeballs" version = "2.6.1" @@ -149,6 +161,480 @@ files = [ frozenlist = ">=1.1.0" typing-extensions = {version = ">=4.2", markers = "python_version < \"3.13\""} +[[package]] +name = "alibabacloud-actiontrail20200706" +version = "2.4.1" +description = "Alibaba Cloud ActionTrail (20200706) SDK Library for Python" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "alibabacloud_actiontrail20200706-2.4.1-py3-none-any.whl", hash = "sha256:5dee0009db9b7cba182fbac742820f6a949287a8faafb843b5107f7dc89136da"}, + {file = "alibabacloud_actiontrail20200706-2.4.1.tar.gz", hash = "sha256:b65c6b37a96443fbe625dd5a4dd1be52a7476006a411db75206908b11588ffa8"}, +] + +[package.dependencies] +alibabacloud-endpoint-util = ">=0.0.4,<1.0.0" +alibabacloud-openapi-util = ">=0.2.2,<1.0.0" +alibabacloud-tea-openapi = ">=0.3.16,<1.0.0" +alibabacloud-tea-util = ">=0.3.13,<1.0.0" + +[[package]] +name = "alibabacloud-credentials" +version = "1.0.3" +description = "The alibabacloud credentials module of alibabaCloud Python SDK." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "alibabacloud-credentials-1.0.3.tar.gz", hash = "sha256:9d8707e96afc6f348e23f5677ed15a21c2dfce7cfe6669776548ee4c80e1dfaf"}, + {file = "alibabacloud_credentials-1.0.3-py3-none-any.whl", hash = "sha256:30c8302f204b663c655d97e1c283ee9f9f84a6257d7901b931477d6cf34445a8"}, +] + +[package.dependencies] +aiofiles = ">=22.1.0,<25.0.0" +alibabacloud-credentials-api = ">=1.0.0,<2.0.0" +alibabacloud-tea = ">=0.4.0" +APScheduler = ">=3.10.0,<4.0.0" + +[[package]] +name = "alibabacloud-credentials-api" +version = "1.0.0" +description = "Alibaba Cloud Gateway SPI SDK Library for Python" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "alibabacloud-credentials-api-1.0.0.tar.gz", hash = "sha256:8c340038d904f0218d7214a8f4088c31912bfcf279af2cbc7d9be4897a97dd2f"}, +] + +[[package]] +name = "alibabacloud-cs20151215" +version = "6.1.0" +description = "Alibaba Cloud CS (20151215) SDK Library for Python" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "alibabacloud_cs20151215-6.1.0-py3-none-any.whl", hash = "sha256:75e90b1bb9acca2236244bb0e44234ca4805d456ea4303ba4225ac15152a458e"}, + {file = "alibabacloud_cs20151215-6.1.0.tar.gz", hash = "sha256:5b3d99306701bf499ddd57cd9f2905b7721cb1bb4bb38ffe4d051f7b4e80e355"}, +] + +[package.dependencies] +alibabacloud-endpoint-util = ">=0.0.4,<1.0.0" +alibabacloud-openapi-util = ">=0.2.2,<1.0.0" +alibabacloud-tea-openapi = ">=0.3.16,<1.0.0" +alibabacloud-tea-util = ">=0.3.13,<1.0.0" + +[[package]] +name = "alibabacloud-darabonba-array" +version = "0.1.0" +description = "Alibaba Cloud Darabonba Array SDK Library for Python" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "alibabacloud_darabonba_array-0.1.0.tar.gz", hash = "sha256:7f9a7c632518ff4f0cebb0d4e825a48c12e7cf0b9016ea25054dd73732e155aa"}, +] + +[[package]] +name = "alibabacloud-darabonba-encode-util" +version = "0.0.2" +description = "Darabonba Util Library for Alibaba Cloud Python SDK" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "alibabacloud_darabonba_encode_util-0.0.2.tar.gz", hash = "sha256:f1c484f276d60450fa49b4b2987194e741fcb2f7faae7f287c0ae65abc85fd4d"}, +] + +[[package]] +name = "alibabacloud-darabonba-map" +version = "0.0.1" +description = "Alibaba Cloud Darabonba Map SDK Library for Python" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "alibabacloud_darabonba_map-0.0.1.tar.gz", hash = "sha256:adb17384658a1a8f72418f1838d4b6a5fd2566bfd392a3ef06d9dbb0a595a23f"}, +] + +[[package]] +name = "alibabacloud-darabonba-signature-util" +version = "0.0.4" +description = "Darabonba Util Library for Alibaba Cloud Python SDK" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "alibabacloud_darabonba_signature_util-0.0.4.tar.gz", hash = "sha256:71d79b2ae65957bcfbf699ced894fda782b32f9635f1616635533e5a90d5feb0"}, +] + +[package.dependencies] +cryptography = ">=3.0.0" + +[[package]] +name = "alibabacloud-darabonba-string" +version = "0.0.4" +description = "Alibaba Cloud Darabonba String Library for Python" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "alibabacloud-darabonba-string-0.0.4.tar.gz", hash = "sha256:ec6614c0448dadcbc5e466485838a1f8cfdd911135bea739e20b14511270c6f7"}, +] + +[[package]] +name = "alibabacloud-darabonba-time" +version = "0.0.1" +description = "Alibaba Cloud Darabonba Time SDK Library for Python" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "alibabacloud_darabonba_time-0.0.1.tar.gz", hash = "sha256:0ad9c7b0696570d1a3f40106cc7777f755fd92baa0d1dcab5b7df78dde5b922d"}, +] + +[[package]] +name = "alibabacloud-ecs20140526" +version = "7.2.5" +description = "Alibaba Cloud Elastic Compute Service (20140526) SDK Library for Python" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "alibabacloud_ecs20140526-7.2.5-py3-none-any.whl", hash = "sha256:10bda5e185f6ba899e7d51477373595c629d66db7530a8a37433fb4e9034a96f"}, + {file = "alibabacloud_ecs20140526-7.2.5.tar.gz", hash = "sha256:2abbe630ce42d69061821f38950b938c5982cc31902ccd7132d05be328765a55"}, +] + +[package.dependencies] +alibabacloud-endpoint-util = ">=0.0.4,<1.0.0" +alibabacloud-openapi-util = ">=0.2.2,<1.0.0" +alibabacloud-tea-openapi = ">=0.3.16,<1.0.0" +alibabacloud-tea-util = ">=0.3.13,<1.0.0" + +[[package]] +name = "alibabacloud-endpoint-util" +version = "0.0.4" +description = "The endpoint-util module of alibabaCloud Python SDK." +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "alibabacloud_endpoint_util-0.0.4.tar.gz", hash = "sha256:a593eb8ddd8168d5dc2216cd33111b144f9189fcd6e9ca20e48f358a739bbf90"}, +] + +[[package]] +name = "alibabacloud-gateway-oss" +version = "0.0.17" +description = "Alibaba Cloud OSS SDK Library for Python" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "alibabacloud_gateway_oss-0.0.17.tar.gz", hash = "sha256:8c4b66c8c7dd285fc210ee232ab3f062b5573258752804d19382000746531e29"}, +] + +[package.dependencies] +alibabacloud_credentials = ">=0.3.5" +alibabacloud_darabonba_array = ">=0.1.0,<1.0.0" +alibabacloud_darabonba_encode_util = ">=0.0.2,<1.0.0" +alibabacloud_darabonba_map = ">=0.0.1,<1.0.0" +alibabacloud_darabonba_signature_util = ">=0.0.4,<1.0.0" +alibabacloud_darabonba_string = ">=0.0.4,<1.0.0" +alibabacloud_darabonba_time = ">=0.0.1,<1.0.0" +alibabacloud_gateway_oss_util = ">=0.0.3,<1.0.0" +alibabacloud_gateway_spi = ">=0.0.1,<1.0.0" +alibabacloud_openapi_util = ">=0.2.1,<1.0.0" +alibabacloud_oss_util = ">=0.0.5,<1.0.0" +alibabacloud_tea_util = ">=0.3.11,<1.0.0" +alibabacloud_tea_xml = ">=0.0.2,<1.0.0" + +[[package]] +name = "alibabacloud-gateway-oss-util" +version = "0.0.3" +description = "Alibaba Cloud OSS Util Library for Python" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "alibabacloud_gateway_oss_util-0.0.3.tar.gz", hash = "sha256:5eb7fa450dc7350d5c71577974b9d7f489479e5c5ec7efc1c5376385e8c1c0a5"}, +] + +[[package]] +name = "alibabacloud-gateway-sls" +version = "0.4.0" +description = "Alibaba Cloud SLS Gateway Library for Python" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "alibabacloud_gateway_sls-0.4.0-py3-none-any.whl", hash = "sha256:a0299a83a5528025983b42b7533a28028461bced5e180a66f97999e0134760a6"}, + {file = "alibabacloud_gateway_sls-0.4.0.tar.gz", hash = "sha256:9d2aceb377c9b3ed0558149fda16fe39fa114cc0a22e22a88dc76efdda34633b"}, +] + +[package.dependencies] +alibabacloud-credentials = ">=1.0.2,<2.0.0" +alibabacloud-darabonba-array = ">=0.1.0,<1.0.0" +alibabacloud-darabonba-encode-util = ">=0.0.2,<1.0.0" +alibabacloud-darabonba-map = ">=0.0.1,<1.0.0" +alibabacloud-darabonba-signature-util = ">=0.0.4,<1.0.0" +alibabacloud-darabonba-string = ">=0.0.4,<1.0.0" +alibabacloud-gateway-sls-util = ">=0.4.0,<1.0.0" +alibabacloud-gateway-spi = ">=0.0.2,<1.0.0" +alibabacloud-openapi-util = ">=0.2.2,<1.0.0" +alibabacloud-tea-util = ">=0.3.13,<1.0.0" + +[[package]] +name = "alibabacloud-gateway-sls-util" +version = "0.4.0" +description = "Alibaba Cloud SLS Util Library for Python" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "alibabacloud_gateway_sls_util-0.4.0-py3-none-any.whl", hash = "sha256:c91ab7fe55af526a01d25b0d431088c4d241b160db055da3d8cb7330bd74595a"}, + {file = "alibabacloud_gateway_sls_util-0.4.0.tar.gz", hash = "sha256:f8b683a36a2ae3fe9a8225d3d97773ea769bdf9cdf4f4d033eab2eb6062ddd1f"}, +] + +[package.dependencies] +aliyun-log-fastpb = ">=0.2.0" +lz4 = ">=4.3.2" +zstd = ">=1.5.5.1" + +[[package]] +name = "alibabacloud-gateway-spi" +version = "0.0.3" +description = "Alibaba Cloud Gateway SPI SDK Library for Python" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "alibabacloud_gateway_spi-0.0.3.tar.gz", hash = "sha256:10d1c53a3fc5f87915fbd6b4985b98338a776e9b44a0263f56643c5048223b8b"}, +] + +[package.dependencies] +alibabacloud_credentials = ">=0.3.4" + +[[package]] +name = "alibabacloud-openapi-util" +version = "0.2.2" +description = "Aliyun Tea OpenApi Library for Python" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "alibabacloud_openapi_util-0.2.2.tar.gz", hash = "sha256:ebbc3906f554cb4bf8f513e43e8a33e8b6a3d4a0ef13617a0e14c3dda8ef52a8"}, +] + +[package.dependencies] +alibabacloud_tea_util = ">=0.0.2" +cryptography = ">=3.0.0" + +[[package]] +name = "alibabacloud-oss-util" +version = "0.0.6" +description = "The oss util module of alibabaCloud Python SDK." +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "alibabacloud_oss_util-0.0.6.tar.gz", hash = "sha256:d3ecec36632434bd509a113e8cf327dc23e830ac8d9dd6949926f4e334c8b5d6"}, +] + +[package.dependencies] +alibabacloud-tea = "*" + +[[package]] +name = "alibabacloud-oss20190517" +version = "1.0.6" +description = "Alibaba Cloud Object Storage Service (20190517) SDK Library for Python" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "alibabacloud_oss20190517-1.0.6-py3-none-any.whl", hash = "sha256:365fda353de6658a1a289f4d70dcd0394e2a8e2921b6b5834ba6d9772121d2f6"}, + {file = "alibabacloud_oss20190517-1.0.6.tar.gz", hash = "sha256:7cd0fb16af613ceb38d2e0e529aa1f58038c7cf59eb67c8c8775ae44ea717852"}, +] + +[package.dependencies] +alibabacloud-gateway-oss = ">=0.0.9,<1.0.0" +alibabacloud-gateway-spi = ">=0.0.1,<1.0.0" +alibabacloud-openapi-util = ">=0.2.1,<1.0.0" +alibabacloud-tea-openapi = ">=0.3.6,<1.0.0" +alibabacloud-tea-util = ">=0.3.11,<1.0.0" + +[[package]] +name = "alibabacloud-ram20150501" +version = "1.2.0" +description = "Alibaba Cloud Resource Access Management (20150501) SDK Library for Python" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "alibabacloud_ram20150501-1.2.0-py3-none-any.whl", hash = "sha256:03a0f2a0259848787c1f74e802b486184a88e04183486bd9398766971e5eb00a"}, + {file = "alibabacloud_ram20150501-1.2.0.tar.gz", hash = "sha256:6253513c8880769f4fd5b36fedddb362a9ca628ad9ae9c05c0eeacf5fbc95b42"}, +] + +[package.dependencies] +alibabacloud-endpoint-util = ">=0.0.4,<1.0.0" +alibabacloud-openapi-util = ">=0.2.2,<1.0.0" +alibabacloud-tea-openapi = ">=0.3.15,<1.0.0" +alibabacloud-tea-util = ">=0.3.13,<1.0.0" + +[[package]] +name = "alibabacloud-rds20140815" +version = "12.0.0" +description = "Alibaba Cloud rds (20140815) SDK Library for Python" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "alibabacloud_rds20140815-12.0.0-py3-none-any.whl", hash = "sha256:0bd7e2018a428d86b1b0681087336e74665b48fc3eb0a13c4f4377ed5eab2b08"}, + {file = "alibabacloud_rds20140815-12.0.0.tar.gz", hash = "sha256:e7421d94f18a914c0a06b0e7fad0daff557713f1c97d415d463a78c1270e9b98"}, +] + +[package.dependencies] +alibabacloud-endpoint-util = ">=0.0.4,<1.0.0" +alibabacloud-openapi-util = ">=0.2.2,<1.0.0" +alibabacloud-tea-openapi = ">=0.3.15,<1.0.0" +alibabacloud-tea-util = ">=0.3.13,<1.0.0" + +[[package]] +name = "alibabacloud-sas20181203" +version = "6.1.0" +description = "Alibaba Cloud Threat Detection (20181203) SDK Library for Python" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "alibabacloud_sas20181203-6.1.0-py3-none-any.whl", hash = "sha256:1ad735332c50c7961be036b17420d56b5ec3b5557e3aea1daa19491e8b75da20"}, + {file = "alibabacloud_sas20181203-6.1.0.tar.gz", hash = "sha256:e49ffd53e630274a8bf5a8299ca753023ad118510c80f6d9c6fb018b7479bf37"}, +] + +[package.dependencies] +alibabacloud-endpoint-util = ">=0.0.4,<1.0.0" +alibabacloud-openapi-util = ">=0.2.2,<1.0.0" +alibabacloud-tea-openapi = ">=0.3.16,<1.0.0" +alibabacloud-tea-util = ">=0.3.13,<1.0.0" + +[[package]] +name = "alibabacloud-sls20201230" +version = "5.9.0" +description = "Alibaba Cloud Log Service (20201230) SDK Library for Python" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "alibabacloud_sls20201230-5.9.0-py3-none-any.whl", hash = "sha256:c4ae14096817a9686af5a0ae2389f1f6a8781e60b9edb8643445250cf15c26f1"}, + {file = "alibabacloud_sls20201230-5.9.0.tar.gz", hash = "sha256:bea830b64fbc7ed1719ba386ceeefb120f08d705f03eb0e02409dc6f12a291da"}, +] + +[package.dependencies] +alibabacloud-gateway-sls = ">=0.3.0,<1.0.0" +alibabacloud-openapi-util = ">=0.2.2,<1.0.0" +alibabacloud-tea-openapi = ">=0.3.16,<1.0.0" +alibabacloud-tea-util = ">=0.3.13,<1.0.0" + +[[package]] +name = "alibabacloud-sts20150401" +version = "1.1.6" +description = "Alibaba Cloud Sts (20150401) SDK Library for Python" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "alibabacloud_sts20150401-1.1.6-py3-none-any.whl", hash = "sha256:627f5ca1f86e19b0bf8ce0e99071a36fb65579fad9256fbee38fdc8d500598e9"}, + {file = "alibabacloud_sts20150401-1.1.6.tar.gz", hash = "sha256:c2529b41e0e4531e21cb393e4df346e19fd6d54cc6337d1138dbcd2191438d4c"}, +] + +[package.dependencies] +alibabacloud-endpoint-util = ">=0.0.4,<1.0.0" +alibabacloud-openapi-util = ">=0.2.2,<1.0.0" +alibabacloud-tea-openapi = ">=0.3.15,<1.0.0" +alibabacloud-tea-util = ">=0.3.13,<1.0.0" + +[[package]] +name = "alibabacloud-tea" +version = "0.4.3" +description = "The tea module of alibabaCloud Python SDK." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "alibabacloud-tea-0.4.3.tar.gz", hash = "sha256:ec8053d0aa8d43ebe1deb632d5c5404339b39ec9a18a0707d57765838418504a"}, +] + +[package.dependencies] +aiohttp = ">=3.7.0,<4.0.0" +requests = ">=2.21.0,<3.0.0" + +[[package]] +name = "alibabacloud-tea-openapi" +version = "0.4.1" +description = "Alibaba Cloud openapi SDK Library for Python" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "alibabacloud_tea_openapi-0.4.1-py3-none-any.whl", hash = "sha256:e46bfa3ca34086d2c357d217a0b7284ecbd4b3bab5c88e075e73aec637b0e4a0"}, + {file = "alibabacloud_tea_openapi-0.4.1.tar.gz", hash = "sha256:2384b090870fdb089c3c40f3fb8cf0145b8c7d6c14abbac521f86a01abb5edaf"}, +] + +[package.dependencies] +alibabacloud-credentials = ">=1.0.2,<2.0.0" +alibabacloud-gateway-spi = ">=0.0.2,<1.0.0" +alibabacloud-tea-util = ">=0.3.13,<1.0.0" +cryptography = ">=3.0.0,<45.0.0" +darabonba-core = ">=1.0.3,<2.0.0" + +[[package]] +name = "alibabacloud-tea-util" +version = "0.3.14" +description = "The tea-util module of alibabaCloud Python SDK." +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "alibabacloud_tea_util-0.3.14-py3-none-any.whl", hash = "sha256:10d3e5c340d8f7ec69dd27345eb2fc5a1dab07875742525edf07bbe86db93bfe"}, + {file = "alibabacloud_tea_util-0.3.14.tar.gz", hash = "sha256:708e7c9f64641a3c9e0e566365d2f23675f8d7c2a3e2971d9402ceede0408cdb"}, +] + +[package.dependencies] +alibabacloud-tea = ">=0.3.3" + +[[package]] +name = "alibabacloud-tea-xml" +version = "0.0.3" +description = "The tea-xml module of alibabaCloud Python SDK." +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "alibabacloud_tea_xml-0.0.3.tar.gz", hash = "sha256:979cb51fadf43de77f41c69fc69c12529728919f849723eb0cd24eb7b048a90c"}, +] + +[package.dependencies] +alibabacloud-tea = ">=0.4.0" + +[[package]] +name = "alibabacloud-vpc20160428" +version = "6.13.0" +description = "Alibaba Cloud Virtual Private Cloud (20160428) SDK Library for Python" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "alibabacloud_vpc20160428-6.13.0-py3-none-any.whl", hash = "sha256:933cf1e74322a20a2df27ca6323760d857744a4246eeadc9fb3eae01322fb1c6"}, + {file = "alibabacloud_vpc20160428-6.13.0.tar.gz", hash = "sha256:daf00679a83d422799f9fcf263739fe1f360641675843cbfbe623833fc8b1681"}, +] + +[package.dependencies] +alibabacloud-endpoint-util = ">=0.0.4,<1.0.0" +alibabacloud-openapi-util = ">=0.2.2,<1.0.0" +alibabacloud-tea-openapi = ">=0.3.16,<1.0.0" +alibabacloud-tea-util = ">=0.3.13,<1.0.0" + [[package]] name = "alive-progress" version = "3.3.0" @@ -165,6 +651,32 @@ files = [ about-time = "4.2.1" graphemeu = "0.7.2" +[[package]] +name = "aliyun-log-fastpb" +version = "0.2.0" +description = "Fast protobuf serialization for Aliyun Log using PyO3 and quick-protobuf" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "aliyun_log_fastpb-0.2.0-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:51633d92d2b349aed4843c0b503454fb4f7d73eeaaa54f82aa5a36c10c064ef5"}, + {file = "aliyun_log_fastpb-0.2.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:d2984aafc61ccbbf1db2589ce90b6d5a26e72dba137fb1fdf7f61ce3faa967c0"}, + {file = "aliyun_log_fastpb-0.2.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:181fc61ac9934f58b0880fa5617a4a4dc709dba09f8be95b5a71e828f2e48053"}, + {file = "aliyun_log_fastpb-0.2.0-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12b8bfddf0bc5450f16f1954c6387a73da124fae10d1205a17a0117e66bb56db"}, + {file = "aliyun_log_fastpb-0.2.0-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8fbc83cbaa51d332e5e68871c1200014f1f3de54a8cba4fb55a634ee145cd4e4"}, + {file = "aliyun_log_fastpb-0.2.0-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42a86a6e11dd227d595fa23f69d30588446af19d045d1003bd1b66b5c9a55485"}, + {file = "aliyun_log_fastpb-0.2.0-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd92c0b84ba300c1d1c227204c5f2fff243cea80bc3f9399293385e87c82ee3e"}, + {file = "aliyun_log_fastpb-0.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7c07a6d81a3eab6666949240da305236ed2350c305154d7e39fcc121fc52291"}, + {file = "aliyun_log_fastpb-0.2.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2cff4fbdd0edff94adcee1dcabf16daacb5d336a12fc897887aa6e4f0ad25152"}, + {file = "aliyun_log_fastpb-0.2.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5a451809e2a062accbb8dae8750e507e58806e4a8da48d69215cdeef428e9d63"}, + {file = "aliyun_log_fastpb-0.2.0-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:61f09df30232f1f5628d13310cf0e175171399ea1c75a8470e9f9d97b045bfb5"}, + {file = "aliyun_log_fastpb-0.2.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:a5fbf0d41d8c0c964a3dc8dd0ee2e732f876b803e0ed3432550ef3b84dde84f1"}, + {file = "aliyun_log_fastpb-0.2.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ae2f84ed0777e00045791044a56413f370afbd5b061505f5ded540c04b19c58e"}, + {file = "aliyun_log_fastpb-0.2.0-cp37-abi3-win32.whl", hash = "sha256:967f9656c805602fd9be07d8c2756ad89204c852c99689c3c71aa035416ef42a"}, + {file = "aliyun_log_fastpb-0.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:bbdcf7b85f0f3437c2a8e8a1db0ef5584d21468b7c7a358269a4c651c84f4a54"}, + {file = "aliyun_log_fastpb-0.2.0.tar.gz", hash = "sha256:91c714e76fb941c9a0db6b1aa1f4c56cb1626254ff5444c1179860f5e5b63d93"}, +] + [[package]] name = "annotated-types" version = "0.7.0" @@ -212,6 +724,34 @@ doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""] trio = ["trio (>=0.26.1)"] +[[package]] +name = "apscheduler" +version = "3.11.1" +description = "In-process task scheduler with Cron-like capabilities" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "apscheduler-3.11.1-py3-none-any.whl", hash = "sha256:6162cb5683cb09923654fa9bdd3130c4be4bfda6ad8990971c9597ecd52965d2"}, + {file = "apscheduler-3.11.1.tar.gz", hash = "sha256:0db77af6400c84d1747fe98a04b8b58f0080c77d11d338c4f507a9752880f221"}, +] + +[package.dependencies] +tzlocal = ">=3.0" + +[package.extras] +doc = ["packaging", "sphinx", "sphinx-rtd-theme (>=1.3.0)"] +etcd = ["etcd3", "protobuf (<=3.21.0)"] +gevent = ["gevent"] +mongodb = ["pymongo (>=3.0)"] +redis = ["redis (>=3.0)"] +rethinkdb = ["rethinkdb (>=2.4.0)"] +sqlalchemy = ["sqlalchemy (>=1.4)"] +test = ["APScheduler[etcd,mongodb,redis,rethinkdb,sqlalchemy,tornado,zookeeper]", "PySide6 ; platform_python_implementation == \"CPython\" and python_version < \"3.14\"", "anyio (>=4.5.2)", "gevent ; python_version < \"3.14\"", "pytest", "pytz", "twisted ; python_version < \"3.14\""] +tornado = ["tornado (>=4.3)"] +twisted = ["twisted"] +zookeeper = ["kazoo"] + [[package]] name = "astroid" version = "3.3.11" @@ -1444,6 +1984,23 @@ ssh = ["bcrypt (>=3.1.5)"] test = ["certifi (>=2024)", "cryptography-vectors (==44.0.1)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] test-randomorder = ["pytest-randomly"] +[[package]] +name = "darabonba-core" +version = "1.0.4" +description = "The darabonba module of alibabaCloud Python SDK." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "darabonba_core-1.0.4-py3-none-any.whl", hash = "sha256:4c3bc1d76d5af1087297b6afde8e960ea2f54f93e725e2df8453f0b4bb27dd24"}, + {file = "darabonba_core-1.0.4.tar.gz", hash = "sha256:6ede4e9bfd458148bab19ab2331716ae9b5c226ba5f6d221de6f88ee65704137"}, +] + +[package.dependencies] +aiohttp = ">=3.7.0,<4.0.0" +alibabacloud-tea = "*" +requests = ">=2.21.0,<3.0.0" + [[package]] name = "dash" version = "3.1.1" @@ -2490,6 +3047,78 @@ files = [ {file = "lazy_object_proxy-1.11.0.tar.gz", hash = "sha256:18874411864c9fbbbaa47f9fc1dd7aea754c86cfde21278ef427639d1dd78e9c"}, ] +[[package]] +name = "lz4" +version = "4.4.5" +description = "LZ4 Bindings for Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "lz4-4.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d221fa421b389ab2345640a508db57da36947a437dfe31aeddb8d5c7b646c22d"}, + {file = "lz4-4.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7dc1e1e2dbd872f8fae529acd5e4839efd0b141eaa8ae7ce835a9fe80fbad89f"}, + {file = "lz4-4.4.5-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e928ec2d84dc8d13285b4a9288fd6246c5cde4f5f935b479f50d986911f085e3"}, + {file = "lz4-4.4.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daffa4807ef54b927451208f5f85750c545a4abbff03d740835fc444cd97f758"}, + {file = "lz4-4.4.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a2b7504d2dffed3fd19d4085fe1cc30cf221263fd01030819bdd8d2bb101cf1"}, + {file = "lz4-4.4.5-cp310-cp310-win32.whl", hash = "sha256:0846e6e78f374156ccf21c631de80967e03cc3c01c373c665789dc0c5431e7fc"}, + {file = "lz4-4.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:7c4e7c44b6a31de77d4dc9772b7d2561937c9588a734681f70ec547cfbc51ecd"}, + {file = "lz4-4.4.5-cp310-cp310-win_arm64.whl", hash = "sha256:15551280f5656d2206b9b43262799c89b25a25460416ec554075a8dc568e4397"}, + {file = "lz4-4.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d6da84a26b3aa5da13a62e4b89ab36a396e9327de8cd48b436a3467077f8ccd4"}, + {file = "lz4-4.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:61d0ee03e6c616f4a8b69987d03d514e8896c8b1b7cc7598ad029e5c6aedfd43"}, + {file = "lz4-4.4.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:33dd86cea8375d8e5dd001e41f321d0a4b1eb7985f39be1b6a4f466cd480b8a7"}, + {file = "lz4-4.4.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:609a69c68e7cfcfa9d894dc06be13f2e00761485b62df4e2472f1b66f7b405fb"}, + {file = "lz4-4.4.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:75419bb1a559af00250b8f1360d508444e80ed4b26d9d40ec5b09fe7875cb989"}, + {file = "lz4-4.4.5-cp311-cp311-win32.whl", hash = "sha256:12233624f1bc2cebc414f9efb3113a03e89acce3ab6f72035577bc61b270d24d"}, + {file = "lz4-4.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:8a842ead8ca7c0ee2f396ca5d878c4c40439a527ebad2b996b0444f0074ed004"}, + {file = "lz4-4.4.5-cp311-cp311-win_arm64.whl", hash = "sha256:83bc23ef65b6ae44f3287c38cbf82c269e2e96a26e560aa551735883388dcc4b"}, + {file = "lz4-4.4.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:df5aa4cead2044bab83e0ebae56e0944cc7fcc1505c7787e9e1057d6d549897e"}, + {file = "lz4-4.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6d0bf51e7745484d2092b3a51ae6eb58c3bd3ce0300cf2b2c14f76c536d5697a"}, + {file = "lz4-4.4.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7b62f94b523c251cf32aa4ab555f14d39bd1a9df385b72443fd76d7c7fb051f5"}, + {file = "lz4-4.4.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c3ea562c3af274264444819ae9b14dbbf1ab070aff214a05e97db6896c7597e"}, + {file = "lz4-4.4.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24092635f47538b392c4eaeff14c7270d2c8e806bf4be2a6446a378591c5e69e"}, + {file = "lz4-4.4.5-cp312-cp312-win32.whl", hash = "sha256:214e37cfe270948ea7eb777229e211c601a3e0875541c1035ab408fbceaddf50"}, + {file = "lz4-4.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:713a777de88a73425cf08eb11f742cd2c98628e79a8673d6a52e3c5f0c116f33"}, + {file = "lz4-4.4.5-cp312-cp312-win_arm64.whl", hash = "sha256:a88cbb729cc333334ccfb52f070463c21560fca63afcf636a9f160a55fac3301"}, + {file = "lz4-4.4.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6bb05416444fafea170b07181bc70640975ecc2a8c92b3b658c554119519716c"}, + {file = "lz4-4.4.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b424df1076e40d4e884cfcc4c77d815368b7fb9ebcd7e634f937725cd9a8a72a"}, + {file = "lz4-4.4.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:216ca0c6c90719731c64f41cfbd6f27a736d7e50a10b70fad2a9c9b262ec923d"}, + {file = "lz4-4.4.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:533298d208b58b651662dd972f52d807d48915176e5b032fb4f8c3b6f5fe535c"}, + {file = "lz4-4.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:451039b609b9a88a934800b5fc6ee401c89ad9c175abf2f4d9f8b2e4ef1afc64"}, + {file = "lz4-4.4.5-cp313-cp313-win32.whl", hash = "sha256:a5f197ffa6fc0e93207b0af71b302e0a2f6f29982e5de0fbda61606dd3a55832"}, + {file = "lz4-4.4.5-cp313-cp313-win_amd64.whl", hash = "sha256:da68497f78953017deb20edff0dba95641cc86e7423dfadf7c0264e1ac60dc22"}, + {file = "lz4-4.4.5-cp313-cp313-win_arm64.whl", hash = "sha256:c1cfa663468a189dab510ab231aad030970593f997746d7a324d40104db0d0a9"}, + {file = "lz4-4.4.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:67531da3b62f49c939e09d56492baf397175ff39926d0bd5bd2d191ac2bff95f"}, + {file = "lz4-4.4.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a1acbbba9edbcbb982bc2cac5e7108f0f553aebac1040fbec67a011a45afa1ba"}, + {file = "lz4-4.4.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a482eecc0b7829c89b498fda883dbd50e98153a116de612ee7c111c8bcf82d1d"}, + {file = "lz4-4.4.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e099ddfaa88f59dd8d36c8a3c66bd982b4984edf127eb18e30bb49bdba68ce67"}, + {file = "lz4-4.4.5-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2af2897333b421360fdcce895c6f6281dc3fab018d19d341cf64d043fc8d90d"}, + {file = "lz4-4.4.5-cp313-cp313t-win32.whl", hash = "sha256:66c5de72bf4988e1b284ebdd6524c4bead2c507a2d7f172201572bac6f593901"}, + {file = "lz4-4.4.5-cp313-cp313t-win_amd64.whl", hash = "sha256:cdd4bdcbaf35056086d910d219106f6a04e1ab0daa40ec0eeef1626c27d0fddb"}, + {file = "lz4-4.4.5-cp313-cp313t-win_arm64.whl", hash = "sha256:28ccaeb7c5222454cd5f60fcd152564205bcb801bd80e125949d2dfbadc76bbd"}, + {file = "lz4-4.4.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c216b6d5275fc060c6280936bb3bb0e0be6126afb08abccde27eed23dead135f"}, + {file = "lz4-4.4.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c8e71b14938082ebaf78144f3b3917ac715f72d14c076f384a4c062df96f9df6"}, + {file = "lz4-4.4.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9b5e6abca8df9f9bdc5c3085f33ff32cdc86ed04c65e0355506d46a5ac19b6e9"}, + {file = "lz4-4.4.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b84a42da86e8ad8537aabef062e7f661f4a877d1c74d65606c49d835d36d668"}, + {file = "lz4-4.4.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bba042ec5a61fa77c7e380351a61cb768277801240249841defd2ff0a10742f"}, + {file = "lz4-4.4.5-cp314-cp314-win32.whl", hash = "sha256:bd85d118316b53ed73956435bee1997bd06cc66dd2fa74073e3b1322bd520a67"}, + {file = "lz4-4.4.5-cp314-cp314-win_amd64.whl", hash = "sha256:92159782a4502858a21e0079d77cdcaade23e8a5d252ddf46b0652604300d7be"}, + {file = "lz4-4.4.5-cp314-cp314-win_arm64.whl", hash = "sha256:d994b87abaa7a88ceb7a37c90f547b8284ff9da694e6afcfaa8568d739faf3f7"}, + {file = "lz4-4.4.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f6538aaaedd091d6e5abdaa19b99e6e82697d67518f114721b5248709b639fad"}, + {file = "lz4-4.4.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:13254bd78fef50105872989a2dc3418ff09aefc7d0765528adc21646a7288294"}, + {file = "lz4-4.4.5-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e64e61f29cf95afb43549063d8433b46352baf0c8a70aa45e2585618fcf59d86"}, + {file = "lz4-4.4.5-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff1b50aeeec64df5603f17984e4b5be6166058dcf8f1e26a3da40d7a0f6ab547"}, + {file = "lz4-4.4.5-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1dd4d91d25937c2441b9fc0f4af01704a2d09f30a38c5798bc1d1b5a15ec9581"}, + {file = "lz4-4.4.5-cp39-cp39-win32.whl", hash = "sha256:d64141085864918392c3159cdad15b102a620a67975c786777874e1e90ef15ce"}, + {file = "lz4-4.4.5-cp39-cp39-win_amd64.whl", hash = "sha256:f32b9e65d70f3684532358255dc053f143835c5f5991e28a5ac4c93ce94b9ea7"}, + {file = "lz4-4.4.5-cp39-cp39-win_arm64.whl", hash = "sha256:f9b8bde9909a010c75b3aea58ec3910393b758f3c219beed67063693df854db0"}, + {file = "lz4-4.4.5.tar.gz", hash = "sha256:5f0b9e53c1e82e88c10d7c180069363980136b9d7a8306c4dca4f760d60c39f0"}, +] + +[package.extras] +docs = ["sphinx (>=1.6.0)", "sphinx_bootstrap_theme"] +flake8 = ["flake8"] +tests = ["psutil", "pytest (!=3.3.0)", "pytest-cov"] + [[package]] name = "markdown" version = "3.9" @@ -5685,7 +6314,143 @@ enabler = ["pytest-enabler (>=2.2)"] test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] +[[package]] +name = "zstd" +version = "1.5.7.2" +description = "ZSTD Bindings for Python" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "zstd-1.5.7.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e17104d0e88367a7571dde4286e233126c8551691ceff11f9ae2e3a3ac1bb483"}, + {file = "zstd-1.5.7.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:d6ee5dfada4c8fa32f43cc092fcf7d8482da6ad242c22fdf780f7eebd0febcc7"}, + {file = "zstd-1.5.7.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:ae1100776cb400100e2d2f427b50dc983c005c38cd59502eb56d2cfea3402ad5"}, + {file = "zstd-1.5.7.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:489a0ff15caf7640851e63f85b680c4279c99094cd500a29c7ed3ab82505fce0"}, + {file = "zstd-1.5.7.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:92590cf54318849d492445c885f1a42b9dbb47cdc070659c7cb61df6e8531047"}, + {file = "zstd-1.5.7.2-cp27-cp27mu-manylinux_2_4_i686.whl", hash = "sha256:2bc21650f7b9c058a3c4cb503e906fe9cce293941ec1b48bc5d005c3b4422b42"}, + {file = "zstd-1.5.7.2-cp27-cp27mu-manylinux_2_4_x86_64.whl", hash = "sha256:7b13e7eef9aa192804d38bf413924d347c6f6c6ac07f5a0c1ae4a6d7b3af70f0"}, + {file = "zstd-1.5.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d3f14c5c405ea353b68fe105236780494eb67c756ecd346fd295498f5eab6d24"}, + {file = "zstd-1.5.7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07d2061df22a3efc06453089e6e8b96e58f5bb7a0c4074dcfd0b0ce243ddde72"}, + {file = "zstd-1.5.7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:27e55aa2043ba7d8a08aba0978c652d4d5857338a8188aa84522569f3586c7bb"}, + {file = "zstd-1.5.7.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:8e97933addfd71ea9608306f18dc18e7d2a5e64212ba2bb9a4ccb6d714f9f280"}, + {file = "zstd-1.5.7.2-cp310-cp310-manylinux_2_4_i686.whl", hash = "sha256:27e2ed58b64001c9ef0a8e028625477f1a6ed4ca949412ff6548544945cc59c2"}, + {file = "zstd-1.5.7.2-cp310-cp310-manylinux_2_4_x86_64.whl", hash = "sha256:92f072819fc0c7e8445f51a232c9ad76642027c069d2f36470cdb5e663839cdb"}, + {file = "zstd-1.5.7.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:2a653cdd2c52d60c28e519d44bde8d759f2c1837f0ff8e8e1b0045ca62fcf70e"}, + {file = "zstd-1.5.7.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:047803d87d910f4905f48d99aeff1e0539ec2e4f4bf17d077701b5d0b2392a95"}, + {file = "zstd-1.5.7.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0d8c1dc947e5ccea3bd81043080213685faf1d43886c27c51851fabf325f05c0"}, + {file = "zstd-1.5.7.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8291d393321fac30604c6bbf40067103fee315aa476647a5eaecf877ee53496f"}, + {file = "zstd-1.5.7.2-cp310-cp310-win32.whl", hash = "sha256:6922ceac5f2d60bb57a7875168c8aa442477b83e8951f2206cf1e9be788b0a6e"}, + {file = "zstd-1.5.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:346d1e4774d89a77d67fc70d53964bfca57c0abecfd885a4e00f87fd7c71e074"}, + {file = "zstd-1.5.7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f799c1e9900ad77e7a3d994b9b5146d7cfd1cbd1b61c3db53a697bf21ffcc57b"}, + {file = "zstd-1.5.7.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ff4c667f29101566a7b71f06bbd677a63192818396003354131f586383db042"}, + {file = "zstd-1.5.7.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8526a32fa9f67b07fd09e62474e345f8ca1daf3e37a41137643d45bd1bc90773"}, + {file = "zstd-1.5.7.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:2cec2472760d48a7a3445beaba509d3f7850e200fed65db15a1a66e315baec6a"}, + {file = "zstd-1.5.7.2-cp311-cp311-manylinux_2_4_i686.whl", hash = "sha256:a200c479ee1bb661bc45518e016a1fdc215a1d8f7e4bf6c7de0af254976cfdf6"}, + {file = "zstd-1.5.7.2-cp311-cp311-manylinux_2_4_x86_64.whl", hash = "sha256:f5d159e57a13147aa8293c0f14803a75e9039fd8afdf6cf1c8c2289fb4d2333a"}, + {file = "zstd-1.5.7.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:7206934a2bd390080e972a1fed5a897e184dfd71dbb54e978dc11c6b295e1806"}, + {file = "zstd-1.5.7.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7e0027b20f296d1c9a8e85b8436834cf46560240a29d623aa8eaa8911832eb58"}, + {file = "zstd-1.5.7.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d6b17e5581dd1a13437079bd62838d2635db8eb8aca9c0e9251faa5d4d40a6d7"}, + {file = "zstd-1.5.7.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b13285c99cc710f60dd270785ec75233018870a1831f5655d862745470a0ca29"}, + {file = "zstd-1.5.7.2-cp311-cp311-win32.whl", hash = "sha256:cdb5ec80da299f63f8aeccec0bff3247e96252d4c8442876363ff1b438d8049b"}, + {file = "zstd-1.5.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:4f6861c8edceb25fda37cdaf422fc5f15dcc88ced37c6a5b3c9011eda51aa218"}, + {file = "zstd-1.5.7.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2ebe3e60dbace52525fa7aa604479e231dc3e4fcc76d0b4c54d8abce5e58734"}, + {file = "zstd-1.5.7.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ef201b6f7d3a6751d85cc52f9e6198d4d870e83d490172016b64a6dd654a9583"}, + {file = "zstd-1.5.7.2-cp312-cp312-manylinux_2_14_x86_64.whl", hash = "sha256:ac7bdfedda51b1fcdcf0ab69267d01256fc97ddf666ce894fde0fae9f3630eac"}, + {file = "zstd-1.5.7.2-cp312-cp312-manylinux_2_4_i686.whl", hash = "sha256:b835405cc4080b378e45029f2fe500e408d1eaedfba7dd7402aba27af16955f9"}, + {file = "zstd-1.5.7.2-cp312-cp312-win32.whl", hash = "sha256:e4cf97bb97ed6dbb62d139d68fd42fa1af51fd26fd178c501f7b62040e897c50"}, + {file = "zstd-1.5.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:55e2edc4560a5cf8ee9908595e90a15b1f47536ea9aad4b2889f0e6165890a38"}, + {file = "zstd-1.5.7.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6e684e27064b6550aa2e7dc85d171ea1b62cb5930a2c99b3df9b30bf620b5c06"}, + {file = "zstd-1.5.7.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fd6262788a98807d6b2befd065d127db177c1cd76bb8e536e0dded419eb7c7fb"}, + {file = "zstd-1.5.7.2-cp313-cp313-manylinux_2_14_x86_64.whl", hash = "sha256:53948be45f286a1b25c07a6aa2aca5c902208eb3df9fe36cf891efa0394c8b71"}, + {file = "zstd-1.5.7.2-cp313-cp313-win32.whl", hash = "sha256:edf816c218e5978033b7bb47dcb453dfb71038cb8a9bf4877f3f823e74d58174"}, + {file = "zstd-1.5.7.2-cp313-cp313-win_amd64.whl", hash = "sha256:eea9bddf06f3f5e1e450fd647665c86df048a45e8b956d53522387c1dff41b7a"}, + {file = "zstd-1.5.7.2-cp313-cp313t-manylinux_2_14_x86_64.whl", hash = "sha256:1d71f9f92b3abe18b06b5f0aefa5b9c42112beef3bff27e36028d147cb4426a6"}, + {file = "zstd-1.5.7.2-cp314-cp314-manylinux_2_14_x86_64.whl", hash = "sha256:a6105b8fa21dbc59e05b6113e8e5d5aaf56c5d2886aa5778d61030af3256bbb7"}, + {file = "zstd-1.5.7.2-cp314-cp314t-manylinux_2_14_x86_64.whl", hash = "sha256:d0b0ca097efb5f67157c61a744c926848dcccf6e913df2f814e719aa78197a4b"}, + {file = "zstd-1.5.7.2-cp34-cp34m-manylinux_2_4_i686.whl", hash = "sha256:a371274668182ae06be2e321089b207fa0a75a58ae2fd4dfb7eafded9e041b2f"}, + {file = "zstd-1.5.7.2-cp34-cp34m-manylinux_2_4_x86_64.whl", hash = "sha256:74c3f006c9a3a191ed454183f0fb78172444f5cb431be04d85044a27f1b58c7b"}, + {file = "zstd-1.5.7.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:f19a3e658d92b6b52020c4c6d4c159480bcd3b47658773ea0e8d343cee849f33"}, + {file = "zstd-1.5.7.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d9d1bcb6441841c599883139c1b0e47bddb262cce04b37dc2c817da5802c1158"}, + {file = "zstd-1.5.7.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:bb1cb423fc40468cc9b7ab51a5b33c618eefd2c910a5bffed6ed76fe1cbb20b0"}, + {file = "zstd-1.5.7.2-cp35-cp35m-manylinux_2_14_x86_64.whl", hash = "sha256:e2476ba12597e58c5fc7a3ae547ee1bef9dd6b9d5ea80cf8d4034930c5a336e0"}, + {file = "zstd-1.5.7.2-cp35-cp35m-manylinux_2_4_i686.whl", hash = "sha256:2bf6447373782a2a9df3015121715f6d0b80a49a884c2d7d4518c9571e9fca16"}, + {file = "zstd-1.5.7.2-cp35-cp35m-win32.whl", hash = "sha256:a59a136a9eaa1849d715c004e30344177e85ad6e7bc4a5d0b6ad2495c5402675"}, + {file = "zstd-1.5.7.2-cp35-cp35m-win_amd64.whl", hash = "sha256:114115af8c68772a3205414597f626b604c7879f6662a2a79c88312e0f50361f"}, + {file = "zstd-1.5.7.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f576ec00e99db124309dac1e1f34bc320eb69624189f5fdaf9ebe1dc81581a84"}, + {file = "zstd-1.5.7.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:f97d8593da0e23a47f148a1cb33300dccd513fb0df9f7911c274e228a8c1a300"}, + {file = "zstd-1.5.7.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:a130243e875de5aeda6099d12b11bc2fcf548dce618cf6b17f731336ba5338e4"}, + {file = "zstd-1.5.7.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:73cec37649fda383348dc8b3b5fba535f1dbb1bbaeb60fd36f4c145820208619"}, + {file = "zstd-1.5.7.2-cp36-cp36m-manylinux_2_14_x86_64.whl", hash = "sha256:883e7b77a3124011b8badd0c7c9402af3884700a3431d07877972e157d85afb8"}, + {file = "zstd-1.5.7.2-cp36-cp36m-manylinux_2_4_i686.whl", hash = "sha256:b5af6aa041b5515934afef2ef4af08566850875c3c890109088eedbe190eeefb"}, + {file = "zstd-1.5.7.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:53abf577aec7b30afa3c024143f4866676397c846b44f1b30d8097b5e4f5c7d7"}, + {file = "zstd-1.5.7.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:660945ba16c16957c94dafc40aff1db02a57af0489aa3a896866239d47bb44b0"}, + {file = "zstd-1.5.7.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:3e220d2d7005822bb72a52e76410ca4634f941d8062c08e8e3285733c63b1db7"}, + {file = "zstd-1.5.7.2-cp37-cp37m-manylinux_2_4_i686.whl", hash = "sha256:7e998f86a9d1e576c0158bf0b0a6a5c4685679d74ba0053a2e87f684f9bdc8eb"}, + {file = "zstd-1.5.7.2-cp37-cp37m-manylinux_2_4_x86_64.whl", hash = "sha256:70d0c4324549073e05aa72e9eb6a593f89cba59da804b946d325d68467b93ad5"}, + {file = "zstd-1.5.7.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:b9518caabf59405eddd667bbb161d9ae7f13dbf96967fd998d095589c8d41c86"}, + {file = "zstd-1.5.7.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:30d339d8e5c4b14c2015b50371fcdb8a93b451ca6d3ef813269ccbb8b3b3ef7d"}, + {file = "zstd-1.5.7.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:6f5539a10b838ee576084870eed65b63c13845e30a5b552cfe40f7e6b621e61a"}, + {file = "zstd-1.5.7.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:5540ce1c99fa0b59dad2eff771deb33872754000da875be50ac8c2beab42b433"}, + {file = "zstd-1.5.7.2-cp37-cp37m-win32.whl", hash = "sha256:56c4b8cd0a88fd721213661c28b87b64fbd14b6019df39b21b0117a68162b0f2"}, + {file = "zstd-1.5.7.2-cp37-cp37m-win_amd64.whl", hash = "sha256:594f256fa72852ade60e3acb909f983d5cf6839b9fc79728dd4b48b31112058f"}, + {file = "zstd-1.5.7.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9dc05618eb0abceb296b77e5f608669c12abc69cbf447d08151bcb14d290ab07"}, + {file = "zstd-1.5.7.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:70231ba799d681b6fc17456c3e39895c493b5dff400aa7842166322a952b7f2a"}, + {file = "zstd-1.5.7.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:5a73f0f20f71d4eef970a3fed7baac64d9a2a00b238acc4eca2bd7172bd7effb"}, + {file = "zstd-1.5.7.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0a470f8938f69f632b8f88b96578a5e8825c18ddbbea7de63493f74874f963ef"}, + {file = "zstd-1.5.7.2-cp38-cp38-manylinux_2_4_i686.whl", hash = "sha256:d104f1cb2a7c142007c29a2a62dfe633155c648317a465674e583c295e5f792d"}, + {file = "zstd-1.5.7.2-cp38-cp38-manylinux_2_4_x86_64.whl", hash = "sha256:70f29e0504fc511d4b9f921e69637fca79c050e618ba23732a3f75c044814d89"}, + {file = "zstd-1.5.7.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:a62c2f6f7b8fc69767392084828740bd6faf35ff54d4ccb2e90e199327c64140"}, + {file = "zstd-1.5.7.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f2dda0c76f87723fb7f75d7ad3bbd90f7fb47b75051978d22535099325111b41"}, + {file = "zstd-1.5.7.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:f9cf09c2aa6f67750fe9f33fdd122f021b1a23bf7326064a8e21f7af7e77faee"}, + {file = "zstd-1.5.7.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:910bd9eac2488439f597504756b03c74aa63ed71b21e5d0aa2c7e249b3f1c13f"}, + {file = "zstd-1.5.7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9838ec7eb9f1beb2f611b9bcac7a169cb3de708ccf779aead29787e4482fe232"}, + {file = "zstd-1.5.7.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:83a36bb1fd574422a77b36ccf3315ab687aef9a802b0c3312ca7006b74eeb109"}, + {file = "zstd-1.5.7.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:6f8189bc58415758bbbd419695012194f5e5e22c34553712d9a3eb009c09808d"}, + {file = "zstd-1.5.7.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:632e3c1b7e1ebb0580f6d92b781a8f7901d367cf72725d5642e6d3a32e404e45"}, + {file = "zstd-1.5.7.2-cp39-cp39-manylinux_2_4_i686.whl", hash = "sha256:df8083c40fdbfe970324f743f0b5ecc244c37736e5f3ad2670de61dde5e0b024"}, + {file = "zstd-1.5.7.2-cp39-cp39-manylinux_2_4_x86_64.whl", hash = "sha256:300db1ede4d10f8b9b3b99ca52b22f0e2303dc4f1cf6994d1f8345ce22dd5a7e"}, + {file = "zstd-1.5.7.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:97b908ccb385047b0c020ce3dc55e6f51078c9790722fdb3620c076be4a69ecf"}, + {file = "zstd-1.5.7.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c59218bd36a7431a40591504f299de836ea0d63bc68ea76d58c4cf5262f0fa3c"}, + {file = "zstd-1.5.7.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4d5a85344193ec967d05da8e2c10aed400e2d83e16041d2fdfb713cfc8caceeb"}, + {file = "zstd-1.5.7.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ebf6c1d7f0ceb0af5a383d2a1edc8ab9ace655e62a41c8a4ed5a031ee2ef8006"}, + {file = "zstd-1.5.7.2-cp39-cp39-win32.whl", hash = "sha256:44a5142123d59a0dbbd9ba9720c23521be57edbc24202223a5e17405c3bdd4a6"}, + {file = "zstd-1.5.7.2-cp39-cp39-win_amd64.whl", hash = "sha256:8dc542a9818712a9fb37563fa88cdbbbb2b5f8733111d412b718fa602b83ba45"}, + {file = "zstd-1.5.7.2-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:24371a7b0475eef7d933c72067d363c5dc17282d2aa5d4f5837774378718509e"}, + {file = "zstd-1.5.7.2-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:c21d44981b068551f13097be3809fadb7f81617d0c21b2c28a7d04653dde958f"}, + {file = "zstd-1.5.7.2-pp27-pypy_73-manylinux_2_14_x86_64.whl", hash = "sha256:b011bf4cfad78cdf9116d6731234ff181deb9560645ffdcc8d54861ae5d1edfc"}, + {file = "zstd-1.5.7.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:426e5c6b7b3e2401b734bfd08050b071e17c15df5e3b31e63651d1fd9ba4c751"}, + {file = "zstd-1.5.7.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:53375b23f2f39359ade944169bbd88f8895eed91290ee608ccbc28810ac360ba"}, + {file = "zstd-1.5.7.2-pp310-pypy310_pp73-manylinux_2_14_x86_64.whl", hash = "sha256:1b301b2f9dbb0e848093127fb10cbe6334a697dc3aea6740f0bb726450ee9a34"}, + {file = "zstd-1.5.7.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5414c9ae27069ab3ec8420fe8d005cb1b227806cbc874a7b4c73a96b4697a633"}, + {file = "zstd-1.5.7.2-pp311-pypy311_pp73-manylinux_2_14_x86_64.whl", hash = "sha256:5fb2ff5718fe89181223c23ce7308bd0b4a427239379e2566294da805d8df68a"}, + {file = "zstd-1.5.7.2-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:9714d5642867fceb22e4ab74aebf81a2e62dc9206184d603cb39277b752d5885"}, + {file = "zstd-1.5.7.2-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:6584fd081a6e7d92dffa8e7373d1fced6b3cbf473154b82c17a99438c5e1de51"}, + {file = "zstd-1.5.7.2-pp36-pypy36_pp73-manylinux_2_14_x86_64.whl", hash = "sha256:52f27a198e2a72632bae12ec63ebaa31b10e3d5f3dd3df2e01376979b168e2e6"}, + {file = "zstd-1.5.7.2-pp36-pypy36_pp73-win32.whl", hash = "sha256:3b14793d2a2cb3a7ddd1cf083321b662dd20bc11143abc719456e9bfd22a32aa"}, + {file = "zstd-1.5.7.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:faf3fd38ba26167c5a085c04b8c931a216f1baf072709db7a38e61dea52e316e"}, + {file = "zstd-1.5.7.2-pp37-pypy37_pp73-manylinux_2_14_x86_64.whl", hash = "sha256:d17ac6d2584168247796174e599d4adbee00153246287e68881efaf8d48a6970"}, + {file = "zstd-1.5.7.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:9a24d492c63555b55e6bc73a9e82a38bf7c3e8f7cde600f079210ed19cb061f2"}, + {file = "zstd-1.5.7.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:c6abf4ab9a9d1feb14bc3cbcc32d723d340ce43b79b1812805916f3ac069b073"}, + {file = "zstd-1.5.7.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:d7131bb4e55d075cb7847555a1e17fca5b816a550c9b9ac260c01799b6f8e8d9"}, + {file = "zstd-1.5.7.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:a03608499794148f39c932c508d4eb3622e79ca2411b1d0438a2ee8cafdc0111"}, + {file = "zstd-1.5.7.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:86e64c71b4d00bf28be50e4941586e7874bdfa74858274d9f7571dd5dda92086"}, + {file = "zstd-1.5.7.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:0f79492bf86aef6e594b11e29c5589ddd13253db3ada0c7a14fb176b132fb65e"}, + {file = "zstd-1.5.7.2-pp38-pypy38_pp73-manylinux_2_14_x86_64.whl", hash = "sha256:8c3f4bb8508bc54c00532931da4a5261f08493363da14a5526c986765973e35d"}, + {file = "zstd-1.5.7.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:787bcf55cefc08d27aca34c6dcaae1a24940963d1a73d4cec894ee458c541ac4"}, + {file = "zstd-1.5.7.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0f97f872cb78a4fd60b6c1024a65a4c52a971e9d991f33c7acd833ee73050f85"}, + {file = "zstd-1.5.7.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:5e530b75452fdcff4ea67268d9e7cb37a38e7abbac84fa845205f0b36da81aaf"}, + {file = "zstd-1.5.7.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7c1cc65fc2789dd97a98202df840537de186ed04fd1804a17fcb15d1232442c4"}, + {file = "zstd-1.5.7.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:05604a693fa53b60ca083992324b08dafd15a4ac37ac4cffe4b43b9eb93d4440"}, + {file = "zstd-1.5.7.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:baf4e8b46d8934d4e85373f303eb048c63897fc4191d8ab301a1bbdf30b7a3cc"}, + {file = "zstd-1.5.7.2-pp39-pypy39_pp73-manylinux_2_14_x86_64.whl", hash = "sha256:8cc35cc25e2d4a0f68020f05cba96912a2881ebaca890d990abe37aa3aa27045"}, + {file = "zstd-1.5.7.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:ceae57e369e1b821b8f2b4c59bc08acd27d8e4bf9687bfa5211bc4cdb080fe7b"}, + {file = "zstd-1.5.7.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:5189fb44c44ab9b6c45f734bd7093a67686193110dc90dcfaf0e3a31b2385f38"}, + {file = "zstd-1.5.7.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:f51a965871b25911e06d421212f9be7f7bcd3cedc43ea441a8a73fad9952baa0"}, + {file = "zstd-1.5.7.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:624022851c51dd6d6b31dbfd793347c4bd6339095e8383e2f74faf4f990b04c6"}, + {file = "zstd-1.5.7.2.tar.gz", hash = "sha256:6d8684c69009be49e1b18ec251a5eb0d7e24f93624990a8a124a1da66a92fc8a"}, +] + [metadata] lock-version = "2.1" python-versions = ">3.9.1,<3.13" -content-hash = "a367e65bc43c0a16495a3d0f6eab8b356cc49b509e329b61c6641cd87f374ff4" +content-hash = "433468987cb3c4499d094d90e9f8cc9062a25ce115fde991a4e1b39edbfb7815" diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 0b7fe848f0..84c9596d1d 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to the **Prowler SDK** are documented in this file. ### Added - `cloudstorage_uses_vpc_service_controls` check for GCP provider [(#9256)](https://github.com/prowler-cloud/prowler/pull/9256) +- Alibaba Cloud provider with CIS 2.0 benchmark [(#9329)](https://github.com/prowler-cloud/prowler/pull/9329) - `repository_immutable_releases_enabled` check for GitHub provider [(#9162)](https://github.com/prowler-cloud/prowler/pull/9162) - `compute_instance_preemptible_vm_disabled` check for GCP provider [(#9342)](https://github.com/prowler-cloud/prowler/pull/9342) - `compute_instance_automatic_restart_enabled` check for GCP provider [(#9271)](https://github.com/prowler-cloud/prowler/pull/9271) diff --git a/prowler/__main__.py b/prowler/__main__.py index 9587a01cbb..e300033e95 100644 --- a/prowler/__main__.py +++ b/prowler/__main__.py @@ -56,6 +56,7 @@ from prowler.lib.outputs.compliance.c5.c5_gcp import GCPC5 from prowler.lib.outputs.compliance.ccc.ccc_aws import CCC_AWS from prowler.lib.outputs.compliance.ccc.ccc_azure import CCC_Azure from prowler.lib.outputs.compliance.ccc.ccc_gcp import CCC_GCP +from prowler.lib.outputs.compliance.cis.cis_alibabacloud import AlibabaCloudCIS from prowler.lib.outputs.compliance.cis.cis_aws import AWSCIS from prowler.lib.outputs.compliance.cis.cis_azure import AzureCIS from prowler.lib.outputs.compliance.cis.cis_gcp import GCPCIS @@ -104,6 +105,7 @@ from prowler.lib.outputs.ocsf.ocsf import OCSF from prowler.lib.outputs.outputs import extract_findings_statistics, report from prowler.lib.outputs.slack.slack import Slack from prowler.lib.outputs.summary_table import display_summary_table +from prowler.providers.alibabacloud.models import AlibabaCloudOutputOptions from prowler.providers.aws.lib.s3.s3 import S3 from prowler.providers.aws.lib.security_hub.security_hub import SecurityHub from prowler.providers.aws.models import AWSOutputOptions @@ -347,6 +349,10 @@ def prowler(): output_options = OCIOutputOptions( args, bulk_checks_metadata, global_provider.identity ) + elif provider == "alibabacloud": + output_options = AlibabaCloudOutputOptions( + args, bulk_checks_metadata, global_provider.identity + ) # Run the quick inventory for the provider if available if hasattr(args, "quick_inventory") and args.quick_inventory: @@ -1018,6 +1024,34 @@ def prowler(): generated_outputs["compliance"].append(generic_compliance) generic_compliance.batch_write_data_to_file() + elif provider == "alibabacloud": + for compliance_name in input_compliance_frameworks: + if compliance_name.startswith("cis_"): + # Generate CIS Finding Object + filename = ( + f"{output_options.output_directory}/compliance/" + f"{output_options.output_filename}_{compliance_name}.csv" + ) + cis = AlibabaCloudCIS( + findings=finding_outputs, + compliance=bulk_compliance_frameworks[compliance_name], + file_path=filename, + ) + generated_outputs["compliance"].append(cis) + cis.batch_write_data_to_file() + else: + filename = ( + f"{output_options.output_directory}/compliance/" + f"{output_options.output_filename}_{compliance_name}.csv" + ) + generic_compliance = GenericCompliance( + findings=finding_outputs, + compliance=bulk_compliance_frameworks[compliance_name], + file_path=filename, + ) + generated_outputs["compliance"].append(generic_compliance) + generic_compliance.batch_write_data_to_file() + # AWS Security Hub Integration if provider == "aws": # Send output to S3 if needed (-B / -D) for all the output formats diff --git a/prowler/compliance/alibabacloud/__init__.py b/prowler/compliance/alibabacloud/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/compliance/alibabacloud/cis_2.0_alibabacloud.json b/prowler/compliance/alibabacloud/cis_2.0_alibabacloud.json new file mode 100644 index 0000000000..7cda08efbb --- /dev/null +++ b/prowler/compliance/alibabacloud/cis_2.0_alibabacloud.json @@ -0,0 +1,1833 @@ +{ + "Framework": "CIS", + "Name": "CIS Alibaba Cloud Foundations Benchmark v2.0.0", + "Version": "2.0", + "Provider": "alibabacloud", + "Description": "The CIS Alibaba Cloud Foundations Benchmark provides prescriptive guidance for configuring security options for a subset of Alibaba Cloud services with an emphasis on foundational, testable, and architecture agnostic settings.", + "Requirements": [ + { + "Id": "1.1", + "Description": "Avoid the use of the root account", + "Attributes": [ + { + "Section": "1. Identity and Access Management", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "An Alibaba Cloud account can be viewed as a “root” account. The root account has full control permissions to all cloud products and resources under such account. It is highly recommended that the use of this account should be avoided.", + "RationaleStatement": "The root account is the owner of the resources under an Alibaba Cloud account. This account pays for and has full control permissions to resources. Minimizing the use of such account and adopting the principle of least privilege for access management can reduce the risk of accidental or unauthorized changes and disclosure of highly privileged credentials.", + "ImpactStatement": "", + "RemediationProcedure": "All users should operate resources at the RAM user level and follow the principle of least privilege. Follow the remediation instructions of the Ensure RAM policies are attached only to groups or roles recommendation. For more information about RAM user, see terms of RAM user.", + "AuditProcedure": "You can enable ActionTrail for your account, and create a trail to deliver all action logs to Alibaba Cloud Log Service. Then, you can enable an alarm to discover the usage of root account and receive notifications on those conditions. Implement the Ensure a log metric filter and alarm exist for usage of root account recommendation in the Logging and Monitoring section to receive notifications of root account usage. Note: There are a few conditions under which the use of the root account is required, such as requesting account security report or configuring multi-factor authentication (MFA) for the root account.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/102600.htm", + "DefaultValue": "" + } + ], + "Checks": [] + }, + { + "Id": "1.2", + "Description": "Ensure no root account access key exists", + "Attributes": [ + { + "Section": "1. Identity and Access Management", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "Access keys provide programmatic access to a given Alibaba Cloud account. It is recommended that all access keys associated with the root account be removed.", + "RationaleStatement": "An Alibaba Cloud account can be viewed as a “root” account. The root account has the highest privilege of an Alibaba Cloud account. Removing access keys associated with the root account limits the opportunity that the account can be compromised.", + "ImpactStatement": "Programs that already use root account access keys may stop working if you disable or delete the access keys without replacing them with other RAM user access keys in your program.", + "RemediationProcedure": "Perform the following to delete or disable active root access keys: Using the management console 1. Logon to RAM console by using your Alibaba Cloud account (root account). 2. Move the pointer over the account icon in the upper-right corner and click AccessKey. 3. Click Continue to manage AccessKey. 4. On the Security Management page, find the target access keys and perform the following operations: o Click Disable to disable the target access keys temporarily. o Click Delete to delete the target access keys permanently.", + "AuditProcedure": "Perform the following to determine if the root account has access keys: Using the management console: 1. Logon to Resource Access Management (RAM) console https://ram.console.aliyun.com/overview by using your Alibaba Cloud account (root account). 2. In the left-side navigation pane, click Overview. 3. In the Security Check section, make sure that No AK for Root Account is marked as Finished.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/102600.htm", + "DefaultValue": "By default, no access key is created for the root account." + } + ], + "Checks": [ + "ram_no_root_access_key" + ] + }, + { + "Id": "1.3", + "Description": "Ensure MFA is enabled for the root account", + "Attributes": [ + { + "Section": "1. Identity and Access Management", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "With MFA enabled, anytime the “root” account logs on to Alibaba Cloud, it will be prompted for username and password followed by an authentication code from the virtual MFA device. It is recommended that MFA be enabled for the “root” user.", + "RationaleStatement": "It is important to prevent “root” account from being compromised. Enabling MFA requires the “root” account holder to provide additional information on top of username and password. When MFA is enabled, an attacker faces at least two different authentication mechanisms. The additional security makes it harder for an attacker to gain access to protected resources or data.", + "ImpactStatement": "", + "RemediationProcedure": "Perform the following to enable MFA for “root” account Using the management console: 1. Logon to RAM console by using your Alibaba Cloud account (root account). 2. Move the pointer over the account icon in the upper-right corner and click Security Settings. 3. In the Account Protection section, Click Edit. 4. On the displayed page, select a scenario and select TOTP. 5. Click Submit. 6. On the displayed page, click Verify now. 7. Enter the verification code and click Submit. 8. Download and install a mobile application that supports TOTP MFA, such as Google Authenticator, on your mobile phone. Note: If you already installed Google Authenticator, click Next. o For iOS: Install Google Authenticator from the App Store. o For Android: Install Google Authenticator from the Google Play Store. Note: You need to install a QR code scanner from the Google Play Store for Google Authenticator to identify QR codes. 9. After you install Google Authenticator, go back to the Identity Verification page and click Next. 10. Open Google Authenticator and tap BEGIN SETUP. o Tap Scan barcode and scan the QR code on the Identity Verification page. o Tap Manual entry, enter the username and key, and then tap the check mark (√) icon. Note: You can obtain the username and key by moving the pointer over Scan failed on the Identity Verification page. 11. On the Identity Verification page, enter the 6-digit verification code obtained from Google Authenticator and click Next. Note: The verification code is refreshed at an interval of 30 seconds.", + "AuditProcedure": "Perform the following to determine if an MFA device is enabled for the “root” account: Using the management console: 1. Logon to RAM console by using your Alibaba Cloud account (root account). 2. In the left-side navigation pane, click Overview. 3. In the Security Check section, make sure that Enable MFA for Root Account is marked as Finished.", + "AdditionalInformation": "", + "References": "http://tools.ietf.org/html/rfc6238 https://www.alibabacloud.com/help/doc-detail/28635.htm", + "DefaultValue": "" + } + ], + "Checks": [] + }, + { + "Id": "1.4", + "Description": "Ensure that multi-factor authentication is enabled for all RAM users that have a console password", + "Attributes": [ + { + "Section": "1. Identity and Access Management", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "Multi-Factor Authentication (MFA) adds an extra layer of protection on top of a username and password. With MFA enabled, when a user logs on to Alibaba Cloud, they will be prompted for their user name 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.", + "RationaleStatement": "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. The additional security makes it harder for an attacker to gain access to protected resources or data.", + "ImpactStatement": "", + "RemediationProcedure": "Perform the following to determine if an MFA device is enabled for all RAM users having a console password: Using the management console: 1. Logon to RAM console. 2. Choose Identities > Users. 3. In the User Logon Name/Display Name column, click the username of each RAM user. 4. In the Console Logon Management section, click Modify Logon Settings. 5. Select Enabled for Console Password Logon, and Required for Enable MFA. Note: After you select Enabled for Console Password Logon, and Required for Enable MFA when modifying the logon settings of a RAM user, the user can go to step 7 when logging on to the RAM console for the first time. 6. In the MFA Device section, click Enable the device. 7. Download and install Google Authenticator on your mobile phone. o For iOS: Install Google Authenticator from the App Store. o For Android: Install Google Authenticator from the Google Play Store. Note: You need to install a QR code scanner from the Google Play Store for Google Authenticator to identify QR codes. 8. Open Google Authenticator and tap BEGIN SETUP. o Tap Scan barcode and scan the QR code displayed on the Scan the code tab in the console. o Tap Manual entry, enter the username and key, and then tap the check mark (√) icon. Note: You can obtain the username and key from the Retrieval manually enter information tab in the console. 9. On the Scan the code tab, enter the two consecutive security codes obtained from Google Authenticator and click Enable. Note: The security code is refreshed at an interval of 30 seconds. For more information, see Enable an MFA device for a RAM user.", + "AuditProcedure": "Perform the following to determine if an MFA device is enabled for all RAM users having a console password: Using the management console: 1. Logon to RAM console. 2. Choose Identities > Users. 3. In the User Logon Name/Display Name column, click the username of each RAM user. 4. In the Console Logon Management section, if Console Access is set to Enabled, make sure that Required to Enable MFA is set to Yes. Using the CLI Run the following command to determine if an MFA device is enabled for a RAM user: aliyun ram GetUserMFAInfo --UserName Note: If an error is reported, no MFA device is enabled for the RAM user.", + "AdditionalInformation": "", + "References": "http://tools.ietf.org/html/rfc6238 https://www.alibabacloud.com/help/doc-detail/93720.htm https://www.alibabacloud.com/help/doc-detail/119555.htm https://www.alibabacloud.com/help/en/ram/user-guide/bind-an-mfa-device-to-a-", + "DefaultValue": "MFA is enabled by default for RAM users" + } + ], + "Checks": [ + "ram_user_mfa_enabled_console_access" + ] + }, + { + "Id": "1.5", + "Description": "Ensure users not logged on for 90 days or longer are disabled for console logon", + "Attributes": [ + { + "Section": "1. Identity and Access Management", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "Alibaba Cloud RAM users can logon to Alibaba Cloud console by using their user name 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.", + "RationaleStatement": "Disabling users from having unnecessary logon privileges will reduce the opportunity that an abandoned user or a user with compromised password to be used.", + "ImpactStatement": "RAM users who still need to log on to the management console or other Alibaba Cloud sites may encounter logon failure.", + "RemediationProcedure": "Perform the following to disable console logon for a user: Using the management console: 1. Logon to RAM console. 2. Choose Identities > Users. 3. In the User Logon Name/Display Name column, click the username of the target RAM user. 4. In the Console Logon Management section, click Modify Logon Settings. 5. In the Console Password Logon section, select Disabled. 6. Click OK. Using the CLI aliyun ram DeleteLoginProfile --UserName ", + "AuditProcedure": "Perform the following to determine if a user has not logged on for 90 days or longer: Using the management console: 1. Logon RAM console. 2. Choose Identities > Users. 3. In the User Logon Name/Display Name column, click the username of each RAM user. 4. In the Console Logon Management section, check the latest logon time of each user in the Last Console Logon field. 5. Make sure that each user does not have a last console logon time dated earlier than 90 days ago.", + "AdditionalInformation": "", + "References": "", + "DefaultValue": "" + } + ], + "Checks": [ + "ram_user_console_access_unused" + ] + }, + { + "Id": "1.6", + "Description": "Ensure access keys are rotated every 90 days or less", + "Attributes": [ + { + "Section": "1. Identity and Access Management", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "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. RAM users need their own access keys to make programmatic calls to Alibaba Cloud from the Alibaba Cloud SDKs, CLIs, or direct HTTP/HTTPS calls using the APIs for individual Alibaba Cloud services. It is recommended that all access keys be regularly rotated.", + "RationaleStatement": "Access keys might be compromised by leaving them in codes, configuration files, on premise and cloud storages, and then stolen by attackers. Rotating access keys will reduce the window of opportunity that a compromised access key to be used.", + "ImpactStatement": "", + "RemediationProcedure": "Perform the following to disable and delete access keys: Using the management console: 1. Logon to RAM console. 2. In the left-side navigation pane, click Users under Identities. 3. In the User Logon Name/Display Name column, click the username of the target RAM user. 4. In the User AccessKeys section, click Create AccessKey. 5. Click OK to create a new AccessKy pair for rotation. 6. Update all applications and systems to use the new AccessKey pair. 7. Disable the original AccessKey pair by following below steps: a) Log on to RAM console. b) In the left-side navigation pane, click Users under Identities. c) On the Users page, click username of the target RAM user in the User Logon Name/Display Name column. d) In the User AccessKeys section, find the target AccessKey pair and click Disable. 8. Confirm that your applications and systems are working. 9. Delete the original AccessKey pair by following below steps: a) Log on to RAM console. b) In the left-side navigation pane, click Users under Identities. c) In the User Logon Name/Display Name column, click the username of the target RAM user. d) In the User AccessKeys section, find the target access keys and Click Delete. e) In the dialog box that appears, select I am aware of the risk and confirm the deletion. 10. Click OK. Using the CLI: • Run the following command to delete an access key: aliyun ram DeleteAccessKey --UserAccessKeyId --UserName • Run the following command to disable an active access key: aliyun ram UpdateAccessKey --UserAccessKeyId --Status Inactive --UserName • Run the following command to delete an access key: aliyun ram DeleteAccessKey --UserAccessKeyId --UserName Your programs that use access keys may stop working if you rotate the access keys without replacing them in your program prior to the rotation.", + "AuditProcedure": "Perform the following to determine if access keys are rotated within 90 days: Using the management console: 1. Logon to RAM console. 2. Choose Identities > Groups. 3. In the User Logon Name/Display Name column, click the username of each RAM user. 4. In the User AccessKeys section, check the date and time that an access key was created. 5. Make sure that no user has an access key created earlier than 90 days ago. Using the CLI: Run the following command to obtain a list of access keys of a RAM user, and then determine if the access keys are rotated within 90 days according to the CreateDate parameter: aliyun ram ListAccessKeys --UserName Note: In the output, if the AccessKey parameter is empty, no access key exists.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/116806.htm https://www.alibabacloud.com/help/doc-detail/116808.htm https://www.alibabacloud.com/help/doc-detail/152682.htm https://www.alibabacloud.com/help/doc-detail/116401.htm", + "DefaultValue": "" + } + ], + "Checks": [ + "ram_rotate_access_key_90_days" + ] + }, + { + "Id": "1.7", + "Description": "Ensure RAM password policy requires at least one uppercase letter", + "Attributes": [ + { + "Section": "1. Identity and Access Management", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "RAM password policies can be used to ensure password complexity. It is recommended that the password policy require at least one uppercase letter.", + "RationaleStatement": "Enhancing complexity of a password policy increases account resiliency against brute force logon attempts.", + "ImpactStatement": "", + "RemediationProcedure": "Perform the following to set the password policy as expected: Using the management console: 1. Logon to RAM console. 2. Choose Settings. 3. In the Password section, click Modify. 4. In the Charset section, select Upper case. 5. Click OK. Using the CLI: aliyun ram SetPasswordPolicy --RequireUppercaseCharacters true", + "AuditProcedure": "Perform the following to ensure the password policy is configured as expected: Using the management console: 1. Logon to RAM console. 2. Choose Settings. 3. In the Password section, make sure that the value of Charset contains Upper case. Using the CLI: aliyun ram GetPasswordPolicy In the output, make sure that the RequireUppercaseCharacters parameter is set to true.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/116413.htm", + "DefaultValue": "The default password policy does not enforce any charset in a password." + } + ], + "Checks": [ + "ram_password_policy_uppercase" + ] + }, + { + "Id": "1.8", + "Description": "Ensure RAM password policy requires at least one lowercase letter", + "Attributes": [ + { + "Section": "1. Identity and Access Management", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "RAM password policies can be used to ensure password complexity. It is recommended that the password policy require at least one lowercase letter.", + "RationaleStatement": "Enhancing complexity of a password policy increases account resiliency against brute force logon attempts.", + "ImpactStatement": "", + "RemediationProcedure": "Perform the following to set the password policy as expected: Using the management console: 1. Logon to RAM console. 2. Choose Settings. 3. In the Password section, click Modify. 4. In the Charset section, select Upper case. 5. Click OK. Using the CLI: aliyun ram SetPasswordPolicy --RequireLowercaseCharacters true", + "AuditProcedure": "Perform the following to ensure the password policy is configured as expected: Using the management console: 1. Logon to RAM console. 2. Choose Settings. 3. In the Password section, make sure that the value of Charset contains Lower case. Using the CLI: aliyun ram GetPasswordPolicy In the output, make sure that the RequireLowercaseCharacters parameter is set to true.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/116413.htm", + "DefaultValue": "The default password policy does not enforce any Charset in a password." + } + ], + "Checks": [ + "ram_password_policy_lowercase" + ] + }, + { + "Id": "1.9", + "Description": "Ensure RAM password policy require at least one symbol", + "Attributes": [ + { + "Section": "1. Identity and Access Management", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "RAM password policies can be used to ensure password complexity. It is recommended that the password policy require at least one symbol.", + "RationaleStatement": "Enhancing complexity of a password policy increases account resiliency against brute force logon attempts.", + "ImpactStatement": "", + "RemediationProcedure": "Perform the following to set the password policy as expected: Using the management console: 1. Logon to RAM console. 2. Choose Settings. 3. In the Password section, click Modify. 4. In the Charset section, select Symbol. 5. Click OK. Using the CLI: aliyun ram SetPasswordPolicy --RequireSymbols true", + "AuditProcedure": "Perform the following to ensure the password policy is configured as expected: Using the management console: 1. Logon to RAM console. 2. Choose Settings. 3. In the Password section, make sure that the value of Charset contains Symbol. Using the CLI: aliyun ram GetPasswordPolicy In the output, make sure that the RequireSymbols parameter is set to true.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/116413.htm", + "DefaultValue": "The default password policy does not enforce any Charset in a password." + } + ], + "Checks": [ + "ram_password_policy_symbol" + ] + }, + { + "Id": "1.10", + "Description": "Ensure RAM password policy require at least one number", + "Attributes": [ + { + "Section": "1. Identity and Access Management", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "RAM password policies can be used to ensure password complexity. It is recommended that the password policy require at least one number.", + "RationaleStatement": "Enhancing complexity of a password policy increases account resiliency against brute force logon attempts.", + "ImpactStatement": "", + "RemediationProcedure": "Perform the following to set the password policy as expected: Using the management console 1. Logon to RAM console. 2. Choose Settings. 3. In the Password section, click Modify. 4. In the Charset section, select Number. 5. Click OK. Using the CLI aliyun ram SetPasswordPolicy --RequireNumbers true", + "AuditProcedure": "Perform the following to ensure the password policy is configured as expected: Using the management console: 1. Logon to RAM console. 2. Choose Settings. 3. In the Password section, make sure that the value of Charset contains Number. Using the CLI: aliyun ram GetPasswordPolicy In the output, make sure that the RequireNumbers parameter is set to true.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/116413.htm", + "DefaultValue": "The default password policy does not enforce any charset in a password." + } + ], + "Checks": [] + }, + { + "Id": "1.11", + "Description": "Ensure RAM password policy requires minimum length of 14 or greater", + "Attributes": [ + { + "Section": "1. Identity and Access Management", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "RAM password policies can be used to ensure password complexity. It is recommended that the password policy require a minimum of 14 or greater characters for any password.", + "RationaleStatement": "Enhancing complexity of a password policy increases account resiliency against brute force logon attempts.", + "ImpactStatement": "", + "RemediationProcedure": "Perform the following to set the password policy: Using the management console: 1. Logon to RAM console. 2. Choose Settings. 3. In the Password section, click Modify. 4. In the Length section, enter 14 or a greater number. 5. Click OK. Using the CLI aliyun ram SetPasswordPolicy --MinimumPasswordLength 14", + "AuditProcedure": "Perform the following to ensure the password policy is configured as expected: Using the management console: 1. Logon to RAM console. 2. Choose Settings. 3. In the Password section, make sure that the value of Length is a value of 14 to 32. Using the CLI: aliyun ram GetPasswordPolicy In the output, make sure that the MinimumPasswordLength parameter is set to 14 or a greater number.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/116413.htm", + "DefaultValue": "The default password policy requires a minimum of 8 characters for a password." + } + ], + "Checks": [ + "ram_password_policy_minimum_length" + ] + }, + { + "Id": "1.12", + "Description": "Ensure RAM password policy prevents password reuse", + "Attributes": [ + { + "Section": "1. Identity and Access Management", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "It is recommended that the password policy prevent the reuse of passwords.", + "RationaleStatement": "Preventing password reuse increases account resiliency against brute force logon attempt.", + "ImpactStatement": "", + "RemediationProcedure": "Perform the following to set the password policy as expected: Using the management console: 1. Logon to RAM console. 2. Choose Settings. 3. In the Password section, click Modify. 4. In the Do Not repeat History section field, enter '5'. 5. Click OK. Using the CLI: aliyun ram SetPasswordPolicy --PasswordReusePrevention 5", + "AuditProcedure": "Perform the following to ensure the password policy is configured as expected: Using the management console: 1. Logon to RAM console. 2. Choose Settings. 3. In the Password section, make sure that the value of Do Not Repeat History is set to 5. Using the CLI: aliyun ram GetPasswordPolicy In the output, make sure that the PasswordReusePrevention parameter is set to 5.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/116413.htm", + "DefaultValue": "The default password policy does not prevent password reuse." + } + ], + "Checks": [ + "ram_password_policy_password_reuse_prevention" + ] + }, + { + "Id": "1.13", + "Description": "Ensure RAM password policy expires passwords in 365 days or greater", + "Attributes": [ + { + "Section": "1. Identity and Access Management", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "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 greater.", + "RationaleStatement": "To 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. In an effort to scale back, the CIS now recommends an annual password reset. Users inevitably share credentials between accounts, and this measure causes minimal burden. This compliments other industry best practices that call for password to be changed only when there's a confirmed or suspected breach.", + "ImpactStatement": "", + "RemediationProcedure": "Perform the following to set the password policy as expected: Using the management console: 1. Logon to RAM console. 2. Choose Setting. 3. In the Password section, click Modify. 4. check the box under Max Age, enter 365 or a greater number up to 1095. 5. Click OK. Using the CLI: aliyun ram SetPasswordPolicy --MaxPasswordAge 365", + "AuditProcedure": "Perform the following to ensure the password policy is configured as expected: Using the management console: 1. Logon to RAM console. 2. Choose Settings. 3. In the Password section, make sure that the value of Max Age is either disabled or greater than 365. Using the CLI: aliyun ram GetPasswordPolicy In the output, make sure that the MaxPasswordAge parameter is set to <365> or a greater number.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/116413.htm", + "DefaultValue": "The default password policy does not define max age." + } + ], + "Checks": [ + "ram_password_policy_max_password_age" + ] + }, + { + "Id": "1.14", + "Description": "Ensure RAM password policy temporarily blocks logon after 5 incorrect logon attempts within an hour", + "Attributes": [ + { + "Section": "1. Identity and Access Management", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "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.", + "RationaleStatement": "Temporarily blocking logon for incorrect password input increases account resiliency against brute force logon attempts.", + "ImpactStatement": "", + "RemediationProcedure": "Perform the following to set the password policy as expected: Using the management console: 1. Logon to RAM console. 2. Choose Settings. 3. In the Password section, click MOdify. 4. In the Max Attempts field, Check the box next to Enable and enter 5 in the field. 5. Click OK. Using the CLI: aliyun ram SetPasswordPolicy --MaxLoginAttemps 5", + "AuditProcedure": "Perform the following to ensure the password policy is configured as expected: Using the management console: 1. Logon to RAM console. 2. Choose Settings. 3. In the Password section, make sure that the value of Max Attempts is 5. Using the CLI: aliyun ram GetPasswordPolicy In the output, make sure that the MaxLoginAttemps parameter is set to <5>.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/116413.htm", + "DefaultValue": "The default password policy does not define Max Attempts." + } + ], + "Checks": [ + "ram_password_policy_max_login_attempts" + ] + }, + { + "Id": "1.15", + "Description": "Ensure RAM policies that allow full *:* administrative privileges are not created", + "Attributes": [ + { + "Section": "1. Identity and Access Management", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "RAM policies represent permissions that can be granted to users, groups, or roles. It is recommended and considered a standard security advice to grant least privilege—that is, granting only the permissions required to perform tasks. Determine what users need to do and then create policies with permissions only fits those tasks, instead of allowing full administrative privileges.", + "RationaleStatement": "It is more secure to start with a minimum set of permissions and grant additional permissions as necessary, rather than starting with permissions that exceed the necessity and then trying to tighten them later. Providing full administrative privileges exposes your resources on Alibaba Cloud to potentially unwanted actions. RAM policies that have a statement with Effect: Allow, Action: *, and Resource: * should be prohibited.", + "ImpactStatement": "If you edit the policy document, or remove all references from the policy, the identities using this policy may encounter access denied errors for the actions and resources that are not covered by their current permissions.", + "RemediationProcedure": "Perform the following to detach the policy that has full administrative privileges and remove them: Using the management console: 1. Logon to RAM console. 2. Choose Permissions > Policies. 3. From the Policy Type drop-down list, select Custom Policy. 4. In the Policy Name column, click the name of the target policy. 5. In the Policy Document section, check whether the policy has a statement that includes Effect: Allow, Action: , and Resource: . o If it does not, skip this section. o If it does, edit the policy to remove such statement or remove the policy from any RAM users, user groups, or roles that have this policy attached. - To edit the policy: a. On the Policy Document tab, click Modify Policy Document. b. Remove the entire “Statement” element which contains the full : administrative privilege, or modify it to a smaller permission. - To remove all references from the policy: a. Go to the References tab, review if there is any reference of the custom policy. b. For each reference, click Revoke Permission. 6. Click OK. Using the CLI: 1. Run the following command to list all RAM users, groups, and roles to which the specified policy (i.e. policy with .) is attached: aliyun ram ListEntitiesForPolicy --PolicyName --PolicyType Custom 2. Run the following command to detach the policy from all RAM users: aliyun ram DetachPolicyFromUser --PolicyName --PolicyType Custom --UserName 3. Run the following command to detach the policy from all RAM user groups: aliyun ram DetachPolicyFromGroup --PolicyName --PolicyType Custom --GroupName 4. Run the following command to detach the policy from all RAM roles: aliyun ram DetachPolicyFromRole --PolicyName --PolicyType Custom --RoleName ", + "AuditProcedure": "Perform the following to check what permissions are allowed inside a policy: Using the management console: 1. Logon to RAM console. 2. Choose Permissions > Policies. 3. From the Policy Type drop-down list, select Custom Policy. 4. In the Policy Name column, click the name of each policy. 5. In the Policy Document section, make sure that no policy has a statement that includes Effect: Allow, Action: *, and Resource: *, or any policy with such statement is not attached to any RAM identities (including RAM user, group, or role). Using the CLI: 1. Run the following command to obtain a list of policies aliyun ram ListPolicies --PolicyType Custom 2. For each policy returned, run the following command to determine if any policies allow full administrative privileges: aliyun ram GetPolicy --PolicyName --PolicyType Custom Note: In the preceding command, policy_name is the value of the PolicyName parameter in each policy the ListPolicies command returned. In the output, check the value of PolicyDocument under DefaultPolicyVersion to make sure that no policy has a statement that includes Effect: Allow, Action: *, and Resource: *, or make sure that the value of AttachmentCount under Policy is set to 0 for such policies.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/93733.htm https://www.alibabacloud.com/help/doc-detail/116803.htm https://www.alibabacloud.com/help/doc-detail/116818.htm", + "DefaultValue": "By default, no custom policy is created." + } + ], + "Checks": [ + "ram_policy_no_administrative_privileges" + ] + }, + { + "Id": "1.16", + "Description": "Ensure RAM policies are attached only to groups or roles", + "Attributes": [ + { + "Section": "1. Identity and Access Management", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "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. It is recommended that RAM policies be applied directly to groups and roles but not users.", + "RationaleStatement": "Assigning privileges at the group or role level reduces the complexity of access management as the number of users grows. Reducing access management complexity may in-turn reduce opportunity for a principal to inadvertently receive or retain excessive privileges.", + "ImpactStatement": "There may be cases that a user needs to have permissions that cannot be covered by the groups it joins or roles it can assume. It may still be needed to attach specific policies to RAM users for certain operation that cannot be grouped with other permission under role or group.", + "RemediationProcedure": "Perform the following to create a RAM user group and assign a policy to it: Using the management console: 1. Log on to RAM console. 2. Choose Identities > Users. 3. Click Create Group, and enter the group name, display name, and description. 4. Click OK. 5. In the Group Name/Display Name column, find the target RAM user group and click Add Permissions. 6. In the Select Policy section, select the target policy or policies and click OK. Using the CLI: 1. Run the following command to create a RAM user group: aliyun ram CreateGroup –GroupName 2. Run the following command to attach a policy to the group: aliyun ram AttachPolicyToGroup --GroupName --PolicyName --PolicyType Perform the following to add a user to a given group: Using the management console: 1. Log on to RAM console. 2. Choose Identities > Groups. 3. In the Group Name/Display Name column, find the target RAM user group and click Add Group Members. 4. In the User section, select the target RAM user and click OK. Using the CLI: Run the following command to add a RAM user to a user group: aliyun ram AddUserToGroup --GroupName --UserName Perform the following to remove a direct association between a user and policy: Using the management console: 1. Logon to RAM console. 2. Choose Permissions > Grants. 3. In the Principal column, find the target RAM user and click Revoke Permission. 4. Click OK. Using the CLI: Run the following command to remove a policy from a RAM user: aliyun ram DetachPolicyFromUser --PolicyName --PolicyType --UserName ", + "AuditProcedure": "Perform the following to determine if policies are attached directly to users: Using the management console: 1. Logon to RAM console. 2. Choose Identities > Users. 3. In the User Logon Name/Display Name column, click the username of each RAM user. 4. Click the Permissions tab. 5. On the Individual tab, make sure that no policy exists. Using the CLI: 1. Run the following command to obtain a list of RAM users: aliyun ram ListUsers 2. For each user returned, run the following command to determine if any policies are attached to the user: aliyun ram ListPoliciesForUser --UserName If any polices are returned, the user has a direct policy attached.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/116809.htm https://www.alibabacloud.com/help/doc-detail/116815.htm https://www.alibabacloud.com/help/doc-detail/116147.htm https://www.alibabacloud.com/help/doc-detail/116820.htm", + "DefaultValue": "" + } + ], + "Checks": [ + "ram_policy_attached_only_to_group_or_roles" + ] + }, + { + "Id": "2.1", + "Description": "Ensure that ActionTrail are configured to export copies of all Log entries", + "Attributes": [ + { + "Section": "2. Logging and Monitoring", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "ActionTrail is a web service that records API calls for your account and delivers log files to you. The 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, command line tools.", + "RationaleStatement": "The API call history produced by ActionTrail enables security analysis, resource change tracking, and compliance auditing. Moreover, ensuring that a multi-regions trail exists will ensure that any unexpected activities occurring in otherwise unused regions are detected. Global Service Logging should be enabled by default to capture recording of events generated on Alibaba Cloud global services for a multi-regions trail, therefore, ensuring the recording of management operations that are performed on all resources in an Alibaba Cloud account.", + "ImpactStatement": "OSS lifecycle features can be used to manage the accumulation and management of logs over time. See the following resource for more information on these features: http://help.aliyun.com/document_detail/31863.html", + "RemediationProcedure": "Perform the following to enable global (Multi-region) ActionTrail logging: Using the management Console: 1. Logon to ActionTrail Console. 2. Click on Trails on the left navigation pane. 3. Click Add new trail. a. Enter a trail name in the Trail name box. b. Set Yes for Apply Trail to All Regions. c. Specify an OSS bucket name in the OSS bucket box. d. Specify an SLS project name in the SLS project box. e. Click Create. Using CLI: aliyun actiontrail CreateTrail --Name --OssBucketName --RoleName aliyunactiontraildefaultrole --SlsProjectArn --SlsWriteRoleArn --EventRW aliyun actiontrail UpdateTrail --Name --OssBucketName --RoleName aliyunactiontraildefaultrole --SlsProjectArn --SlsWriteRoleArn --EventRW ", + "AuditProcedure": "Perform the following to determine if ActionTrail is enabled for all regions: Using the management Console: 1. Logon to ActionTrail Console. 2. Click on Trails on the left navigation pane, you will be presented with a list of trails across all regions. 3. Ensure at least one Trail has All specified in the Region column. 4. Click on a trail via the link in the Name column. 5. Ensure Logging is set to Enable to export log copies to OSS for storage. 6. Ensure Yes is selected for Apply Trail to All Regions. Using CLI: Ensure Trail is set to enable and Trail Region is set to All aliyun actiontrail DescribeTrails", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/28829.htm", + "DefaultValue": "By default, there are no trails configured. Once the trail is enabled, it applies to all regions by default." + } + ], + "Checks": [ + "actiontrail_multi_region_enabled" + ] + }, + { + "Id": "2.2", + "Description": "Ensure the OSS used to store ActionTrail logs is not publicly accessible", + "Attributes": [ + { + "Section": "2. Logging and Monitoring", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "ActionTrail logs a record of every API call made in your Alibaba Cloud account. These logs file are stored in an OSS bucket. It is recommended that the access control list (ACL) of the OSS bucket, which ActionTrail logs to, shall prevent public access to the ActionTrail logs.", + "RationaleStatement": "Allowing public access to ActionTrail log content may aid an adversary in identifying weaknesses in the affected account's use or configuration.", + "ImpactStatement": "", + "RemediationProcedure": "Perform the following to remove any public access that has been granted to the bucket via an ACL: Using the Management Console: 1. Logon to OSS Console. 2. Right on the bucket and click Basic Settings. 3. In the Access Control List pane, click the Configure. 4. The Bucket ACL tab shows three kind of grants. Like Private, Public Read, Public Read/Write. 5. Ensure Private be set to the bucket. 6. Click Save to save the ACL.", + "AuditProcedure": "Perform the following to determine if any public access is granted to an OSS bucket via an ACL: Using the Management Console: 1. Logon to ActionTrail Console. 2. In the API activity history pane on the left, click Trails. 3. In the Trails pane, note the bucket names in the OSS bucket column. 4. Log on to OSS Console. 5. For each bucket noted in step 3, click on the bucket and click Basic Settings. 6. In the Access Control List pane, click the Configure. 7. The Bucket ACL tab shows three kind of grants, Private Public Read, Public Read/Write. 8. Ensure Private be set to the bucket. Using CLI: 1. Get the name of the OSS bucket that ActionTrail is logging to: aliyuncli actiontrail DescribeTrails 2. Ensure the Bucket ACL is to be set private: ossutil set-acl oss:// private -b", + "AdditionalInformation": "", + "References": "https://help.aliyun.com/document_detail/31954.html", + "DefaultValue": "By default, OSS buckets are not publicly accessible." + } + ], + "Checks": [ + "actiontrail_oss_bucket_not_publicly_accessible" + ] + }, + { + "Id": "2.3", + "Description": "Ensure audit logs for multiple cloud resources are integrated with Log Service", + "Attributes": [ + { + "Section": "2. Logging and Monitoring", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "Log Service provides functions of log collection and analysis in real time across multiple cloud resources under the authorized resource owners. This enable the large-scale corporate for security governance over all resources owned by multiple accounts by integrating the log from different sources and monitoring. For example, Log Service supports the integration to collect logs from the following sources: • ActionTrail is a cloud service that records API calls made in a given Alibaba Cloud account. • ApsaraDB RDS and DRDS audit records all data manipulation language (DML) and data definition language (DDL) operations through network protocol analysis and only consumes a small amount of CPU resources. The Trial Edition of SQL Explorer retains SQL log data generated within up to one day free of charge. • Object Storage Service (OSS) support recording every changes to its resources including bucket, ACL, replications, and files, as well as file access logs. • The access log feature of SLB can be applied to HTTP- and HTTPS-based Layer 7 load balancing. Access logs can contain about 30 fields such as the time when a request is received, the IP address of the client, processing latency, request URI, backend server (ECS instance) address, and returned status code. As an Internet access point, SLB needs to distribute a large number of access requests. • Alibaba Cloud API Gateway provides API hosting service to facilitate micro- service aggregation, frontend and backend isolation, and system integration. Each API request corresponds to an access record, which contains information such as the IP address of the API caller, requested URL, response latency, returned status code, and number of bytes for each request and response. With the preceding information, you can understand the operating status of your web services. • NAS audit and access log support to record each request to Network File System (NFS) file system including file changes and access, details of the access request, such as the operation type, target object, and response status of the current user. Log Service also provides rich functions such as real-time query and analysis, and dashboard presentation for this part of logs.", + "RationaleStatement": "Sending the audit logs to Log Service will facilitate real-time and historic activity logging based on user, API, resource, and IP address, and provides benefits to collect logs under multiple accounts, store logs centrally, establish alarms and notifications for anomalous or sensitivity account activity, and extend the default log retention period to 180 days.", + "ImpactStatement": "RDS Audit Log integration requires to enable SQL Explorer feature on RDS side, which may introduce extra charge.", + "RemediationProcedure": "Perform the following to ensure the logs are integrated with Log Services: 1. Logon to SLS Console. 2. Click Log Service Audit Service in the navigation pane. 3. Go to Access to Cloud Products > Global Configuration page. a. Select a location of project for logs. b. Check the appropriate product logging selection, such as Action Trail, RDS SQL Audit Logs, OSS Access Logs, SLB Access Log, NAS Access Log, API Gateway Access log and configure a proper storage period (in days). c. Click Save to save the changes. 4. Go to Multi-Account Configurations > Global Configuration page. a. Modify it to input the other resource owner account ID. b. Click Save to save the changes. 5. Go to Access to Cloud Products > Status Dashboard page to ensure the Status is Green.", + "AuditProcedure": "Perform the following to ensure the logs are integrated with Log Services: 1. Logon to SLS Console. 2. Click Log Service Audit Service in the navigation pane to go to the Log Service Audit Service page. 3. Ensure the Action Trail, RDS SQL Audit Logs, OSS Access Logs, SLB Access Log, NAS Access Log, API Gateway Access log are Enabled under the Access to Cloud Products > Global Configuration page. 4. Ensure all resource owners account are tracked under the Multi-Account Configurations > Global Configuration page. 5. Ensure the Status is Green under the Access to Cloud Products > Status Dashboard page.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/84920.htm", + "DefaultValue": "Not enabled." + } + ], + "Checks": [] + }, + { + "Id": "2.4", + "Description": "Ensure Log Service is enabled for Container Service for Kubernetes", + "Attributes": [ + { + "Section": "2. Logging and Monitoring", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "Log Service shall be connected with Kubernetes clusters of Alibaba Cloud Container Service to collect the audit log for central monitoring and analysis. You can simply enable Log Service when creating a cluster for log collection.", + "RationaleStatement": "By enabling Log Service Audit Log function to integrate audit log of Kubernetes, it is possible to capture all events on container to improve the security of serverless cluster. Central log collection and monitoring allows access to all log information on one dashboard which can be useful in security and incident response workflows.", + "ImpactStatement": "", + "RemediationProcedure": "Perform the following ensure the Log Service for Kubernetes clusters is enabled: 1. Logon to ACK Console. 2. Click Clusters in the left-side navigation pane and click Create Kubernetes Cluster in the upper-right corner. 3. Scroll to the bottom of the page and select the Using Log Service check box. The log plug-in will be installed in the newly created Kubernetes cluster. 4. When you select the Using Log Service check box, project options are displayed. A project is the unit in Log Service to manage logs. 5. After you complete the configuration, click Create in the upper-right corner. 6. In the displayed dialog box, click OK.", + "AuditProcedure": "Perform the following to ensure the Kubernetes logs are integrated with Log Services: 1. Logon to ACK Console. 2. Click Cluster > Clusters in the left-side navigation pane and select a cluster to click Action > Manage. 3. Ensure the Cluster Auditing page is available.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/87540.htm https://www.alibabacloud.com/help/doc-detail/87540.htm", + "DefaultValue": "Logging is disabled." + } + ], + "Checks": [] + }, + { + "Id": "2.5", + "Description": "Ensure virtual network flow log service is enabled", + "Attributes": [ + { + "Section": "2. Logging and Monitoring", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "The flow log can be used to capture the traffic of an Elastic Network Interface (ENI), Virtual Private Cloud (VPC) or Virtual Switch (VSwitch). The flow log of a VPC or VSwitch shall be integrated with Log Service to capture the traffic of all ENIs in the VPC or VSwtich including the ENIs created after the flow log function is enabled. The traffic data captured by flow logs is stored in Log Service for real-time monitoring and analysis. A capture window is about 10 minutes, during which the traffic data is aggregated and then released to flow log record.", + "RationaleStatement": "By integrating virtual network flow log to Log Service, the inbound and outbound traffic over the ENI in your VPC is captured for monitoring and analysis which can be useful in monitoring network traffic and access control rules as well as network trouble shooting.", + "ImpactStatement": "", + "RemediationProcedure": "Perform the following ensure the virtual network flow log is enabled: 1. Logon to VPC console. 2. In the left-side navigation pane, click FlowLog. 3. Select the region to which the flow log is to be created. 4. On the FlowLog page, click Create FlowLog. 5. On the Create FlowLog page, set the required parameters by following the instruction, and then click OK.", + "AuditProcedure": "Perform the following ensure the virtual network flow log is enabled: 1. Logon to VPC console. 2. In the left-side navigation pane, click FlowLog. 3. Select the region to which the target flow log belongs. 4. On the FlowLog page, ensure the target flow log and logstore is configured.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/90628.htm", + "DefaultValue": "Logging is disabled." + } + ], + "Checks": [] + }, + { + "Id": "2.6", + "Description": "Ensure Anti-DDoS access and security log service is enabled", + "Attributes": [ + { + "Section": "2. Logging and Monitoring", + "Profile": "Level 2", + "AssessmentStatus": "Manual", + "Description": "Alibaba Cloud Anti-DDoS Pro supports integration with Log Service for website access log (including HTTP flood attack logs) to enable the real-time analysis and reporting center features. The log collected can be monitored on a central dashboard on Log Service.", + "RationaleStatement": "By integrating Anti-DDoS access and security log to Log Service, the website access log and flood attack logs can be collected and monitored to enable real-time query and improve the network security.", + "ImpactStatement": "Extra charge will incur.", + "RemediationProcedure": "Perform the following ensure the Anti-DDoS access and security log is enabled: 1. Logon to Anti-DDoS Pro Console, and go to the Log > Full Log page. 2. Select the specific website for which you want to enable the Full Log service and click to turn on the Status switch.", + "AuditProcedure": "Perform the following ensure the Anti-DDoS access and security log is enabled: 1. Logon to Anti-DDoS Pro Console, and go to the Log > Full Log page. 2. Select the specific website. 3. Ensure the Log Collection is turned on. 4. Ensure the log volume usage indicator is sufficient for log storage.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/85007.htm", + "DefaultValue": "Logging is disabled." + } + ], + "Checks": [] + }, + { + "Id": "2.7", + "Description": "Ensure Web Application Firewall access and security log service is enabled", + "Attributes": [ + { + "Section": "2. Logging and Monitoring", + "Profile": "Level 2", + "AssessmentStatus": "Manual", + "Description": "Log Service collects log entries that record visits to and attacks on websites that are protected by Alibaba Cloud Web Application Firewall (WAF), and supports real-time log query and analysis. The query results are centrally displayed in dashboards.", + "RationaleStatement": "The WAF access and security log shall be enabled to enable timely analytical investigation on visits to and attacks on your websites and help security engineers to develop protection strategies.", + "ImpactStatement": "Extra charge will incur by enabling the log.", + "RemediationProcedure": "Perform the following ensure the Anti-DDoS access and security log is enabled: 1. Logon to WAF Console. 2. Choose App Market > App Management. 3. Select the region where your WAF instance is located. 4. Click Upgrade in Real-time Log Query and Analysis Service. 5. Enable Log Service. 6. Select the log storage period and the log storage size, and click Buy Now. 7. Return to the WAF Console and choose App Market > App Management, and then click Authorize in Real-time Log Query and Analysis Service. 8. Click Agree to authorize WAF to write log entries to your exclusive logstore. 9. Return to the WAF Console and choose App Market > App Management and then, click Configure in Real-time Log Query and Analysis Service. 10. On the Log Service page, select the domain name of your website that is protected by WAF, and turn on the Status switch on the right to enable WAF Log Service. These log entries can be queried and analyzed in real time.", + "AuditProcedure": "Perform the following ensure the WAF access and security log is enabled: 1. Logon to WAF Console. 2. Choose App Market > App Management. 3. Click Configure in Real-time Log Query and Analysis Service. 4. On Log Service page, select the specific domain name of your website. 5. Ensure the Status switch on the right is turned on. 6. Ensure the log volume usage indicator is sufficient for log storage.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/95267", + "DefaultValue": "Logging is disabled." + } + ], + "Checks": [] + }, + { + "Id": "2.8", + "Description": "Ensure Cloud Firewall access and security log analysis is enabled", + "Attributes": [ + { + "Section": "2. Logging and Monitoring", + "Profile": "Level 2", + "AssessmentStatus": "Manual", + "Description": "Log Service collects log entries of internet traffic that are protected by Cloud Firewall, and supports real-time log query and analysis. The query results are centrally displayed in dashboards.", + "RationaleStatement": "The Cloud Firewall log shall be enabled with the Log Service to collect and store real- time log of both inbound and outbound traffic for timely analysis, reports, alarms and downstream computing interconnection and provides the detailed results displaying centrally on dashboard to monitor and improve network security.", + "ImpactStatement": "Extra charge will incur by enabling the log.", + "RemediationProcedure": "Perform the following ensure the Cloud Firewall access and security log is enabled: 1. Logon to Cloud Firewall Console. 2. In the left-side navigation pane, select Advanced Features > Log Analysis. 3. Click Active Now on the Log Analysis page. 4. Select your log storage capacity, and then click Pay to complete the payment. 5. Go back to Log Analysis page on Cloud Firewall console. 6. Click the Status on the right side to enable the Log Analysis service.", + "AuditProcedure": "Perform the following ensure the Cloud Firewall access and security log is enabled: 1. Logon to Cloud Firewall Console. 2. In the left-side navigation pane, select Advanced Features > Log Analysis. 3. Ensure the Status switch on the right side is enabled. 4. Ensure the log volume usage indicator is not exhausted.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/113184.htm", + "DefaultValue": "Logging is disabled." + } + ], + "Checks": [] + }, + { + "Id": "2.9", + "Description": "Ensure Security Center Network, Host and Security log analysis is enabled", + "Attributes": [ + { + "Section": "2. Logging and Monitoring", + "Profile": "Level 2", + "AssessmentStatus": "Manual", + "Description": "Log Service collects log entries of Security Center for security logs, network logs, and host logs, with 14 subtypes, including 1. Security logs a. Vulnerability logs b. Baseline logs c. Security alerting logs 2. Security logs a. Vulnerability logs b. Baseline logs c. Security alerting logs 3. Network logs a. DNS logs b. Local DNS logs c. Network session logs d. Web logs 4. Server logs a. Process initiation logs b. Network connection logs c. System logon logs d. Brute-force cracking logs e. Process snapshots f. Account snapshots g. Port listening snapshots The Log Service supports real-time log query and analysis over the logs mentioned above. The query results are centrally displayed in dashboards.", + "RationaleStatement": "The Security Center log shall be enabled to collect and store real-time security log, network log and server log to better protect your assets in real time.", + "ImpactStatement": "Extra charge will incur by enabling the log.", + "RemediationProcedure": "Perform the following ensure the Cloud Firewall access and security log is enabled: 1. Logon to Security Center Console. 2. In the left-side navigation pane, select Investigation > Log Analysis to enter the Activate Log Analysis page. 3. Click Active Now on the Activate log Analysis page. 4. On the Purchase page, check Full Log and configure some other settings as needed. 5. Click Purchase Now. 6. In the Activate log Analysis click Activate log Analysis to complete the authorization. 7. In the log type menu, check the log types to enable the log collection.", + "AuditProcedure": "Perform the following ensure the Cloud Firewall access and security log is enabled: 1. Logon to Security Center Console. 2. In the left-side navigation pane, select Investigation > Log Analysis to enter the Activate Log Analysis page. 3. In the Activate Log Analysis page, ensure the switch for the specific log type are turned on. 4. Ensure the log volume usage indicator is not exhausted.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/93065.htm https://www.alibabacloud.com/help/doc-detail/93117.htm", + "DefaultValue": "Logging is disabled." + } + ], + "Checks": [] + }, + { + "Id": "2.10", + "Description": "Ensure log monitoring and alerts are set up for RAM Role changes", + "Attributes": [ + { + "Section": "2. Logging and Monitoring", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "It is recommended that a query and alarm should be established for RAM Role creation, deletion and updating activities.", + "RationaleStatement": "Alibaba Cloud Resource Access Management (RAM) provides predefined roles that give granular access to specific resources and prevent unwanted access to other resources. Log Service provides ability to create custom monitoring query: monitoring role creation, deletion and updating activities will help in identifying any potential malicious actions at early stage.", + "ImpactStatement": "", + "RemediationProcedure": "Perform the following to ensure the log monitoring and alerts are set up for RAM Role Changes: 1. Logon to SLS Console. 2. Click Log Service Audit Service in the navigation pane. 3. Go to Access to Cloud Products > Global Configuration page. a. Select a location of project for logs. b. Check the Action Trail and configure a proper days. c. Click Save to save the changes. 4. Go to Access to Cloud Products > Global Configurations click Central Project. 5. Select Log Management > Actiontrail Log. 6. In the search/analytics console, input below query (event.serviceName: ResourceManager or event.serviceName: Ram) and (event.eventName: CreatePolicy or event.eventName: DeletePolicy or event.eventName: CreatePolicyVersion or event.eventName: UpdatePolicyVersion or event.eventName: SetDefaultPolicyVersion or event.eventName: DeletePolicyVersion) | select count(1) as c 7. Create a dashboard and set alert for the query result.", + "AuditProcedure": "Perform the following to ensure the log monitoring and alerts are set up for RAM Role Changes: 1. Logon to SLS Console. 2. Click Log Service Audit Service in the navigation pane to go to the Log Service Audit Service page. 3. Ensure the Action Trail are Enabled under the Access to Cloud Products > Global Configuration page, and click Central Project. 4. Select Alerts. 5. Ensure below alert rule has been enabled and saved in the target actiontrail_log (event.serviceName: ResourceManager or event.serviceName: Ram) and (event.eventName: CreatePolicy or event.eventName: DeletePolicy or event.eventName: CreatePolicyVersion or event.eventName: UpdatePolicyVersion or event.eventName: SetDefaultPolicyVersion or event.eventName: DeletePolicyVersion) | select count(1) as c", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/91784.htm", + "DefaultValue": "The monitoring dashboard and alert is not set by default" + } + ], + "Checks": [ + "sls_ram_role_changes_alert_enabled" + ] + }, + { + "Id": "2.11", + "Description": "Ensure log monitoring and alerts are set up for Cloud Firewall changes", + "Attributes": [ + { + "Section": "2. Logging and Monitoring", + "Profile": "Level 2", + "AssessmentStatus": "Manual", + "Description": "It is recommended that a metric filter and alarm be established for Cloud Firewall rule changes.", + "RationaleStatement": "Monitoring for Create or Update firewall rule events gives insight network access changes and may reduce the time it takes to detect suspicious activity.", + "ImpactStatement": "", + "RemediationProcedure": "Perform the following to ensure the log monitoring and alerts are set up Cloud Firewall Changes: 1. Logon to SLS Console. 2. Click Log Service Audit Service in the navigation pane. 3. Go to Access to Cloud Products > Global Configuration page. a. Select a location of project for logs. b. Check the Action Trail and configure a proper days. c. Click Save to save the changes. 4. Go to Access to Cloud Products > Global Configurations click Central Project. 5. Select Log Management > Actiontrail Log. 6. In the search/analytics console, input below query event.serviceName: Cloudfw and (event.eventName: CreateVpcFirewallControlPolicy or event.eventName: DeleteVpcFirewallControlPolicy or event.eventName: ModifyVpcFirewallControlPolicy) | select count(1) as c 7. Create a dashboard and set alert for the query result.", + "AuditProcedure": "Perform the following to ensure the log monitoring and alerts are set up for Cloud Firewall Changes: 1. Logon to SLS Console. 2. Click Log Service Audit Service in the navigation pane to go to the Log Service Audit Service page. 3. Ensure the Action Trail are Enabled under the Access to Cloud Products > Global Configuration page, and click Central Project. 4. Select Alerts. 5. Ensure below alert rule has been enabled and saved in the target actiontrail_log event.serviceName: Cloudfw and (event.eventName: CreateVpcFirewallControlPolicy or event.eventName: DeleteVpcFirewallControlPolicy or event.eventName: ModifyVpcFirewallControlPolicy) | select count(1) as c", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/en/doc-detail/91784.htm", + "DefaultValue": "The monitoring dashboard and alert is not set by default" + } + ], + "Checks": [ + "sls_cloud_firewall_changes_alert_enabled" + ] + }, + { + "Id": "2.12", + "Description": "Ensure log monitoring and alerts are set up for VPC network route changes", + "Attributes": [ + { + "Section": "2. Logging and Monitoring", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "It is recommended that a metric filter and alarm be established for VPC network route changes.", + "RationaleStatement": "Routes define the paths network traffic takes from a VM instance to another destinations. The other destination can be inside your VPC network (such as another VM) or outside of it. Every route consists of a destination and a next hop. Traffic whose destination IP is within the destination range is sent to the next hop for delivery. Monitoring changes to route tables will help ensure that all VPC traffic flows through an expected path.", + "ImpactStatement": "", + "RemediationProcedure": "Perform the following to ensure the log monitoring and alerts are set up for VPC network route changes: 1. Logon to SLS Console. 2. Click Log Service Audit Service in the navigation pane. 3. Go to Access to Cloud Products > Global Configuration page. a. Select a location of project for logs. b. Check the Action Trail and configure a proper days. c. Click Save to save the changes. 4. Go to Access to Cloud Products > Global Configurations click Central Project. 5. Select Log Management > Actiontrail Log. 6. In the search/analytics console, input below query (event.serviceName: Ecs or event.serviceName: Vpc) and (event.eventName: CreateRouteEntry or event.eventName: DeleteRouteEntry or event.eventName: ModifyRouteEntry or event.eventName: AssociateRouteTable or event.eventName: UnassociateRouteTable) | select count(1) as c 7. Create a dashboard and set alert for the query result.", + "AuditProcedure": "Perform the following steps to ensure log monitoring and alerts are set for VPC network route changes. 1. Logon to SLS Console. 2. Click Log Service Audit Service in the navigation pane to go to the Log Service Audit Service page. 3. Ensure the Action Trail are Enabled under the Access to Cloud Products > Global Configuration page, and click Central Project. 4. Select Alerts. 5. Ensure below alert rule has been enabled and saved in the target actiontrail_log (event.serviceName: Ecs or event.serviceName: Vpc) and (event.eventName: CreateRouteEntry or event.eventName: DeleteRouteEntry or event.eventName: ModifyRouteEntry or event.eventName: AssociateRouteTable or event.eventName: UnassociateRouteTable) | select count(1) as c", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/en/doc-detail/91784.htm", + "DefaultValue": "The monitoring dashboard and alert is not set by default." + } + ], + "Checks": [ + "sls_vpc_network_route_changes_alert_enabled" + ] + }, + { + "Id": "2.13", + "Description": "Ensure log monitoring and alerts are set up for VPC changes", + "Attributes": [ + { + "Section": "2. Logging and Monitoring", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "It is recommended that a log search/analysis query and alarm be established for VPC changes.", + "RationaleStatement": "Monitoring changes to VPC will help ensure VPC traffic flow is not getting impacted.", + "ImpactStatement": "", + "RemediationProcedure": "Perform the following to ensure the log monitoring and alerts are set up for VPC changes: 1. Logon to SLS Console. 2. Click Log Service Audit Service in the navigation pane. 3. Go to Access to Cloud Products > Global Configuration page. a. Select a location of project for logs. b. Check the Action Trail and configure a proper days. c. Click Save to save the changes. 4. Go to Access to Cloud Products > Global Configurations click Central Project. 5. Select Log Management > Actiontrail Log. 6. In the search/analytics console, input below query (event.serviceName: Ecs or event.serviceName: Vpc) and (event.eventName: CreateVpc or event.eventName: DeleteVpc or event.eventName: DisableVpcClassicLink or event.eventName: EnableVpcClassicLink or event.eventName: DeletionProtection or event.eventName: AssociateVpcCidrBlock or event.eventName: UnassociateVpcCidrBlock or event.eventName: RevokeInstanceFromCen or event.eventName: CreateVSwitch or event.eventName: DeleteVSwitch or event.eventName: CreateVSwitch) | select count(1) as c 7. Create a dashboard and set alert for the query result.", + "AuditProcedure": "Perform the following steps to ensure log monitoring and alerts are set for VPC changes. 1. Logon to the SLS Console. 2. Click Log Service Audit Service in the navigation pane to go to the Log Service Audit Service page. 3. Ensure the Action Trail are Enabled under the Access to Cloud Products > Global Configuration page, and click Central Project. 4. Select Alerts. 5. Ensure below alert rule has been enabled and saved in the target actiontrail_log (event.serviceName: Ecs or event.serviceName: Vpc) and (event.eventName: CreateVpc or event.eventName: DeleteVpc or event.eventName: DisableVpcClassicLink or event.eventName: EnableVpcClassicLink or event.eventName: DeletionProtection or event.eventName: AssociateVpcCidrBlock or event.eventName: UnassociateVpcCidrBlock or event.eventName: RevokeInstanceFromCen or event.eventName: CreateVSwitch or event.eventName: DeleteVSwitch or event.eventName: CreateVSwitch) | select count(1) as c", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/en/doc-detail/91784.htm", + "DefaultValue": "The monitoring dashboard and alert is not set by default." + } + ], + "Checks": [ + "sls_vpc_changes_alert_enabled" + ] + }, + { + "Id": "2.14", + "Description": "Ensure log monitoring and alerts are set up for OSS permission changes", + "Attributes": [ + { + "Section": "2. Logging and Monitoring", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "It is recommended that a metric filter and alarm be established for OSS Bucket RAM changes.", + "RationaleStatement": "Monitoring changes to OSS permissions may reduce time to detect and correct permissions on sensitive OSS bucket and objects inside the bucket.", + "ImpactStatement": "", + "RemediationProcedure": "Perform the following to ensure the log monitoring and alerts are set up for OSS permission changes: 1. Logon to SLS Console. 2. Click Log Service Audit Service in the navigation pane. 3. Go to Access to Cloud Products > Global Configuration page. a. Select a location of project for logs. b. Check the OSS and configure a proper days. c. Click Save to save the changes. 4. Go to Access to Cloud Products > Global Configurations click Central Project. 5. Select Log Management > OSS Log. 6. In the search/analytics console, input below query (operation: PutBucket and request_uri: acl) or operation: PutObjectAcl| select bucket, count (1) as c group by bucket 7. Create a dashboard and set alert for the query result.", + "AuditProcedure": "Perform the following steps to ensure log monitoring and alerts are set for OSS permission changes. 1. Logon to SLS Console. 2. Click Log Service Audit Service in the navigation pane to go to the Log Service Audit Service page. 3. Ensure the OSS are Enabled under the Access to Cloud Products > Global Configuration page, and click Central Project. 4. Select Alerts. 5. Ensure below alert rule has been enabled and saved in the target oss_log (operation: PutBucket and request_uri: acl) or operation: PutObjectAcl| select bucket, count (1) as c group by bucket", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/en/doc-detail/91784.htm", + "DefaultValue": "The monitoring dashboard and alert is not set by default." + } + ], + "Checks": [ + "sls_oss_permission_changes_alert_enabled" + ] + }, + { + "Id": "2.15", + "Description": "Ensure log monitoring and alerts are set up for RDS instance configuration changes", + "Attributes": [ + { + "Section": "2. Logging and Monitoring", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "It is recommended that a metric filter and alarm be established for RDS Instance configuration changes.", + "RationaleStatement": "Monitoring changes to RDS Instance configuration changes may reduce time to detect and correct misconfigurations done on database server. Below are the few of configurable Options which may impact security posture of a RDS Instance: 1. Enable auto backups and high availability: Misconfiguration may adversely impact Business continuity, Disaster Recovery and High Availability. 2. Authorize networks : Misconfiguration may increase exposure to the untrusted networks.", + "ImpactStatement": "", + "RemediationProcedure": "Perform the following to ensure the log monitoring and alerts are set up for RDS instance configuration changes: 1. Logon to SLS Console. 2. Click Log Service Audit Service in the navigation pane. 3. Go to Access to Cloud Products > Global Configuration page. a. Select a location of project for logs. b. Check the Action Trail and configure a proper days. c. Click Save to save the changes. 4. Go to Access to Cloud Products > Global Configurations click Central Project. 5. Select Log Management > Actiontrail Log. 6. In the search/analytics console, input below query event.serviceName: rds and (event.eventName: ModifyHASwitchConfig or event.eventName: ModifyDBInstanceHAConfig or event.eventName: SwitchDBInstanceHA or event.eventName: ModifyDBInstanceSpec or event.eventName: MigrateSecurityIPMode or event.eventName: ModifySecurityIps or event.eventName: ModifyDBInstanceSSL or event.eventName: MigrateToOtherZone or event.eventName: UpgradeDBInstanceKernelVersion or event.eventName: UpgradeDBInstanceEngineVersion or event.eventName: ModifyDBInstanceMaintainTime or event.eventName: ModifyDBInstanceAutoUpgradeMinorVersion or event.eventName: AllocateInstancePublicConnection or event.eventName: ModifyDBInstanceConnectionString or event.eventName: ModifyDBInstanceNetworkExpireTime or event.eventName: ReleaseInstancePublicConnection or event.eventName: SwitchDBInstanceNetType or event.eventName: ModifyDBInstanceNetworkType or event.eventName: ModifyDBInstanceSSL or event.eventName: ModifyDTCSecurityIpHostsForSQLServer or event.eventName: ModifySecurityGroupConfiguration or event.eventName: CreateBackup or event.eventName: ModifyBackupPolicy or event.eventName: DeleteBackup or event.eventName: CreateDdrInstance or event.eventName: ModifyInstanceCrossBackupPolicy) | select count(1) as cnt 7. Create a dashboard and set alert for the query result.", + "AuditProcedure": "Perform the following steps to ensure log monitoring and alerts are set for SQL instance configuration changes. 1. Logon to SLS Console. 2. Click Log Service Audit Service in the navigation pane to go to the Log Service Audit Service page. 3. Ensure the Action Trail are Enabled under the Access to Cloud Products > Global Configuration page, and click Central Project. 4. Select Alerts. 5. Ensure below alert rule has been enabled and saved in the target actiontrail_log event.serviceName: rds and (event.eventName: ModifyHASwitchConfig or event.eventName: ModifyDBInstanceHAConfig or event.eventName: SwitchDBInstanceHA or event.eventName: ModifyDBInstanceSpec or event.eventName: MigrateSecurityIPMode or event.eventName: ModifySecurityIps or event.eventName: ModifyDBInstanceSSL or event.eventName: MigrateToOtherZone or event.eventName: UpgradeDBInstanceKernelVersion or event.eventName: UpgradeDBInstanceEngineVersion or event.eventName: ModifyDBInstanceMaintainTime or event.eventName: ModifyDBInstanceAutoUpgradeMinorVersion or event.eventName: AllocateInstancePublicConnection or event.eventName: ModifyDBInstanceConnectionString or event.eventName: ModifyDBInstanceNetworkExpireTime or event.eventName: ReleaseInstancePublicConnection or event.eventName: SwitchDBInstanceNetType or event.eventName: ModifyDBInstanceNetworkType or event.eventName: ModifyDBInstanceSSL or event.eventName: ModifyDTCSecurityIpHostsForSQLServer or event.eventName: ModifySecurityGroupConfiguration or event.eventName: CreateBackup or event.eventName: ModifyBackupPolicy or event.eventName: DeleteBackup or event.eventName: CreateDdrInstance or event.eventName: ModifyInstanceCrossBackupPolicy) | select count(1) as cnt", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/en/doc-detail/91784.htm", + "DefaultValue": "The monitoring dashboard and alert is not set by default." + } + ], + "Checks": [ + "sls_rds_instance_configuration_changes_alert_enabled" + ] + }, + { + "Id": "2.16", + "Description": "Ensure a log monitoring and alerts are set up for unauthorized API calls", + "Attributes": [ + { + "Section": "2. Logging and Monitoring", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "Real-time monitoring of API calls can be achieved by directing ActionTrail Logs to LogService and establishing corresponding query and alarms. It is recommended that a query and alarm be established for unauthorized API calls.", + "RationaleStatement": "Monitoring unauthorized API calls will help reveal application errors and may reduce time to detect malicious activity.", + "ImpactStatement": "", + "RemediationProcedure": "Perform the following to ensure the log monitoring and alerts are set up for unauthorized API calls: 1. Logon to SLS Console. 2. Click Log Service Audit Service in the navigation pane. 3. Go to Access to Cloud Products > Global Configuration page. a. Select a location of project for logs. b. Check the Action Trail and configure a proper days. c. Click Save to save the changes. 4. Go to Access to Cloud Products > Global Configurations click Central Project. 5. Select Log Management > Actiontrail Log. 6. In the search/analytics console, input below query event.eventType: ApiCall and (event.errorCode: NoPermission or event.errorCode: NoPermission.* or event.errorCode: Forbidden or event.errorCode: Forbbiden or event.errorCode: Forbidden.* or event.errorCode: InvalidAccessKeyId or event.errorCode: InvalidAccessKeyId.* or event.errorCode: InvalidSecurityToken or event.errorCode: InvalidSecurityToken.* or event.errorCode: SignatureDoesNotMatch or event.errorCode: InvalidAuthorization or event.errorCode: AccessForbidden or event.errorCode: NotAuthorized) | select count(1) as cnt 7. Create a dashboard and set alert for the query result.", + "AuditProcedure": "Perform the following steps to ensure log monitoring and alerts are set for unauthorized API calls. 1. Logon to SLS Console. 2. Click Log Service Audit Service in the navigation pane to go to the Log Service Audit Service page. 3. Ensure the Action Trail are Enabled under the Access to Cloud Products > Global Configuration page, and click Central Project. 4. Select Alerts. 5. Ensure below alert rule has been enabled and saved in the target actiontrail_log event.eventType: ApiCall and (event.errorCode: NoPermission or event.errorCode: NoPermission.* or event.errorCode: Forbidden or event.errorCode: Forbbiden or event.errorCode: Forbidden.* or event.errorCode: InvalidAccessKeyId or event.errorCode: InvalidAccessKeyId.* or event.errorCode: InvalidSecurityToken or event.errorCode: InvalidSecurityToken.* or event.errorCode: SignatureDoesNotMatch or event.errorCode: InvalidAuthorization or event.errorCode: AccessForbidden or event.errorCode: “NotAuthorized) | select count(1) as cnt", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/en/doc-detail/91784.htm", + "DefaultValue": "The monitoring dashboard and alert is not set by default." + } + ], + "Checks": [ + "sls_unauthorized_api_calls_alert_enabled" + ] + }, + { + "Id": "2.17", + "Description": "Ensure a log monitoring and alerts are set up for Management Console sign-in without MFA", + "Attributes": [ + { + "Section": "2. Logging and Monitoring", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "Real-time monitoring of API calls can be achieved by directing ActionTrail Logs to Log Service and establishing corresponding query and alarms. It is recommended that a query and alarm be established for console logins that are not protected by multi-factor authentication (MFA).", + "RationaleStatement": "Monitoring for single-factor console logins will increase visibility into accounts that are not protected by MFA.", + "ImpactStatement": "", + "RemediationProcedure": "Perform the following to ensure the log monitoring and alerts are set up for Management Console sign-in without MFA: 1. Logon to SLS Console. 2. Click Log Service Audit Service in the navigation pane. 3. Go to Access to Cloud Products > Global Configuration page. a. Select a location of project for logs. b. Check the Action Trail and configure a proper days. c. Click Save to save the changes. 4. Go to Access to Cloud Products > Global Configurations click Central Project. 5. Select Log Management > Actiontrail Log. 6. In the search/analytics console, input below query event.eventName: ConsoleSignin and addionalEventData.loginAccount: false 7. Create a dashboard and set alert for the query result.", + "AuditProcedure": "Perform the following steps to ensure log monitoring and alerts are set for Management Console sign-in without MFA. 1. Logon to SLS Console. 2. Click Log Service Audit Service in the navigation pane to go to the Log Service Audit Service page. 3. Ensure the Action Trail are Enabled under the Access to Cloud Products > Global Configuration page, and click Central Project. 4. Select Alerts. 5. Ensure below alert rule has been enabled and saved in the target actiontrail_log event.eventName: ConsoleSignin and addionalEventData.loginAccount: false", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/en/doc-detail/91784.htm", + "DefaultValue": "The monitoring dashboard and alert is not set by default." + } + ], + "Checks": [ + "sls_management_console_signin_without_mfa_alert_enabled" + ] + }, + { + "Id": "2.18", + "Description": "Ensure a log monitoring and alerts are set up for usage of root account", + "Attributes": [ + { + "Section": "2. Logging and Monitoring", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "Real-time monitoring of API calls can be achieved by directing ActionTrail Logs to Log Service and establishing corresponding query and alarms. It is recommended that a query and alarm be established for console logins that are not protected by root login attempts.", + "RationaleStatement": "Monitoring for root account logins will provide visibility into the use of a fully privileged account and an opportunity to reduce the use of it.", + "ImpactStatement": "", + "RemediationProcedure": "Perform the following to ensure the log monitoring and alerts are set up for usage of “root” account: 1. Logon to SLS Console. 2. Click Log Service Audit Service in the navigation pane. 3. Go to Access to Cloud Products > Global Configuration page. a. Select a location of project for logs. b. Check the Action Trail and configure a proper days. c. Click Save to save the changes. 4. Go to Access to Cloud Products > Global Configurations click Central Project. 5. Select Log Management > Actiontrail Log. 6. In the search/analytics console, input below query event.eventName: ConsoleSignin and event.userIdentity.type : root-account 7. Create a dashboard and set alert for the query result.", + "AuditProcedure": "Perform the following steps to ensure log monitoring and alerts are set for usage of “root” account. 1. Logon to SLS Console. 2. Click Log Service Audit Service in the navigation pane to go to the Log Service Audit Service page. 3. Ensure the Action Trail are Enabled under the Access to Cloud Products > Global Configuration page, and click Central Project. 4. Select Alerts. 5. Ensure below alert rule has been enabled and saved in the target actiontrail_log event.eventName: ConsoleSignin and event.userIdentity.type : root-account", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/en/doc-detail/91784.htm", + "DefaultValue": "The monitoring dashboard and alert is not set by default." + } + ], + "Checks": [ + "sls_root_account_usage_alert_enabled" + ] + }, + { + "Id": "2.19", + "Description": "Ensure a log monitoring and alerts are set up for Management Console authentication failures", + "Attributes": [ + { + "Section": "2. Logging and Monitoring", + "Profile": "Level 2", + "AssessmentStatus": "Manual", + "Description": "Real-time monitoring of API calls can be achieved by directing ActionTrail Logs to Log Service and establishing corresponding query and alarms. It is recommended that a query and alarm be established for failed console authentication attempts.", + "RationaleStatement": "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.", + "ImpactStatement": "", + "RemediationProcedure": "Perform the following to ensure the log monitoring and alerts are set up for Management Console authentication failures: 1. Logon to SLS Console. 2. Click Log Service Audit Service in the navigation pane. 3. Go to Access to Cloud Products > Global Configuration page. a. Select a location of project for logs. b. Check the Action Trail and configure a proper days. c. Click Save to save the changes. 4. Go to Access to Cloud Products > Global Configurations click Central Project. 5. Select Log Management > Actiontrail Log. 6. In the search/analytics console, input below query event.eventName: ConsoleSignin and event.errorCode : * and not event.errorCode : | select count(1) as cnt 7. Create a dashboard and set alert for the query result.", + "AuditProcedure": "Perform the following steps to ensure log monitoring and alerts are set for Management Console authentication failures. 1. Logon to SLS Console. 2. Click Log Service Audit Service in the navigation pane to go to the Log Service Audit Service page. 3. Ensure the Action Trail are Enabled under the Access to Cloud Products > Global Configuration page, and click Central Project. 4. Select Alerts. 5. Ensure below alert rule has been enabled and saved in the target actiontrail_log event.eventName: ConsoleSignin and event.errorCode : * and not event.errorCode : | select count(1) as cnt", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/en/doc-detail/28810.htm https://www.alibabacloud.com/help/en/doc-detail/91784.htm https://www.alibabacloud.com/help/en/doc-detail/93517.html", + "DefaultValue": "The monitoring dashboard and alert is not set by default." + } + ], + "Checks": [ + "sls_management_console_authentication_failures_alert_enabled" + ] + }, + { + "Id": "2.20", + "Description": "Ensure a log monitoring and alerts are set up for disabling or deletion of customer created CMKs", + "Attributes": [ + { + "Section": "2. Logging and Monitoring", + "Profile": "Level 2", + "AssessmentStatus": "Manual", + "Description": "Real-time monitoring of API calls can be achieved by directing ActionTrail Logs to Log Service and establishing corresponding query and alarms. It is recommended that a query and alarm be established for customer created KMSs which have changed state to disabled or deletion.", + "RationaleStatement": "Data encrypted with disabled or deleted keys will no longer be accessible.", + "ImpactStatement": "", + "RemediationProcedure": "Perform the following to ensure the log monitoring and alerts are set up for disabling or scheduled deletion of customer created CMKs: 1. Logon to SLS Console. 2. Click Log Service Audit Service in the navigation pane. 3. Go to Access to Cloud Products > Global Configuration page. a. Select a location of project for logs. b. Check the Action Trail and configure a proper days. c. Click Save to save the changes. 4. Go to Access to Cloud Products > Global Configurations click Central Project. 5. Select Log Management > Actiontrail Log. 6. In the search/analytics console, input below query. event.serviceName: Kms and (event.eventName: DisableKey or event.eventName: ScheduleKeyDeletion or event.eventName: DeleteKeyMaterial 7. Create a dashboard and set alert for the query result", + "AuditProcedure": "Perform the following steps to ensure log monitoring and alerts are set for disabling or deletion of customer created CMKs. 1. Logon to SLS Console. 2. Click Log Service Audit Service in the navigation pane to go to the Log Service Audit Service page. 3. Ensure the Action Trail are Enabled under the Access to Cloud Products > Global Configuration page, and click Central Project. 4. Select Alerts. 5. Ensure below alert rule has been enabled and saved in the target actiontrail_log event.serviceName: Kms and (event.eventName: DisableKey or event.eventName: ScheduleKeyDeletion or event.eventName: DeleteKeyMaterial", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/en/doc-detail/91784.htm", + "DefaultValue": "The monitoring dashboard and alert is not set by default." + } + ], + "Checks": [ + "sls_customer_created_cmk_changes_alert_enabled" + ] + }, + { + "Id": "2.21", + "Description": "Ensure a log monitoring and alerts are set up for OSS bucket policy changes", + "Attributes": [ + { + "Section": "2. Logging and Monitoring", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "Real-time monitoring of API calls can be achieved by directing ActionTrail Logs to Log Service and establishing corresponding query and alarms. It is recommended that a query and alarm be established for changes to OSS bucket policies.", + "RationaleStatement": "Monitoring changes to OSS bucket policies may reduce time to detect and correct permissive policies on sensitive OSS buckets.", + "ImpactStatement": "", + "RemediationProcedure": "Perform the following to ensure the log monitoring and alerts are set up for OSS bucket policy changes. 1. Logon to SLS Console. 2. Click Log Service Audit Service in the navigation pane. 3. Go to Access to Cloud Products > Global Configuration page. a. Select a location of project for logs. b. Check the Action Trail and configure a proper days. c. Click Save to save the changes. 4. Go to Access to Cloud Products > Global Configurations click Central Project. 5. Select Log Management > Actiontrail Log. 6. In the search/analytics console, input below query. event.eventName: PutBucketLifecycle or event.eventName: PutBucketPolicy or event.eventName: PutBucketCors or event.eventName: PutBucketEncryption or event.eventName: PutBucketReplication or event.eventName: DeleteBucketPolicy or event.eventName: DeleteBucketCors or event.eventName: DeleteBucketLifecycle or event.eventName: DeleteBucketEncryption or event.eventName: DeleteBucketReplication) | select bucket, count(1) as cnt 7. Create a dashboard and set alert for the query result.", + "AuditProcedure": "Perform the following steps to ensure log monitoring and alerts are set for OSS bucket policy changes. 1. Logon to SLS Console. 2. Click Log Service Audit Service in the navigation pane to go to the Log Service Audit Service page. 3. Ensure the Action Trail are Enabled under the Access to Cloud Products > Global Configuration page, and click Central Project. 4. Select Alerts. 5. Ensure below alert rule has been enabled and saved in the target actiontrail_log event.eventName: PutBucketLifecycle or event.eventName: PutBucketPolicy or event.eventName: PutBucketCors or event.eventName: PutBucketEncryption or event.eventName: PutBucketReplication or event.eventName: DeleteBucketPolicy or event.eventName: DeleteBucketCors or event.eventName: DeleteBucketLifecycle or event.eventName: DeleteBucketEncryption or event.eventName: DeleteBucketReplication) | select bucket, count(1) as cnt", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/en/doc-detail/91784.htm", + "DefaultValue": "The monitoring dashboard and alert is not set by default." + } + ], + "Checks": [ + "sls_oss_bucket_policy_changes_alert_enabled" + ] + }, + { + "Id": "2.22", + "Description": "Ensure a log monitoring and alerts are set up for security group changes", + "Attributes": [ + { + "Section": "2. Logging and Monitoring", + "Profile": "Level 2", + "AssessmentStatus": "Manual", + "Description": "Real-time monitoring of API calls can be achieved by directing ActionTrail Logs to Log Service and establishing corresponding query and alarms. 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 changes to Security Groups.", + "RationaleStatement": "Monitoring changes to security group will help ensure that resources and services are not unintentionally exposed.", + "ImpactStatement": "", + "RemediationProcedure": "Perform the following to ensure the log monitoring and alerts are set up for security group changes。 1. Logon to SLS Console. 2. Click Log Service Audit Service in the navigation pane. 3. Go to Access to Cloud Products > Global Configuration page. a. Select a location of project for logs. b. Check the Action Trail and configure a proper days. c. Click Save to save the changes. 4. Go to Access to Cloud Products > Global Configurations click Central Project. 5. Select Log Management > Actiontrail Log. 6. In the search/analytics console, input below query. (event_name: CreateSecurityGroup or event_name: AuthorizeSecurityGroup or event_name: AuthorizeSecurityGroupEgress or event_name: RevokeSecurityGroup or event_name: RevokeSecurityGroupEgress or event_name: JoinSecurityGroup or event_name: LeaveSecurityGroup or event_name: DeleteSecurityGroup or event_name: ModifySecurityGroupPolicy) | select count(1) as cnt 7. Create a dashboard and set alert for the query result.", + "AuditProcedure": "Perform the following steps to ensure log monitoring and alerts are set for security group changes. 1. Logon to SLS Console. 2. Click Log Service Audit Service in the navigation pane to go to the Log Service Audit Service page. 3. Ensure the Action Trail are Enabled under the Access to Cloud Products > Global Configuration page, and click Central Project. 4. Select Alerts. 5. Ensure below alert rule has been enabled and saved in the target actiontrail_log (event_name: CreateSecurityGroup or event_name: AuthorizeSecurityGroup or event_name: AuthorizeSecurityGroupEgress or event_name: RevokeSecurityGroup or event_name: RevokeSecurityGroupEgress or event_name: JoinSecurityGroup or event_name: LeaveSecurityGroup or event_name: DeleteSecurityGroup or event_name: ModifySecurityGroupPolicy) | select count(1) as cnt", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/en/doc-detail/91784.htm", + "DefaultValue": "The monitoring dashboard and alert is not set by default." + } + ], + "Checks": [ + "sls_security_group_changes_alert_enabled" + ] + }, + { + "Id": "2.23", + "Description": "Ensure that Logstore data retention period is set 365 days or greater", + "Attributes": [ + { + "Section": "2. Logging and Monitoring", + "Profile": "Level 2", + "AssessmentStatus": "Manual", + "Description": "Ensure Activity Log Retention is set for 365 days or greater", + "RationaleStatement": "Logstore life cycle controls how your activity log is exported and retained. It is recommended to retain your activity log for 365 days or more in order to have time to respond to any incidents.", + "ImpactStatement": "", + "RemediationProcedure": "Perform below steps to ensure the log retention is set to 365 days or greater. 1. Logon to SLS Console. 2. Find the project in the Projects section, and then click the target project name. 3. On the page that appears, click Modify a Logstore icon next to the Logstore, and then choose Modify. 4. On the page that appears, click Modify, modify the Data Retention Period, to 365 or greater and then click Save.", + "AuditProcedure": "Perform below steps to ensure the log retention is set to 365 days or greater. 1. Logon to SLS Console. 2. In the Projects section, click the target project name. On the page that appears, click the plus sign (+) next to the search box. 3. In the dialog box that appears, check whether the Permanent Storage is turned on, which means the log data will be stored permanently, or else 4. Ensure the Data Retention Period is set to 365 or greater.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/48990.htm", + "DefaultValue": "The Permanent Storage is turned off by default." + } + ], + "Checks": [ + "sls_logstore_retention_period" + ] + }, + { + "Id": "3.1", + "Description": "Ensure legacy networks does not exist", + "Attributes": [ + { + "Section": "3. Networking", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "In order to prevent use of legacy networks, ECS instances should not have a legacy network configured.", + "RationaleStatement": "Legacy 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. Legacy networks can thus have an impact for high network traffic ECS instance and subject to the single point of failure.", + "ImpactStatement": "", + "RemediationProcedure": "1. Logon to ECS Console 2. In the left-side navigation pane, choose Instance & Image > Instances. 3. Click Create Instance. 4. Specify the basic instance information required by following the instruction and click Next: Networking. 5. Select the Network Type of VPC.", + "AuditProcedure": "1. Logon to ECS Console 2. In the left-side navigation pane, choose Instance & Image > Instances. 3. Check all ECS instances to ensure the Network Type is not classic", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/87190.htm", + "DefaultValue": "By default the ECS are created with VPC Network Type." + } + ], + "Checks": [ + "ecs_instance_no_legacy_network" + ] + }, + { + "Id": "3.2", + "Description": "Ensure that SSH access is restricted from the internet", + "Attributes": [ + { + "Section": "3. Networking", + "Profile": "Level 2", + "AssessmentStatus": "Manual", + "Description": "Security groups provide stateful filtering of ingress/egress network traffic to Alibaba Cloud resources. It is recommended that no security group allows unrestricted ingress access to port 22 or port 3389.", + "RationaleStatement": "Removing unfettered connectivity to remote console services, such as SSH or RDP, reduces a server's exposure to risk.", + "ImpactStatement": "All SSH or RDP connections from outside of the network to the concerned VPC(s) will be blocked. There could be a business need where ssh access is required from outside of the network to access resources associated with the VPC. In that case, specific source IP(s) should be mentioned in firewall rules to white-list access to SSH or RDP port for the concerned VPC(s).", + "RemediationProcedure": "1. Logon to ECS Console 2. Go to Security Group 3. Find the Security Group you want to modify 4. Modify Source IP range to specific IP 5. Save", + "AuditProcedure": "1. Logon to ECS Console 2. In the left-side navigation pane, choose Network & Security > Security Groups. 3. Ensure Port is not equal to 22 or 3389 and Action is not Allow. 4. Ensure IP Ranges is not equal to 0.0.0.0 under Source filters.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/25475.htm https://www.alibabacloud.com/help/doc-detail/100380.htm", + "DefaultValue": "SSH connection is allowed by default." + } + ], + "Checks": [ + "ecs_securitygroup_restrict_ssh_internet" + ] + }, + { + "Id": "3.3", + "Description": "Ensure VPC flow logging is enabled in all VPCs", + "Attributes": [ + { + "Section": "3. Networking", + "Profile": "Level 2", + "AssessmentStatus": "Manual", + "Description": "You can use the flow log function to monitor the IP traffic information for an ENI, a VSwitch or a VPC. If you create a flow log for a VSwitch or a VPC, all the Elastic Network Interfaces, including the newly created Elastic Network Interfaces, 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.", + "RationaleStatement": "VPC Flow Logs provide visibility into network traffic that traverses the VPC and can be used to detect anomalous traffic or insight during security workflows.", + "ImpactStatement": "Currently, the flow log function is available for free. However, corresponding storage and indexing fees associated with the use of Log Service are billed. Before you activate the flow log function, note the following: • The object where a flow log is created can only be ENI. • Only the following resource types support the creation of flow logs: VPC, VSwitch, and ENI. • The maximum number of flow log instances that can be created in each region is 10. If you need to create more flow log instances, open a ticket.", + "RemediationProcedure": "1. Logon to VPC console. 2. In the left-side navigation pane, click FlowLog. 3. Follow the instruction to create FlowLog for each of your VPCs", + "AuditProcedure": "1. Logon to VPC console. 2. In the left-side navigation pane, click FlowLog. 3. Check for every existing VPC to ensure that there is an associated VPC ID on the FlowLog tab.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/90628.html", + "DefaultValue": "By default, Flow Logs is not enabled when you create a new VPC" + } + ], + "Checks": [ + "vpc_flow_logs_enabled" + ] + }, + { + "Id": "3.4", + "Description": "Ensure routing tables for VPC peering are least access", + "Attributes": [ + { + "Section": "3. Networking", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "Once a VPC peering connection is established, routing tables must be updated to establish any connections between the peered VPCs. These routes can be as specific as desired, even peering a VPC to only a single host on the other side of the connection.", + "RationaleStatement": "Although the routing table is empty by default upon creation for any newly created routing table, hence it denies any default access, it is recommended that the table entry is only added based on the least access principle. Being highly selective in peering routing tables is a very effective way of minimizing the impact of breach as resources outside of these routes are inaccessible to the peered VPC.", + "ImpactStatement": "", + "RemediationProcedure": "1. Logon to VPC console. 2. Open the routing table 3. Remove and add route table entries to ensure that the least number of subnets or hosts as is required to accomplish the purpose for peering are routable.", + "AuditProcedure": "1. Logon to VPC console. 2. Open the routing table 3. Review routing tables of peered VPCs for whether they route all subnets of each VPC and whether that is necessary to accomplish the intended purposes for peering the VPCs.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/97766.htm", + "DefaultValue": "Routing table is empty by default upon creation for any newly created routing table, hence it denies any default access" + } + ], + "Checks": [] + }, + { + "Id": "3.5", + "Description": "Ensure the security group are configured with fine grained rules", + "Attributes": [ + { + "Section": "3. Networking", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "Security groups provide stateful filtering of ingress/egress network traffic to Alibaba Cloud resources. It is recommended that all security group configured with fine grained rules.", + "RationaleStatement": "Configure fine grained security group rules is a very effective way of minimizing the impact of breach as resources outside of these rules are inaccessible to the ECS instance.", + "ImpactStatement": "", + "RemediationProcedure": "1. Logon to ECS Console. 2. In the left-side navigation pane, choose Network & Security > Security Groups. 3. Remove any unnecessary rules in all security groups.", + "AuditProcedure": "1. Logon to ECS Console. 2. In the left-side navigation pane, choose Network & Security > Security Groups. 3. Ensure the rules in each of your security groups are all necessary for your operation.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/25475.htm", + "DefaultValue": "" + } + ], + "Checks": [] + }, + { + "Id": "4.1", + "Description": "Ensure that 'Unattached disks' are encrypted", + "Attributes": [ + { + "Section": "4. Virtual Machines", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "Ensure that unattached disks in a subscription are encrypted.", + "RationaleStatement": "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 the data is read from disks.", + "ImpactStatement": "", + "RemediationProcedure": "1. Logon to ECS Console 2. In the left-side navigation pane, choose Storage & Snapshots > Disk. 3. In the upper-right corner of the Disks page, click Create Disk. 4. In the Disk section, check the Disk Encryption box and then select a key from the drop-down list.", + "AuditProcedure": "1. Logon to ECS Console 2. In the left pane, click to expand Storage and Snapshots, click Disks 3. Select each Disk 4. Ensure that each disk has Disks Encryption has Encryption checked with the value of key tag is true", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/59643.htm", + "DefaultValue": "By default, data disks are not encrypted." + } + ], + "Checks": [ + "ecs_unattached_disk_encrypted" + ] + }, + { + "Id": "4.2", + "Description": "Ensure that Virtual Machines disk are encrypted", + "Attributes": [ + { + "Section": "4. Virtual Machines", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "Ensure that disk are encrypted when it is created with the creation of VM instance.", + "RationaleStatement": "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 the data is read from disks.", + "ImpactStatement": "", + "RemediationProcedure": "Encrypt a system disk when copying an image in the ECS console by following the below steps: 1. Logon to ECS Console 2. In the left-side navigation pane, choose Instances & Images > Instances 3. In the top navigation bar, select a region. 4. On the Images page, click the Custom Image tab. 5. Select the target image and click copy Image in the Actions column. 6. In the Copy Image dialog box, check the Encrypt box and then select a key from the drop-down list. 7. Click OK. You can encrypt a data disk when creating an instance by following the below steps: 1. Logon to ECS Console 2. In the left-side navigation pane, choose Instances & Images > Instances 3. On the Instances page, click Create Instance 4. On the Basic Configurations page, find the Storage section and perform the following steps a) Click Add Disk b) Specify the disk category and capacity of data disk c) Select Disk Encryption and then select a key from the drop-down list.", + "AuditProcedure": "1. Logon to ECS Console 2. In the left pane, click to expand Storage and Snapshots, click Disks 3. Select each Data disk 4. Ensure that each disk under Data disks has encryption", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/59643.htm", + "DefaultValue": "Not checked" + } + ], + "Checks": [ + "ecs_attached_disk_encrypted" + ] + }, + { + "Id": "4.3", + "Description": "Ensure no security groups allow ingress from 0.0.0.0/0 to port 22", + "Attributes": [ + { + "Section": "4. Virtual Machines", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "Security groups provide stateful filtering of ingress/egress network traffic to Alibaba Cloud resources. It is recommended that no security group allows unrestricted ingress access to port 22.", + "RationaleStatement": "Rationale: Removing unfettered connectivity to remote console services, such as SSH, reduces a server's exposure to risk.", + "ImpactStatement": "For valid operation needs, such as updating an existing environment, care should be taken to ensure that administrators currently relying on an existing ingress from 0.0.0.0/0 have access to ports 22 through another security group.", + "RemediationProcedure": "1. Logon to ECS Console . 2. In the left pane, click to expand Network* and Security, click Security Groups 3. For each security group, perform the following: a)Select the security group b)Click Add Rules c)Click the Inbound tab d)Identify the rules to be removed f)Click Delete in the Remove column g)Click OK", + "AuditProcedure": "1. Logon to ECS Console . 2. In the left pane, click to expand Network and Security, click Security Groups 3. For each security group, perform the following: 4. Select the security group 5. Click Add Rules 6. Click the Inbound tab 7. Ensure no rule exists that has a port range that includes port 22 and has an Authorization Object of 0.0.0.0/0 Note: A Port value of ALL or a port range such as 0-1024 also includes port 22.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/51170.htm", + "DefaultValue": "By default, Authorization Object and port range are not set." + } + ], + "Checks": [ + "ecs_securitygroup_restrict_ssh_internet" + ] + }, + { + "Id": "4.4", + "Description": "Ensure no security groups allow ingress from 0.0.0.0/0 to port 3389", + "Attributes": [ + { + "Section": "4. Virtual Machines", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "Security groups provide filtering of ingress/egress network traffic to Aliyun resources. It is recommended that no security group allows unrestricted ingress access to port 3389.", + "RationaleStatement": "Removing unfettered connectivity to remote console services, such as RDP, reduces a server's exposure to risk.", + "ImpactStatement": "For valid operation needs, such as updating an existing environment, care should be taken to ensure that administrators currently relying on an existing ingress from 0.0.0.0/0 have access to ports 3389 through another security group.", + "RemediationProcedure": "1. Logon to ECS Console . 2. In the left pane, click to expand Network and Security, click Security Groups For each security group, perform the following: 1. Select the security group 2. Click Add Rules 3. Click the Inbound tab 4. Identify the rules to be removed 5. Click Delete in the Remove column 6. Click OK", + "AuditProcedure": "1. Logon to ECS Console . 2. In the left pane, click to expand Network and Security, click Security Groups 3. For each security group, perform the following: 4. Select the security group 5. Click Add Rules 6. Click the Inbound tab 7. Ensure no rule exists that has a port range that includes port 3389 and has an Authorization Object of 0.0.0.0/0 Note: A Port value of ALL or a port range such as 0-1024 also includes port 3389.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/51170.htm", + "DefaultValue": "By default, Authorization Object and port range are not set." + } + ], + "Checks": [ + "ecs_securitygroup_restrict_rdp_internet" + ] + }, + { + "Id": "4.5", + "Description": "Ensure that the latest OS Patches for all Virtual Machines are applied", + "Attributes": [ + { + "Section": "4. Virtual Machines", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "Ensure that the latest OS patches for all virtual machines are applied.", + "RationaleStatement": "Windows and Linux virtual machines should be kept updated to: • Address a specific bug or flaw • Improve an OS or applications general stability • Fix a security vulnerability The Alibaba Cloud Security Center checks for the latest updates in Linux and Windows systems. If an ECS instance is missing a system update, the Security Center will recommend system updates be applied.", + "ImpactStatement": "", + "RemediationProcedure": "1. Logon to Security Center Console 2. Select Vulnerabilities 3. Apply all patches for vulnerabilities", + "AuditProcedure": "1. Logon to Security Center Console 2. Select Vulnerabilities 3. Ensure all vulnerabilities are fixed", + "AdditionalInformation": "", + "References": "", + "DefaultValue": "By default, patches are not automatically deployed." + } + ], + "Checks": [ + "ecs_instance_latest_os_patches_applied" + ] + }, + { + "Id": "4.6", + "Description": "Ensure that the endpoint protection for all Virtual Machines is installed", + "Attributes": [ + { + "Section": "4. Virtual Machines", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "Install endpoint protection for all virtual machines.", + "RationaleStatement": "Installing endpoint protection systems (like Security Center for Alibaba Cloud) provides for real-time protection capability that helps identify and remove viruses, spyware, and other malicious software, with configurable alerts when known malicious software attempts to install itself or run on ECS.", + "ImpactStatement": "", + "RemediationProcedure": "Using the Alibaba Cloud Management Console: 1. Logon to Security Center Console 2. Select Settings 3. Click Agent 4. On the Agent tab, select the virtual machines without Security Center agent installed 5. Click Install", + "AuditProcedure": "Using the Alibaba Cloud Management Console: 1. Logon to Security Center Console 2. Select Overview 3. Ensure all ECS are installed with Security Center agent", + "AdditionalInformation": "", + "References": "", + "DefaultValue": "Not installed" + } + ], + "Checks": [ + "ecs_instance_endpoint_protection_installed" + ] + }, + { + "Id": "5.1", + "Description": "Ensure that OSS bucket is not anonymously or publicly accessible", + "Attributes": [ + { + "Section": "5. Storage", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "A bucket is a container used to store objects in Object Storage Service (OSS). All objects in OSS are stored in buckets. It is recommended that the access policy on OSS bucket does not allows anonymous and/or public access.", + "RationaleStatement": "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. Hence, ensure that anonymous and/or public access to a bucket is not allowed.", + "ImpactStatement": "Customers may set ACL to public due to the business needs.", + "RemediationProcedure": "The anonymous or public access to OSS bucket can be restricted through both Bucket ACL and Bucket Policy. Using the Bucket ACL: 1. Logon to OSS console. 2. In the bucket-list pane, click on a target OSS bucket 3. Click on Basic Setting in top middle of the console 4. Under ACL section, click on configure 5. Click Private 6. Click Save Using Bucket Policy: 1. Logon to OSS console. 2. Click Bucket, and then click the name of target bucket. 3. Click the Files tab. On the page that appears, click Authorize. 4. In the Authorize dialog box that appears, click Authorize. 5. In the Authorize dialog box that appears, choose the Anonymous Accounts (*) for Accounts and choose None for Authorized Operation`. 6. Click OK.", + "AuditProcedure": "The anonymous or public access to OSS bucket can be restricted through both Bucket Access Control List (ACL) and Bucket Policy. Using the Bucket ACL: 1. Logon to OSS console. 2. In the bucket-list pane, click on a target OSS bucket 3. Click on Basic Setting in top middle of the console 4. Under ACL section, ensure the Bucket ACL is set to `Private. Using Bucket Policy: 1. Logon to OSS console. 2. Click Bucket, and then click the name of target bucket. 3. Click the Files tab. On the page that appears, click Authorize. 4. In the Authorize dialog box that appears, click Authorize. 5. In the Authorize dialog box that appears, ensure the Anonymous Accounts (*) is selected under Accounts and None is selected under Authorized Operation.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/31896.htm", + "DefaultValue": "Private" + } + ], + "Checks": [ + "oss_bucket_not_publicly_accessible" + ] + }, + { + "Id": "5.2", + "Description": "Ensure that there are no publicly accessible objects in storage buckets", + "Attributes": [ + { + "Section": "5. Storage", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "A bucket is a container used to store objects in Object Storage Service (OSS). All objects in OSS are stored in buckets. It is recommended that storage object ACL should not grant public access.", + "RationaleStatement": "Allowing public access to objects allows anyone with an internet connection to access sensitive data that is important to your business. Also note that even if a bucket ACL applied on storage does not allow public access, there could be object specific ACLs that allows public access to the specific access to the specific objects inside the buckets. Hence it is important to check object ACLs at individual object level.", + "ImpactStatement": "Customers may set ACL to public due to the business needs.", + "RemediationProcedure": "Using the Management Console: 1. Logon to OSS console. 2. In the bucket-list pane, click on a target OSS bucket 3. Click on Files in top middle of the console 4. Hover on More in the right column on a target object 5. Click Set ACL 6. Click Private 7. Click Save", + "AuditProcedure": "Using the Management Console: 1. Logon to OSS console. 2. In the bucket-list pane, click on a target OSS bucket 3. Click on Files in top middle of the console 4. Click on View details in the right column on a target object 5. Ensure File ACL is set to private", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/31909.htm", + "DefaultValue": "By Default, object ACLs is inherited from corresponding bucket ACL." + } + ], + "Checks": [] + }, + { + "Id": "5.3", + "Description": "Ensure that logging is enabled for OSS buckets", + "Attributes": [ + { + "Section": "5. Storage", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "OSS Bucket Access Logging generates a log that contains access records for each request made to your OSS bucket. An access log record contains details about the request, such as the request type, the resources specified in the request worked, and the time and date the request was processed. It is recommended that bucket access logging be enabled on the OSS bucket.", + "RationaleStatement": "By enabling OSS bucket logging on target OSS buckets, it is possible to capture all events which may affect objects within an target buckets. Configuring logs to be placed in a separate bucket allows access to log information which can be useful in security and incident response workflows.", + "ImpactStatement": "Extra cost for log storage may incur.", + "RemediationProcedure": "Perform the following to enable OSS bucket logging: Through the management console: 1. Logon to OSS console. 2. In the bucket-list pane, click on a target OSS bucket 3. Under Log, click configure 4. Configure bucket logging 5. Click the Enabled checkbox 6. Select Target Bucket from list 7. Enter a Target Prefix 8. Click Save", + "AuditProcedure": "Perform the following ensure the OSS bucket has access logging is enabled: Through the management console: 1. Logon to the OSS console. 2. In the bucket-list pane, click on a target OSS bucket 3. Under Log, ensure Enabled is checked.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/31900.htm", + "DefaultValue": "Logging is disabled." + } + ], + "Checks": [ + "oss_bucket_logging_enabled" + ] + }, + { + "Id": "5.4", + "Description": "Ensure that 'Secure transfer required' is set to 'Enabled'", + "Attributes": [ + { + "Section": "5. Storage", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "Enable the data encryption in transit.", + "RationaleStatement": "The secure transfer enhances the security of OSS bucket by only allowing requests to the storage account by a secure connection. For example, when calling REST APIs to access storage accounts, the connection must use HTTPS. Any requests using HTTP will be rejected.", + "ImpactStatement": "", + "RemediationProcedure": "USing the management console: 1. Logon to OSS console. 2. In the bucket-list pane, click on a target OSS bucket 3. Click on Files in top middle of the console 4. Click on Authorize 5. Click on Whole Bucket,*, None (Authorized Operation) and http (Conditions:Access Method) 6. Click on Save", + "AuditProcedure": "Using the management console: 1. Logon to OSS console. 2. In the bucket-list pane, click on a target OSS bucket 3. Click on Files in top middle of the console 4. Click on Authorize 5. Ensure a policy is set to None (Authorized Operation) and http (Conditions:Access Method)", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/85111.htm", + "DefaultValue": "" + } + ], + "Checks": [ + "oss_bucket_secure_transport_enabled" + ] + }, + { + "Id": "5.5", + "Description": "Ensure that the shared URL signature expires within an hour", + "Attributes": [ + { + "Section": "5. Storage", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "Expire the shared URL signature within an hour.", + "RationaleStatement": "URL signature is a URL that grants access rights to OSS. You can add signature information to a URL so that you can forward the URL to the third party for authorized access. A URL signature can be provided to the third party for authorized access. Providing a URL signature to these clients allows them access to a resource for a specified period of time. This time should be set as low as possible, and preferably no longer than an hour.", + "ImpactStatement": "", + "RemediationProcedure": "Through the management console: 1. Logon to OSS console. 2. In the bucket-list pane, click on a target OSS bucket 3. Click on Files in top middle of the console 4. Click on View Details in the right column on a target object 5. Set Validity Period to a value less than 3600", + "AuditProcedure": "Through the management console: 1. Logon to OSS console. 2. In the bucket-list pane, click on a target OSS bucket 3. Click Files in top middle of the console 4. Click View Details in the right column on a target object 5. Ensure Validity Period is set to less than 3600", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/31912.htm", + "DefaultValue": "300 seconds." + } + ], + "Checks": [] + }, + { + "Id": "5.6", + "Description": "Ensure that URL signature is allowed only over https", + "Attributes": [ + { + "Section": "5. Storage", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "URL signature is a URL that grants access rights to OSS. You can add signature information to a URL so that you can forward the URL to the third party for authorized access.A URL signature can be provided to the third party for authorized access.", + "RationaleStatement": "It is recommended to allow such access requests over HTTPS protocol only. URL signature should be allowed only over HTTPS protocol.", + "ImpactStatement": "", + "RemediationProcedure": "Using the management console: 1. Logon to OSS console. 2. In the bucket-list pane, click on a target OSS bucket 3. Click on Files in top middle of the console 4. Click on View Details in the right column on a target object 5. Set HTTPS to Enabled", + "AuditProcedure": "Using the management console: 1. Logon to OSS console. 2. In the bucket-list pane, click on a target OSS bucket 3. Click on Files in top middle of the console 4. Click on View Details in the right column on a target object 5. Ensure HTTPS is set to Enabled", + "AdditionalInformation": "", + "References": "", + "DefaultValue": "Enabled" + } + ], + "Checks": [] + }, + { + "Id": "5.7", + "Description": "Ensure network access rule for storage bucket is not set to publicly accessible", + "Attributes": [ + { + "Section": "5. Storage", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "Restricting default network access helps to provide a new layer of security, since OSS accept connections from clients on any network. To limit access to selected networks, the default action must be changed.", + "RationaleStatement": "Access can be granted to public internet IP address ranges, to enable connections from specific internet or on-premises clients. When network rules are configured, only applications from allowed networks can access OSS bucket.", + "ImpactStatement": "", + "RemediationProcedure": "Using the management console: 1. Logon to OSS console. 2. In the bucket-list pane, click on a target OSS bucket 3. Click on Files in top middle of the console 4. Click on Authorize 5. Click on Whole Bucket,*,None, Condition IP = specified IP address or IP address segment 6. Click on Save", + "AuditProcedure": "Using the management console: 1. Logon to OSS console. 2. In the bucket-list pane, click on a target OSS bucket 3. Click on Files in top middle of the console 4. Click on Authorize 5. Ensure a policy is set to be granted to public internet IP address ranges", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/85111.htm", + "DefaultValue": "Not set." + } + ], + "Checks": [] + }, + { + "Id": "5.8", + "Description": "Ensure server-side encryption is set to Encrypt with Service Key", + "Attributes": [ + { + "Section": "5. Storage", + "Profile": "Level 2", + "AssessmentStatus": "Manual", + "Description": "Enable server-side encryption (Encrypt with Service Key) for objects.", + "RationaleStatement": "Server-side encryption protects your data at rest.", + "ImpactStatement": "Service key incurs an additional cost from accessing the KMS service.", + "RemediationProcedure": "Using the management console: Perform the following to configure the OSS bucket to use SSE-KMS: 1. Logon to OSS console. 2. In the bucket-list pane, click on the target OSS bucket 3. Click Basic Setting in top middle of the console 4. Under the Server-side Encryption section, click on configure 5. Click KMS and select KMS service key(alias/acs/oss)", + "AuditProcedure": "Perform the following to determine if the OSS bucket is configured to use SSE-KMS: Using the management console: 1. Logon to OSS console. 2. In the bucket-list pane, click on the target OSS bucket 3. Click on Basic Setting in top middle of the console 4. Under the Server-side Encryption section, ensure the target OSS Bucket Encryption is set to KMS and the Encryption Method of KMS and the service key (alias/acs/oss) is selected.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/108880.htm", + "DefaultValue": "Not encrypted." + } + ], + "Checks": [] + }, + { + "Id": "5.9", + "Description": "Ensure server-side encryption is set to Encrypt with BYOK", + "Attributes": [ + { + "Section": "5. Storage", + "Profile": "Level 2", + "AssessmentStatus": "Manual", + "Description": "Enable server-side encryption (Encrypt with BYOK) for objects.", + "RationaleStatement": "Server-side encryption protects your data at rest.", + "ImpactStatement": "Service key incurs an additional cost from accessing the KMS service.", + "RemediationProcedure": "Perform the following to configure the OSS bucket to use SSE-KMS: Using the management console: 1. Logon to OSS console. 2. In the bucket-list pane, click on the target OSS bucket 3. Click on Basic Setting in top middle of the console 4. Under the Server-side Encryption section, click on configure 5. Click on KMS and select an existing CMK from the KMS key Id drop-down menu 6. Click save", + "AuditProcedure": "Perform the following to determine if the OSS bucket is configured to use SSE-KMS: Using the management console: 1. Logon to OSS console. 2. In the bucket-list pane, click on the target OSS bucket 3. Click on Basic Setting in top middle of the console 4. Under the Server-side Encryption section, ensure the target OSS Bucket Encryption is set to KMS and a customer created KMS key ID is specified in the KMS Key Id field.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/108880.htm", + "DefaultValue": "By default, Buckets are not set to be encrypted." + } + ], + "Checks": [] + }, + { + "Id": "6.1", + "Description": "Ensure that RDS instance requires all incoming connections to use SSL", + "Attributes": [ + { + "Section": "6. Relational Database Services", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "It is recommended to enforce all incoming connections to SQL database instance to use SSL.", + "RationaleStatement": "SQL database connections if successfully trapped (MITM); can reveal sensitive data like credentials, database queries, query outputs etc. For security, it is recommended to always use SSL encryption when connecting to your instance. This recommendation is applicable for PostgreSQL and MySQL Instances.", + "ImpactStatement": "", + "RemediationProcedure": "Using the management console: 1. Logon to RDS Console. 2. Select the region where the target instance is located. 3. Click the ID of the target instance to enter the Basic Information page. 4. In the left-side navigation pane, click Data Security. 5. Click the SSL Encryption tab. 6. Click the switch next to Disabled in the SSL Encryption parameter. 7. In the Configure SSL dialog box, select the endpoint for which you want to enable SSL encryption and then click OK. 8. Click Download CA Certificate to download an SSL certificate. 9. The downloaded SSL certificate is a package including the following files: p7b file: is used to import the CA certificate on Windows OS. PEM file: is used to import the CA certificate on other systems or for other applications. JKS file: is a Java truststore certificate file used for importing CA certificate chains in Java programs. The password is apsaradb.", + "AuditProcedure": "Using the management console: 1. Logon to RDS Console. 2. Select the region where the target instance is located. 3. Click the ID of the target instance to enter the Basic Information page. 4. In the left-side navigation pane, click Data Security to go to the Security page. 5. Click the SSL Encryption tab. 6. Check the button SSL Encryption is Enabled.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/32474.htm", + "DefaultValue": "Encryption is off by default." + } + ], + "Checks": [ + "rds_instance_ssl_enabled" + ] + }, + { + "Id": "6.2", + "Description": "Ensure that RDS Instances are not open to the world", + "Attributes": [ + { + "Section": "6. Relational Database Services", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "Database Server should accept connections only from trusted Network(s)/IP(s) and restrict access from the world.", + "RationaleStatement": "To minimize attack surface on a Database server Instance, only trusted/known and required IP(s) should be white-listed to connect to it. Authorized network should not have IPs/networks configured to 0.0.0.0 or /0 which will allow access to the instance from anywhere in the world.", + "ImpactStatement": "", + "RemediationProcedure": "Using the management console: 1. Logon to RDS Console. 2. In the upper left corner, select the region where the target instance is located. 3. Locate the target instance and click its ID. 4. In the left-side navigation pane, click Data Security to visit the Security page. 5. On the Whitelist Settings tab page, follow below instructions based on your scenario: • To access the RDS instance from an ECS instance located within a VPC, click Edit for the default VPC whitelist. • To access the RDS instance from an ECS instance located within a classic network, click Edit for the default Classic Network whitelist. • To access the RDS instance from a server or computer located in a public network, click Edit for the default Classic Network whitelist. 6. In the displayed Edit Whitelist dialog box, remove any 0.0.0.0 or /0 entries, and only add the IP addresses that need to access the instance, and then click OK. • If you add an IP address range, such as 10.10.10.0/24, any IP address in 10.10.10.X format can access the RDS instance. • If you add multiple IP addresses or IP address ranges, separate them with a comma (without spaces), for example, 192.168.0.1,172.16.213.9. • You can click Add Internal IP Addresses of ECS Instance to display the IP addresses of all the ECS instances under your Alibaba Cloud account and add to the whitelist.", + "AuditProcedure": "Using the management console: 1. Logon to RDS Console. 2. In the upper left corner, select the region where the target instance is located. 3. Locate the target instance and click its ID. 4. In the left-side navigation pane, click Data Security to visit the Security page. 5. On the Whitelist Settings tab, check if the authorized servers IPs have been configured, and it is not configured as 0.0.0.0 or /0. Note: You can also click Add a Whitelist Group to create a new group.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/26198.htm", + "DefaultValue": "By default, the whitelist setting is 127.0.0.1 that is not allowing any connection from any server." + } + ], + "Checks": [ + "rds_instance_no_public_access_whitelist" + ] + }, + { + "Id": "6.3", + "Description": "Ensure that 'Auditing' is set to 'On' for applicable database instances", + "Attributes": [ + { + "Section": "6. Relational Database Services", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "Enable SQL auditing on all RDS except SQL Server 2012/2016/2017 and MariaDB TX.", + "RationaleStatement": "The Alibaba Cloud allows MySQL instance to be created as a service. Enabling auditing at the server level ensures that all existing and newly created databases on the MySQL instance are audited. Auditing policy applied on the MySQL database does not override auditing policy and settings applied on the particular MySQL server where the database is hosted. Auditing tracks database events and writes them to an audit log in the Alibaba Cloud MySQL account. It also helps to maintain regulatory compliance, understand database activity, and gain insight into discrepancies and anomalies that could indicate business concerns or suspected security violations.", + "ImpactStatement": "By activating Auditing, the system then automatically starts charging an hourly fee of US$ 0.0018 per GB.", + "RemediationProcedure": "Using the management console: 1. Logon to RDS Console. 2. In the upper-left corner, select the region of the target instance. 3. Locate the target instance, and click the instance ID. 4. In the left-side navigation pane, select SQL Explorer. 5. Click Activate Now. 6. Specify the SQL log storage duration (for how long you want to keep the SQL log), and click Activate.", + "AuditProcedure": "Using the management console: 1. Logon to RDS Console. 2. In the upper-left corner, select the region of the target instance. 3. Locate the target instance, and click the instance ID. 4. In the left-side navigation pane, select SQL Explorer. 5. Check if there is a “Welcome to Use SQL Explore” page, as such a page indicates that the auditing is not yet enabled. If the auditing is enabled, then the SQL Explorer should show the SQL Explore dashboard directly.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/96123", + "DefaultValue": "Disable" + } + ], + "Checks": [ + "rds_instance_sql_audit_enabled" + ] + }, + { + "Id": "6.4", + "Description": "Ensure that 'Auditing' Retention is 'greater than 6 months'", + "Attributes": [ + { + "Section": "6. Relational Database Services", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "Database SQL Audit Retention should be configured to be greater than 90 days.", + "RationaleStatement": "Audit Logs can be used to check for anomalies and give insight into suspected breaches or misuse of information and access.", + "ImpactStatement": "", + "RemediationProcedure": "Using the management console: 1. Logon to RDS Console. 2. In the upper-left corner, select the region of the target instance. 3. Locate the target instance, and click the instance ID. 4. In the left-side navigation pane, select SQL Explore. 5. Click Service Setting button on the top right corner. 6. In the service setting page, enable Activate SQL Explore, set the storage duration as 6 months or longer.", + "AuditProcedure": "Using the management console: 1. Logon to RDS Console. 2. In the upper-left corner, select the region of the target instance. 3. Locate the target instance, and click the instance ID. 4. In the left-side navigation pane, select SQL Explore. 5. Click Service Setting button on the top right corner. 6. In the service setting page, assure the storage duration is set as 6 months or longer.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/96123.htm", + "DefaultValue": "Active SQL Explorer is disabled." + } + ], + "Checks": [ + "rds_instance_sql_audit_retention" + ] + }, + { + "Id": "6.5", + "Description": "Ensure that 'TDE' is set to 'Enabled' on for applicable database instance", + "Attributes": [ + { + "Section": "6. Relational Database Services", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "Enable Transparent Data Encryption on every RDS instance.", + "RationaleStatement": "RDS Database transparent data encryption 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 without requiring changes to the application.", + "ImpactStatement": "", + "RemediationProcedure": "Using the management console: 1. Logon to RDS Console. 2. In the upper-left corner, select the region of the target instance. 3. Locate the target instance, and click the instance ID to enter the Basic Information page. 4. In the left-side navigation pane, click Data Security to go to the Security page. 5. Click the TDE tab. 6. On the TDE tab, find TDE Status and click the switch next to Disabled. 7. In the displayed dialog box, choose automatically generated key or custom key, click Confirm. • Encrypt a table a. For RDS for MySQL, connect to the instance and run the following command to encrypt tables. alter table engine=innodb, block_format=encrypted b. For RDS for SQL Server, click Configure TDE, select the databases to encrypt, add them to the right, and click OK. • Decrypt data a. To decrypt a MySQL table encrypted by TDE, run the following command: alter table engine=innodb, block_format=default b. To decrypt a SQL Server table encrypted by TDE, click Configure TDE and move the database to the left.", + "AuditProcedure": "Using the management console: 1. Logon to RDS Console. 2. In the upper-left corner, select the region of the target instance. 3. Locate the target instance, and click the instance ID. 4. In the left-side navigation pane, click Data Security to go to the Security page. 5. Click the TDE tab. 6. Check the button TDE Status is Enabled.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/33510.html", + "DefaultValue": "Disabled" + } + ], + "Checks": [ + "rds_instance_tde_enabled" + ] + }, + { + "Id": "6.6", + "Description": "Ensure RDS instance TDE protector is encrypted with BYOK (Use your own key)", + "Attributes": [ + { + "Section": "6. Relational Database Services", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "TDE with BYOK support provides increased transparency and control, increased security with an HSM-backed KMS service, and promotion of separation of duties. With TDE, data is encrypted at rest with a symmetric key (called the database encryption key). With BYOK support for TDE, the DEK can be protected with an asymmetric key that is stored in the KMS. Based 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).", + "RationaleStatement": "Bring Your Own Key (BYOK) support for Transparent Data Encryption (TDE) allows user control of TDE encryption keys and restricts who can access them and when. Alibaba Cloud KMS, a cloud-based key management system is the service where TDE has integrated support for BYOK. With BYOK, the database encryption key is protected by an asymmetric key stored in the KMS.", + "ImpactStatement": "Additional investment in administration time is needed to produce, maintain, store, etc. customer provided keys.", + "RemediationProcedure": "Using the management console: 1. Logon to RDS Console. 2. In the upper-left corner, select the region of the target instance. 3. Locate the target instance, and click the instance ID to enter the Basic Information page. 4. In the left-side navigation pane, click Data Security to go to the Security page. 5. Click the TDE tab. 6. On the TDE tab, find TDE Status and click the switch next to Disabled. 7. In the displayed dialog box, choose custom key, click Confirm.", + "AuditProcedure": "Using the management console: 1. Logon to RDS Console. 2. In the upper-left corner, select the region of the target instance. 3. Locate the target instance, and click the instance ID. 4. In the left-side navigation pane, click Data Security to go to the Security page. 5. Click the TDE tab. 6. Check the button TDE Status is Enabled and a custom key ID is shown for the Key field and the status is Valid.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/96121.htm", + "DefaultValue": "Disabled" + } + ], + "Checks": [ + "rds_instance_tde_key_custom" + ] + }, + { + "Id": "6.7", + "Description": "Ensure parameter 'log_connections' is set to 'ON' for PostgreSQL Database", + "Attributes": [ + { + "Section": "6. Relational Database Services", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "Enable log_connections on PostgreSQL Servers.", + "RationaleStatement": "Enabling log_connections helps PostgreSQL Database to log attempted connection to the server, as well as successful completion of client authentication. Log data can be used to identify, troubleshoot, and repair configuration errors and suboptimal performance.", + "ImpactStatement": "", + "RemediationProcedure": "Using the management console: 1. Logon to RDS Console. 2. In the upper-left corner, select the region of the target instance. 3. Locate the target instance, and click the instance ID to enter the Basic Information page. 4. In the left-side navigation pane, select Parameters. 5. Click the Edit icon of log_connection parameter next the Actual Value column. 6. Enter On as the Actual Value and click Confirm. 7. Click Apply Changes. 8. In the message that appears, click Confirm.", + "AuditProcedure": "Using the management console: 1. Logon to RDS Console. 2. In the upper-left corner, select the region of the target instance. 3. Locate the target instance, and click the instance ID to enter the Basic Information page. 4. In the left-side navigation pane, select Parameters and ensure the log_connection is set as On in the Actual Value column.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/96751.htm", + "DefaultValue": "Off" + } + ], + "Checks": [ + "rds_instance_postgresql_log_connections_enabled" + ] + }, + { + "Id": "6.8", + "Description": "Ensure server parameter 'log_disconnections' is set to 'ON' for PostgreSQL Database Server", + "Attributes": [ + { + "Section": "6. Relational Database Services", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "Enable log_disconnections on PostgreSQL Servers.", + "RationaleStatement": "Enabling log_disconnections helps PostgreSQL Database to log session terminations of the server, as well as duration of the session. Log data can be used to identify, troubleshoot, and repair configuration errors and suboptimal performance.", + "ImpactStatement": "", + "RemediationProcedure": "Using the management console: 1. Login to RDS Console. 2. In the upper-left corner, select the region of the target instance. 3. Locate the target instance, and click the instance ID to enter the Basic Information page. 4. In the left-side navigation pane, select Parameters. 5. Click the Edit icon of log_disconnections parameter next the Actual Value column. 6. Enter On as the Actual Value and click Confirm. 7. Click Apply Changes. 8. In the message that appears, click Confirm.", + "AuditProcedure": "Using the management console: 1. Logon to RDS Console. 2. In the upper-left corner, select the region of the target instance. 3. Locate the target instance, and click the instance ID to enter the Basic Information page. 4. In the left-side navigation pane, select Parameters and ensure the log_disconnections is set as On in the Actual Value column.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/96751.htm", + "DefaultValue": "Off" + } + ], + "Checks": [ + "rds_instance_postgresql_log_disconnections_enabled" + ] + }, + { + "Id": "6.9", + "Description": "Ensure server parameter 'log_duration is set to 'ON' for PostgreSQL Database Server", + "Attributes": [ + { + "Section": "6. Relational Database Services", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "Enable log_duration on PostgreSQL Servers.", + "RationaleStatement": "Enabling log_duration helps PostgreSQL Database to Logs the duration of each completed SQL statement which in turn generates query and error logs. Query and error logs can be used to identify, troubleshoot, and repair configuration errors and sub- optimal performance.", + "ImpactStatement": "", + "RemediationProcedure": "Using the management console: 1. Logon to RDS Console. 2. In the upper-left corner, select the region of the target instance. 3. Locate the target instance, and click the instance ID to enter the Basic Information page. 4. In the left-side navigation pane, select Parameters. 5. Click the Edit icon of log_durantion parameter next the Actual Value column. 6. Enter On as the Actual Value and click Confirm. 7. Click Apply Changes. 8. In the message that appears, click Confirm.", + "AuditProcedure": "Using the management console: 1. Logon to RDS Console. 2. In the upper-left corner, select the region of the target instance. 3. Locate the target instance, and click the instance ID to enter the Basic Information page. 4. In the left-side navigation pane, select Parameters and ensure the log_durantion is set as On in the Actual Value column.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/96751.htm", + "DefaultValue": "Off" + } + ], + "Checks": [ + "rds_instance_postgresql_log_duration_enabled" + ] + }, + { + "Id": "7.1", + "Description": "Ensure Log Service is set to Enabled on Kubernetes Engine Clusters", + "Attributes": [ + { + "Section": "7. Kubernetes Engine", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "Log Service is a complete real-time data logging service on Alibaba Cloud to support collection, shipping, search, storage and analysis for logs. It includes a user interface to call the Log Viewer and an API to management logs pragmatically. Log Service could automatically collect, process, and store your container and audit logs in a dedicated, persistent datastore. Container logs are collected from your containers. Audit logs are collected from the kube-apiserver or the deployed ingress. Events are logs about activity in the cluster, such as the deleting of Pods or Secrets.", + "RationaleStatement": "By enabling you will have container and system logs, Kubernetes Engine deploys a per- node logging agent that reads container logs, adds helpful metadata, and then stores them. The logging agent would help to collecting the following sources: • kube-apiserver audit logs • ingress visiting logs • Standard output and standard error logs from containerized processes For events, Kubernetes Engine uses a Deployment in the kube-system namespace which automatically collects events and sends them to Log Service. Log Service is compatible with JSON formats.", + "ImpactStatement": "", + "RemediationProcedure": "Using the management console: 1. Logon to ACK console 2. Click Create Kubernetes Cluster and set Enable Log Service to Enabled when creating cluster", + "AuditProcedure": "Using the management console: 1. Logon to ACK console 2. Select the target cluster and click its name into cluster detail page 3. Select Cluster Auditing on the left column and check if audit page shown", + "AdditionalInformation": "", + "References": "https://help.aliyun.com/document_detail/91406.html https://help.aliyun.com/document_detail/86532.html", + "DefaultValue": "By default, logging service is disabled when you create a new cluster using console." + } + ], + "Checks": [ + "cs_kubernetes_log_service_enabled" + ] + }, + { + "Id": "7.2", + "Description": "Ensure CloudMonitor is set to Enabled on Kubernetes Engine Clusters", + "Attributes": [ + { + "Section": "7. Kubernetes Engine", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "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. The monitor can access metrics about CPU utilization, some disk traffic metrics, network traffic, and disk IO information, which help to monitor signals and build operations in your Kubernetes Engine clusters.", + "RationaleStatement": "By Enabling CloudMonitor installation you will have system metrics and custom metrics. System metrics are measurements of the cluster's infrastructure, such as CPU or memory usage. For system metrics, a monitor controller would be created and periodically connects to each node and collects metrics about its Pods and containers, then sends the metrics to CloudMonitor server. Metrics for usage of system resources are collected from the CPU, Memory, Evictable memory, Non-evictable memory, and Disk sources.", + "ImpactStatement": "", + "RemediationProcedure": "Using the management console: 1. Logon to ACK console 2. Click the Create Kubernetes Cluster button and set CloudMonitor Agent to Enabled under creation options.", + "AuditProcedure": "Using the management console: 1. Logon to ACK console 2. Select the target cluster and click its name into cluster detail page 3. Select the Nodes on the left column and click the Monitor link on the Actions column of the selected node 4. Check if OS Metrics data existing in the CloudMonitor page of the selected ECS node", + "AdditionalInformation": "", + "References": "https://help.aliyun.com/document_detail/125508.html https://help.aliyun.com/document_detail/102337.html", + "DefaultValue": "By default, CloudMonitor Agent installation is disenabled when you create a new cluster using console." + } + ], + "Checks": [ + "cs_kubernetes_cloudmonitor_enabled" + ] + }, + { + "Id": "7.3", + "Description": "Ensure role-based access control (RBAC) authorization is Enabled on Kubernetes Engine Clusters", + "Attributes": [ + { + "Section": "7. Kubernetes Engine", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "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. To ensure that RBAC limits permissions correctly, you must disable the legacy authorizer. RBAC has significant security advantages, can help you ensure that users only have access to specific cluster resources within their own namespace and is now stable in Kubernetes.", + "RationaleStatement": "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, and the subaccounts who bind the roles could only have the permissions to access the specific resources in the cluster or namespaces defined in RBAC policies.", + "ImpactStatement": "", + "RemediationProcedure": "Using the management console: 1. Logon to ACK console 2. Select the target RAM sub-account and configure the RBAC roles on specific clusters or namespaces.", + "AuditProcedure": "Using the management console: 1. Logon to ACK console 2. Select the target RAM sub-account in the Clusters -> Authorizations page 3. After RAM user/role is selected, configure the RBAC roles on specific clusters or namespaces", + "AdditionalInformation": "", + "References": "https://help.aliyun.com/document_detail/87656.html https://help.aliyun.com/document_detail/119596.html", + "DefaultValue": "By default, RBAC authorization is enabled on ACK clusters, and the legacy authorizations as ABAC is disenable. Besides, the RAM sub-users have no permissions to access any resources in ACK clusters by default." + } + ], + "Checks": [ + "cs_kubernetes_rbac_enabled" + ] + }, + { + "Id": "7.4", + "Description": "Ensure Cluster Check triggered at least once per week for Kubernetes Clusters", + "Attributes": [ + { + "Section": "7. Kubernetes Engine", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "Kubernetes Engine's cluster check feature helps you verify the system nodes and components healthy status. When you trigger the checking, the process would check on the health state of each node in your cluster and also the cluster configuration as kubelet\\docker daemon\\kernel and network iptables configuration, if there are fails consecutive health checks, the diagnose would report to admin for further repair.", + "RationaleStatement": "Kubernetes Engine uses the node's health status to determine if a node needs to be repaired. A node reporting a Ready status is considered healthy. The cluster administrator could choose to trigger the cluster check periodically. An cluster healthy checking including: • The cloud resource healthy status, including the VPC/VSwitch SLB and every ECS node status in cluster. • The kubelet, docker daemon, kernel, iptables configurations on every node in cluster. Kubernetes Engine generates the diagnose report when checking finish. You can check the diagnose suggestion on ACK console.", + "ImpactStatement": "", + "RemediationProcedure": "Using the management console: 1. Logon to ACK console 2. Select the target cluster and open the More pop-menu for advance options on cluster 3. Select Global Check and click the Start button to trigger the checking", + "AuditProcedure": "Using the management console: 1. Logon to ACK console 2. Select the target cluster and open the More pop-menu for advance options on cluster. 3. Select Overview page on left column and check if the Last check status is Normal. 4. Verify the checking time and details in Global Check.", + "AdditionalInformation": "", + "References": "https://help.aliyun.com/document_detail/114882.html", + "DefaultValue": "By default, the cluster checking process is not auto triggered, the cluster administrator could start it in ACK console." + } + ], + "Checks": [ + "cs_kubernetes_cluster_check_recent" + ] + }, + { + "Id": "7.5", + "Description": "Ensure Kubernetes web UI / Dashboard is not enabled", + "Attributes": [ + { + "Section": "7. Kubernetes Engine", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "Dashboard is a web-based Kubernetes user interface. It can be used to deploy containerized applications to a Kubernetes cluster, troubleshoot your containerized application, and manage the cluster itself along with its attendant resources. You can use Dashboard to get an overview of applications running on your cluster, as well as for creating or modifying individual Kubernetes resources (such as Deployments, Jobs, DaemonSets, etc). For example, you can scale a Deployment, initiate a rolling update, restart a pod or deploy new applications using a deploy wizard.", + "RationaleStatement": "You should disable the Kubernetes Web UI (Dashboard) when running on Kubernetes Engine. The Kubernetes Web UI (Dashboard) is backed by a highly privileged Kubernetes Service Account. It is recommended to use ACK User Console instead of Dashboard to avoid any privileged escalation via compromise the dashboard.", + "ImpactStatement": "", + "RemediationProcedure": "Using the management console: 1. Logon to ACK console 2. Select the target cluster and select the kube-system namespace in the Namespace pop-menu 3. Input dashboard in the deploy filter bar, make sure there is no result exist after the filter, delete the dashboard deployment by selecting the Delete in More pop- menu.", + "AuditProcedure": "Using the management console: 1. Logon to ACK console 2. Select the target cluster and select the kube-system namespace in the Namespace pop-menu 3. Input dashboard in the deploy filter bar, and make sure there is no result exist after the filter.", + "AdditionalInformation": "", + "References": "", + "DefaultValue": "By default, the kube-dashboard would not install in cluster, and the overview console use the managed dashboard which controlled by ACK service." + } + ], + "Checks": [ + "cs_kubernetes_dashboard_disabled" + ] + }, + { + "Id": "7.6", + "Description": "Ensure Basic Authentication is not enabled on Kubernetes Engine Clusters", + "Attributes": [ + { + "Section": "7. Kubernetes Engine", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "Basic authentication allows a user to authenticate to the cluster with a username and password and it is stored in plain text without any encryption. Disabling Basic authentication will prevent attacks like brute force. Its recommended to use either client certificate or RAM for authentication.", + "RationaleStatement": "When disabled, you will still be able to authenticate to the cluster with client certificate or RAM. A client certificate is a base 64-encoded public certificate used by clients to authenticate to the cluster endpoint, and ACK cluster would auto generate the client certificate for each logging RAM user.", + "ImpactStatement": "", + "RemediationProcedure": "1. ssh into any master node in cluster 2. Make sure the basic-auth-file not exist in apiserver manifest with below command: cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep basic-auth-file 3. If you found basic-auth-file existing in apiserver manitfest, please override the manifest file with new manifest content to not include the basic-auth-file and then restart the apiserver, you need repeat the action on all of the master nodes", + "AuditProcedure": "1. ssh into any master node in cluster 2. Make sure the basic-auth-file not exist in apiserver manifest with below command: cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep basic-auth-file", + "AdditionalInformation": "", + "References": "https://help.aliyun.com/document_detail/86494.html https://help.aliyun.com/document_detail/123848.html https://github.com/AliyunContainerService/ack-ram-authenticator", + "DefaultValue": "By default, Basic authentication is not enabled when you create a new cluster." + } + ], + "Checks": [] + }, + { + "Id": "7.7", + "Description": "Ensure Network policy is enabled on Kubernetes Engine Clusters", + "Attributes": [ + { + "Section": "7. Kubernetes Engine", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "A network policy is a specification of how groups of pods are allowed to communicate with each other and other network endpoints. NetworkPolicy resources use labels to select pods and define rules which specify what traffic is allowed to the selected pods. The Kubernetes Network Policy API allows the cluster administrator to specify what pods are allowed to communicate with each other.", + "RationaleStatement": "By default, pods are non-isolated; they accept traffic from any source. Pods become isolated by having a NetworkPolicy that selects them. Once there is any NetworkPolicy in a namespace selecting a particular pod, that pod will reject any connections that are not allowed by any NetworkPolicy. (Other pods in the namespace that are not selected by any NetworkPolicy will continue to accept all traffic.)", + "ImpactStatement": "", + "RemediationProcedure": "Only the Terway network plugin support the Network Policy feature, so please make sure not choose Flannel as network plugin when creating cluster. Using the management console: 1. Logon to ACK console 2. Click the Create Kubernetes Cluster button and select Terway in Network Plugin option.", + "AuditProcedure": "Using the management console: 1. Logon to ACK console 2. Click the Create Kubernetes Cluster button and make sure Terway is selected in Network Plugin option.", + "AdditionalInformation": "", + "References": "https://help.aliyun.com/document_detail/97621.html https://help.aliyun.com/document_detail/86949.html", + "DefaultValue": "By default, Network Policy is disabled when you create a new cluster, and you should choose the Terway as the cluster network plugin when creating the cluster." + } + ], + "Checks": [ + "cs_kubernetes_network_policy_enabled" + ] + }, + { + "Id": "7.8", + "Description": "Ensure ENI multiple IP mode support for Kubernetes Cluster", + "Attributes": [ + { + "Section": "7. Kubernetes Engine", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "Alibaba Cloud ENI (Elastic Network Interface) has supported assign ranges of internal IP addresses as aliases to a single virtual machine's ENI network interfaces. This is useful if you have lots of services running on a VM and you want to assign each service a different IP address without quota limitation.", + "RationaleStatement": "With the feature of 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 your cluster to better interact with other Alibaba Cloud products and entities. Using ENI multiple IPs has several benefits: • Pod IPs are reserved within the network ahead of time, which prevents conflict with other compute resources. • Firewall controls for Pods can be applied separately from their nodes. • Alias IPs allow Pods to directly access hosted services without using a NAT gateway.", + "ImpactStatement": "", + "RemediationProcedure": "Only the Terway network plugin support the Network Policy feature, so please make sure not choose Flannel as network plugin when creating cluster. Using the management console: 1. Logon to ACK console 2. Click the Create Kubernetes Cluster button and select Terway in Network Plugin option.", + "AuditProcedure": "Using the management console: 1. Logon to ACK console 2. Select the target cluster name and go into the cluster detail page 3. Check if the meta of Network Plugin in Cluster Information is Terway", + "AdditionalInformation": "", + "References": "https://github.com/AliyunContainerService/terway/blob/master/README.md#eni- https://help.aliyun.com/document_detail/97467.html", + "DefaultValue": "By default, ENI multiple IP mode is not support in Flannel network plugin which is the default plugin when creating the cluster, and you should choose the Terway as the cluster network plugin when creating the cluster." + } + ], + "Checks": [ + "cs_kubernetes_eni_multiple_ip_enabled" + ] + }, + { + "Id": "7.9", + "Description": "Ensure Kubernetes Cluster is created with Private cluster enabled", + "Attributes": [ + { + "Section": "7. Kubernetes Engine", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "A private cluster is a cluster that makes your master inaccessible from the public internet. In a private cluster, nodes do not have public IP addresses, so your workloads run in an environment that is isolated from the internet. Nodes have addresses only in the private address space. Nodes and masters communicate with each other privately using VPC peering.", + "RationaleStatement": "With a Private cluster enabled, VPC network peering gives you several advantages over using external IP addresses or VPNs to connect networks, including: • Network Latency: Public IP networking suffers higher latency than private networking. • Network Security: Service owners do not need to have their services exposed to the public Internet to reduce any associated risks. • Network Cost: Alibaba Cloud charges egress bandwidth pricing for networks using external IPs to communicate even if the traffic is within the same zone. If, however, the networks are peered they can use internal IPs to communicate and save on those egress costs. Regular network pricing still applies to all traffic.", + "ImpactStatement": "", + "RemediationProcedure": "Using the management console: 1. Logon to ACK console 2. Click the Create Kubernetes Cluster button and make sure Public Access is not enabled.", + "AuditProcedure": "Using the management console: 1. Logon to ACK console 2. Select the target cluster name and go into the cluster detail page 3. Check if there is no meta of API Server Public Network Endpoint under Cluster Information", + "AdditionalInformation": "", + "References": "https://help.aliyun.com/document_detail/100380.html", + "DefaultValue": "By default, public access is not enabled when creating new cluster." + } + ], + "Checks": [ + "cs_kubernetes_private_cluster_enabled" + ] + }, + { + "Id": "8.1", + "Description": "Ensure that Security Center is Advanced or Enterprise Edition", + "Attributes": [ + { + "Section": "8. Security Center", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "The Advanced or Enterprise Edition enables threat detection for network and endpoints, providing malware detection, webshell detection and anomaly detection in Security Center.", + "RationaleStatement": "The Advanced or Enterprise Edition allows for full protection to defend cloud threats.", + "ImpactStatement": "Additional cost will be incurred by enabling other versions of Security Center", + "RemediationProcedure": "Using the management console: 1. Logon to Security Center Console. 2. Select Overview. 3. Click Upgrade. 4. Select Advanced or Enterprise Edition. 5. Finish order placement.", + "AuditProcedure": "Using the management console: 1. Logon to Security Center Console 2. Select Overview 3. Ensure Current Edition is Advanced or Enterprise Edition", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/product/28498.htm", + "DefaultValue": "Not installed." + } + ], + "Checks": [ + "securitycenter_advanced_or_enterprise_edition" + ] + }, + { + "Id": "8.2", + "Description": "Ensure that all assets are installed with security agent", + "Attributes": [ + { + "Section": "8. Security Center", + "Profile": "Level 2", + "AssessmentStatus": "Automated", + "Description": "Enable protection on all endpoints.", + "RationaleStatement": "The endpoint protection of Security requires an agent to be installed on the endpoint to work. Such an agent-based approach allows the security center to provide a set of more comprehensive endpoint intrusion detection and protection capabilities, such as includes remote logon detection, webshell detection and removal, anomaly detection (detection of abnormal process behaviors and abnormal network connections), and detection of changes in key files and suspicious accounts in systems and applications.", + "ImpactStatement": "Additional cost may be incurred by enabling Security Center and install the agent", + "RemediationProcedure": "Using the management console: 1. Logon to Security Center Console. 2. Select Settings. 3. Click Agent. 4. On Client to be installed tab, select all items on the list. 5. Click On-click installation to install the agent all asset.", + "AuditProcedure": "Using the management console: 1. Logon to Security Center Console. 2. Select Overview. 3. Ensure Unprotected Assets is 0.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/111650.htm", + "DefaultValue": "Not installed." + } + ], + "Checks": [ + "securitycenter_all_assets_agent_installed" + ] + }, + { + "Id": "8.3", + "Description": "Ensure that Automatic Quarantine is enabled", + "Attributes": [ + { + "Section": "8. Security Center", + "Profile": "Level 2", + "AssessmentStatus": "Manual", + "Description": "Enable automatic quarantine in Security Center may 1ncure additional cost.", + "RationaleStatement": "Once a virus is detected, the automatic quarantine feature prevents the virus from being executed.", + "ImpactStatement": "Enabling Automatic Quarantine in security center may incur additional cost", + "RemediationProcedure": "Using the management console: 1. Logon to Security Center Console. 2. Select Settings. 3. Click General. 4. Enable Virus Blocking.", + "AuditProcedure": "Using the management console: 1. Logon to Security Center Console. 2. Select Settings. 3. Click General. 4. Ensure Virus Blocking is enabled.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/111847.htm", + "DefaultValue": "Not enabled." + } + ], + "Checks": [] + }, + { + "Id": "8.4", + "Description": "Ensure that Webshell detection is enabled on all web servers", + "Attributes": [ + { + "Section": "8. Security Center", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "Enable webshell detection on all web servers to scans periodically the Web directories for detecting webshells on servers.", + "RationaleStatement": "Web servers are exposed to the Internet and they are commonly attacked through injected webshell by attackers.", + "ImpactStatement": "", + "RemediationProcedure": "Using the management console: 1. Logon to Security Center Console. 2. Select Settings. 3. Click General. 4. Click Manage in Webshell Detection. 5. Add all web servers.", + "AuditProcedure": "Using the management console: 1. Logon to Security Center Console. 2. Select Settings. 3. Click General. 4. Click Manage in Webshell Detection. 5. Ensure all web servers are included.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/111847.htm", + "DefaultValue": "Not enabled." + } + ], + "Checks": [] + }, + { + "Id": "8.5", + "Description": "Ensure that notification is enabled on all high risk items", + "Attributes": [ + { + "Section": "8. Security Center", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "Enable all risk item notification in Vulnerability, Baseline Risks, Alerts and Accesskey Leak event detection categories.", + "RationaleStatement": "To make sure that relevant security operators would receive notifications as soon as security events happens.", + "ImpactStatement": "", + "RemediationProcedure": "Using the management console: 1. Logon to Security Center Console. 2. Select Settings. 3. Click Notification. 4. Enable all high-risk items on Notification setting.", + "AuditProcedure": "Using the management console: 1. Logon to Security Center Console. 2. Select Settings. 3. Click Notification. 4. Review notification settings and ensure all high-risk items are enabled.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/111648.htm", + "DefaultValue": "Not enabled." + } + ], + "Checks": [ + "securitycenter_notification_enabled_high_risk" + ] + }, + { + "Id": "8.6", + "Description": "Ensure that Config Assessment is granted with privilege", + "Attributes": [ + { + "Section": "8. Security Center", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "Grant Security Centers Cloud Platform Configuration Assessment the privilege to access other cloud product.", + "RationaleStatement": "Prior to using Cloud Platform Configuration Assessment, it requires privilege to assess other cloud products settings.", + "ImpactStatement": "", + "RemediationProcedure": "Using the management console: 1. Logon to Security Center Console. 2. Select Config Assessment. 3. Click Authorize.", + "AuditProcedure": "Using the management console: 1. Logon to Security Center Console. 2. Select Config Assessment. 3. Ensure that the prompt of asking privilege is not shown.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/42302.htm", + "DefaultValue": "No privilege is authorized by default." + } + ], + "Checks": [] + }, + { + "Id": "8.7", + "Description": "Ensure that scheduled vulnerability scan is enabled on all servers", + "Attributes": [ + { + "Section": "8. Security Center", + "Profile": "Level 1", + "AssessmentStatus": "Automated", + "Description": "Ensure that scheduled vulnerability scan is enabled on all servers.", + "RationaleStatement": "Be sure that vulnerability scan is performed periodically to discover system vulnerabilities in time.", + "ImpactStatement": "", + "RemediationProcedure": "1. Login to Security Center Console. 2. Select Vulnerabilities. 3. Click Settings. 4. Apply all type of vulnerabilities. 5. Enable High and Medium vulnerabilities scan level.", + "AuditProcedure": "1. Logon to Security Center Console. 2. Select Vulnerabilities. 3. Click Settings. 4. Ensure that all type of vulnerabilities is enabled. 5. Ensure that High and Medium vulnerabilities scan level are enabled.", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/109076.htm", + "DefaultValue": "Not enabled." + } + ], + "Checks": [ + "securitycenter_vulnerability_scan_enabled" + ] + }, + { + "Id": "8.8", + "Description": "Ensure that Asset Fingerprint automatically collects asset fingerprint data", + "Attributes": [ + { + "Section": "8. Security Center", + "Profile": "Level 1", + "AssessmentStatus": "Manual", + "Description": "The Enterprise Edition enables asset fingerprint collection for endpoints providing a collection of port, software, processes, scheduled tasks and middleware in Security Center.", + "RationaleStatement": "The Enterprise Edition allows for enhanced investigation collection of artifacts to identify root cause in a more timely manner of single or multiple server instances hosted within the cloud.", + "ImpactStatement": "", + "RemediationProcedure": "Using the management console: 1. Logon to Security Center Console 2. Select Investigation> Asset Fingerprints 3. Click Setting and set the Refresh Frequencies 4. Set refresh frequency Automatic collection to Collected once a day", + "AuditProcedure": "Using the management console: 1. Logon to Security Center Console 2. Select Investigation > Asset Fingerprints 3. Click Settings 4. Ensure the Refresh Frequencies are all set to Collected once a day", + "AdditionalInformation": "", + "References": "https://www.alibabacloud.com/help/doc-detail/146565.htm", + "DefaultValue": "Not Enabled" + } + ], + "Checks": [] + } + ] +} diff --git a/prowler/config/config.py b/prowler/config/config.py index 9cd9360476..a56f4a86ba 100644 --- a/prowler/config/config.py +++ b/prowler/config/config.py @@ -60,6 +60,7 @@ class Provider(str, Enum): NHN = "nhn" MONGODBATLAS = "mongodbatlas" ORACLECLOUD = "oraclecloud" + ALIBABACLOUD = "alibabacloud" # Compliance diff --git a/prowler/lib/check/check.py b/prowler/lib/check/check.py index 32e7dcf9f5..42c812c1e1 100644 --- a/prowler/lib/check/check.py +++ b/prowler/lib/check/check.py @@ -683,6 +683,10 @@ def execute( is_finding_muted_args["organization_id"] = ( global_provider.identity.organization_id ) + elif global_provider.type == "alibabacloud": + is_finding_muted_args["account_id"] = ( + global_provider.identity.account_id + ) for finding in check_findings: if global_provider.type == "azure": is_finding_muted_args["subscription_id"] = ( diff --git a/prowler/lib/check/models.py b/prowler/lib/check/models.py index a50cfb244b..3f4a675d9f 100644 --- a/prowler/lib/check/models.py +++ b/prowler/lib/check/models.py @@ -649,6 +649,29 @@ class Check_Report_OCI(Check_Report): self.region = region or getattr(resource, "region", "") +@dataclass +class CheckReportAlibabaCloud(Check_Report): + """Contains the Alibaba Cloud Check's finding information.""" + + resource_id: str + resource_arn: str + region: str + + def __init__(self, metadata: Dict, resource: Any) -> None: + """Initialize the Alibaba Cloud Check's finding information. + + Args: + metadata: The metadata of the check. + resource: Basic information about the resource. + """ + super().__init__(metadata, resource) + self.resource_id = ( + getattr(resource, "id", None) or getattr(resource, "name", None) or "" + ) + self.resource_arn = getattr(resource, "arn", "") + self.region = getattr(resource, "region", "") + + @dataclass class Check_Report_Kubernetes(Check_Report): # TODO change class name to CheckReportKubernetes diff --git a/prowler/lib/check/utils.py b/prowler/lib/check/utils.py index 8cf37817c9..6d5ff69644 100644 --- a/prowler/lib/check/utils.py +++ b/prowler/lib/check/utils.py @@ -26,7 +26,7 @@ def recover_checks_from_provider( # We need to exclude common shared libraries in services if ( check_module_name.count(".") == 6 - and "lib" not in check_module_name + and ".lib." not in check_module_name and (not check_module_name.endswith("_fixer") or include_fixers) ): check_path = module_name.module_finder.path diff --git a/prowler/lib/cli/parser.py b/prowler/lib/cli/parser.py index 1b08337934..10b8c12abb 100644 --- a/prowler/lib/cli/parser.py +++ b/prowler/lib/cli/parser.py @@ -27,10 +27,10 @@ class ProwlerArgumentParser: self.parser = argparse.ArgumentParser( prog="prowler", formatter_class=RawTextHelpFormatter, - usage="prowler [-h] [--version] {aws,azure,gcp,kubernetes,m365,github,nhn,mongodbatlas,oraclecloud,dashboard,iac} ...", + usage="prowler [-h] [--version] {aws,azure,gcp,kubernetes,m365,github,nhn,mongodbatlas,oraclecloud,alibabacloud,dashboard,iac} ...", epilog=""" Available Cloud Providers: - {aws,azure,gcp,kubernetes,m365,github,iac,llm,nhn,mongodbatlas,oraclecloud} + {aws,azure,gcp,kubernetes,m365,github,iac,llm,nhn,mongodbatlas,oraclecloud,alibabacloud} aws AWS Provider azure Azure Provider gcp GCP Provider @@ -38,6 +38,7 @@ Available Cloud Providers: m365 Microsoft 365 Provider github GitHub Provider oraclecloud Oracle Cloud Infrastructure Provider + alibabacloud Alibaba Cloud Provider iac IaC Provider (Beta) llm LLM Provider (Beta) nhn NHN Provider (Unofficial) diff --git a/prowler/lib/outputs/compliance/cis/cis_alibabacloud.py b/prowler/lib/outputs/compliance/cis/cis_alibabacloud.py new file mode 100644 index 0000000000..adf8ef2af1 --- /dev/null +++ b/prowler/lib/outputs/compliance/cis/cis_alibabacloud.py @@ -0,0 +1,106 @@ +from prowler.config.config import timestamp +from prowler.lib.check.compliance_models import Compliance +from prowler.lib.outputs.compliance.cis.models import AlibabaCloudCISModel +from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput +from prowler.lib.outputs.finding import Finding + + +class AlibabaCloudCIS(ComplianceOutput): + """ + This class represents the Alibaba Cloud CIS compliance output. + + Attributes: + - _data (list): A list to store transformed data from findings. + - _file_descriptor (TextIOWrapper): A file descriptor to write data to a file. + + Methods: + - transform: Transforms findings into Alibaba Cloud CIS compliance format. + """ + + def transform( + self, + findings: list[Finding], + compliance: Compliance, + compliance_name: str, + ) -> None: + """ + Transforms a list of findings into Alibaba Cloud CIS compliance format. + + Parameters: + - findings (list): A list of findings. + - compliance (Compliance): A compliance model. + - compliance_name (str): The name of the compliance model. + + Returns: + - None + """ + for finding in findings: + # Get the compliance requirements for the finding + finding_requirements = finding.compliance.get(compliance_name, []) + for requirement in compliance.Requirements: + if requirement.Id in finding_requirements: + for attribute in requirement.Attributes: + compliance_row = AlibabaCloudCISModel( + Provider=finding.provider, + Description=compliance.Description, + AccountId=finding.account_uid, + Region=finding.region, + AssessmentDate=str(timestamp), + Requirements_Id=requirement.Id, + Requirements_Description=requirement.Description, + Requirements_Attributes_Section=attribute.Section, + Requirements_Attributes_SubSection=attribute.SubSection, + Requirements_Attributes_Profile=attribute.Profile, + Requirements_Attributes_AssessmentStatus=attribute.AssessmentStatus, + Requirements_Attributes_Description=attribute.Description, + Requirements_Attributes_RationaleStatement=attribute.RationaleStatement, + Requirements_Attributes_ImpactStatement=attribute.ImpactStatement, + Requirements_Attributes_RemediationProcedure=attribute.RemediationProcedure, + Requirements_Attributes_AuditProcedure=attribute.AuditProcedure, + Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, + Requirements_Attributes_DefaultValue=attribute.DefaultValue, + Requirements_Attributes_References=attribute.References, + Status=finding.status, + StatusExtended=finding.status_extended, + ResourceId=finding.resource_uid, + ResourceName=finding.resource_name, + CheckId=finding.check_id, + Muted=finding.muted, + Framework=compliance.Framework, + Name=compliance.Name, + ) + self._data.append(compliance_row) + # Add manual requirements to the compliance output + for requirement in compliance.Requirements: + if not requirement.Checks: + for attribute in requirement.Attributes: + compliance_row = AlibabaCloudCISModel( + Provider=compliance.Provider.lower(), + Description=compliance.Description, + AccountId="", + Region="", + AssessmentDate=str(timestamp), + Requirements_Id=requirement.Id, + Requirements_Description=requirement.Description, + Requirements_Attributes_Section=attribute.Section, + Requirements_Attributes_SubSection=attribute.SubSection, + Requirements_Attributes_Profile=attribute.Profile, + Requirements_Attributes_AssessmentStatus=attribute.AssessmentStatus, + Requirements_Attributes_Description=attribute.Description, + Requirements_Attributes_RationaleStatement=attribute.RationaleStatement, + Requirements_Attributes_ImpactStatement=attribute.ImpactStatement, + Requirements_Attributes_RemediationProcedure=attribute.RemediationProcedure, + Requirements_Attributes_AuditProcedure=attribute.AuditProcedure, + Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, + Requirements_Attributes_DefaultValue=attribute.DefaultValue, + Requirements_Attributes_References=attribute.References, + Status="MANUAL", + StatusExtended="Manual check", + ResourceId="manual_check", + ResourceName="Manual check", + CheckId="manual", + Muted=False, + Framework=compliance.Framework, + Name=compliance.Name, + ) + self._data.append(compliance_row) diff --git a/prowler/lib/outputs/compliance/cis/models.py b/prowler/lib/outputs/compliance/cis/models.py index 6f3bb45c5f..9e96060078 100644 --- a/prowler/lib/outputs/compliance/cis/models.py +++ b/prowler/lib/outputs/compliance/cis/models.py @@ -241,6 +241,40 @@ class OracleCloudCISModel(BaseModel): Name: str +class AlibabaCloudCISModel(BaseModel): + """ + AlibabaCloudCISModel generates a finding's output in Alibaba Cloud CIS Compliance format. + """ + + Provider: str + Description: str + AccountId: str + Region: str + AssessmentDate: str + Requirements_Id: str + Requirements_Description: str + Requirements_Attributes_Section: str + Requirements_Attributes_SubSection: Optional[str] = None + Requirements_Attributes_Profile: str + Requirements_Attributes_AssessmentStatus: str + Requirements_Attributes_Description: str + Requirements_Attributes_RationaleStatement: str + Requirements_Attributes_ImpactStatement: str + Requirements_Attributes_RemediationProcedure: str + Requirements_Attributes_AuditProcedure: str + Requirements_Attributes_AdditionalInformation: str + Requirements_Attributes_DefaultValue: Optional[str] = None + Requirements_Attributes_References: str + Status: str + StatusExtended: str + ResourceId: str + ResourceName: str + CheckId: str + Muted: bool + Framework: str + Name: str + + # Compliance models alias for backwards compatibility CIS_AWS = AWSCISModel CIS_Azure = AzureCISModel @@ -249,6 +283,7 @@ CIS_Kubernetes = KubernetesCISModel CIS_M365 = M365CISModel CIS_Github = GithubCISModel CIS_OracleCloud = OracleCloudCISModel +CIS_AlibabaCloud = AlibabaCloudCISModel # TODO: Create a parent class for the common fields of CIS and have the specific classes from each provider to inherit from it. diff --git a/prowler/lib/outputs/finding.py b/prowler/lib/outputs/finding.py index 2cc9d36341..4242116160 100644 --- a/prowler/lib/outputs/finding.py +++ b/prowler/lib/outputs/finding.py @@ -342,6 +342,22 @@ class Finding(BaseModel): output_data["resource_uid"] = check_output.resource_id output_data["region"] = check_output.region + elif provider.type == "alibabacloud": + output_data["auth_method"] = get_nested_attribute( + provider, "identity.identity_arn" + ) + output_data["account_uid"] = get_nested_attribute( + provider, "identity.account_id" + ) + output_data["account_name"] = get_nested_attribute( + provider, "identity.account_name" + ) + output_data["resource_name"] = check_output.resource_id + output_data["resource_uid"] = getattr( + check_output, "resource_arn", check_output.resource_id + ) + output_data["region"] = check_output.region + # check_output Unique ID # TODO: move this to a function # TODO: in Azure, GCP and K8s there are findings without resource_name diff --git a/prowler/lib/outputs/html/html.py b/prowler/lib/outputs/html/html.py index 965fe12d73..63a99b7491 100644 --- a/prowler/lib/outputs/html/html.py +++ b/prowler/lib/outputs/html/html.py @@ -1022,6 +1022,73 @@ class HTML(Output): ) return "" + @staticmethod + def get_alibabacloud_assessment_summary(provider: Provider) -> str: + """ + get_alibabacloud_assessment_summary gets the HTML assessment summary for the Alibaba Cloud provider + + Args: + provider (Provider): the Alibaba Cloud provider object + + Returns: + str: HTML assessment summary for the Alibaba Cloud provider + """ + try: + account_id = getattr(provider.identity, "account_id", "unknown") + account_name = getattr(provider.identity, "account_name", "") + audited_regions = getattr( + provider.identity, "audited_regions", "All Regions" + ) + identity_arn = getattr(provider.identity, "identity_arn", "unknown") + user_name = getattr(provider.identity, "user_name", "unknown") + + account_name_item = ( + f""" +
  • + Account Name: {account_name} +
  • """ + if account_name + else "" + ) + + return f""" +
    +
    +
    + Alibaba Cloud Assessment Summary +
    +
      +
    • + Account ID: {account_id} +
    • + {account_name_item} +
    • + Audited Regions: {audited_regions} +
    • +
    +
    +
    +
    +
    +
    + Alibaba Cloud Credentials +
    +
      +
    • + User Name: {user_name} +
    • +
    • + Identity ARN: {identity_arn} +
    • +
    +
    +
    """ + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}" + ) + return "" + @staticmethod def get_assessment_summary(provider: Provider) -> str: """ diff --git a/prowler/lib/outputs/outputs.py b/prowler/lib/outputs/outputs.py index 11355ea5f7..8ba7c3e614 100644 --- a/prowler/lib/outputs/outputs.py +++ b/prowler/lib/outputs/outputs.py @@ -30,6 +30,8 @@ def stdout_report(finding, color, verbose, status, fix): details = finding.check_metadata.CheckID if finding.check_metadata.Provider == "oraclecloud": details = finding.region + if finding.check_metadata.Provider == "alibabacloud": + details = finding.region if (verbose or fix) and (not status or finding.status in status): if finding.muted: diff --git a/prowler/lib/outputs/summary_table.py b/prowler/lib/outputs/summary_table.py index 747bae1821..daf65192bb 100644 --- a/prowler/lib/outputs/summary_table.py +++ b/prowler/lib/outputs/summary_table.py @@ -74,6 +74,9 @@ def display_summary_table( if provider.identity.tenancy_name != "unknown" else provider.identity.tenancy_id ) + elif provider.type == "alibabacloud": + entity_type = "Account" + audited_entities = provider.identity.account_id # Check if there are findings and that they are not all MANUAL if findings and not all(finding.status == "MANUAL" for finding in findings): diff --git a/prowler/providers/alibabacloud/__init__.py b/prowler/providers/alibabacloud/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/alibabacloud/alibabacloud_provider.py b/prowler/providers/alibabacloud/alibabacloud_provider.py new file mode 100644 index 0000000000..23ce01c18d --- /dev/null +++ b/prowler/providers/alibabacloud/alibabacloud_provider.py @@ -0,0 +1,872 @@ +import os +import pathlib + +from alibabacloud_credentials.client import Client as CredClient +from alibabacloud_credentials.models import Config as CredConfig +from alibabacloud_sts20150401.client import Client as StsClient +from alibabacloud_tea_openapi import models as open_api_models +from colorama import Fore, Style + +from prowler.config.config import ( + default_config_file_path, + get_default_mute_file_path, + load_and_validate_config_file, +) +from prowler.lib.logger import logger +from prowler.lib.utils.utils import print_boxes +from prowler.providers.alibabacloud.config import ( + ALIBABACLOUD_DEFAULT_REGION, + ALIBABACLOUD_REGIONS, + ROLE_SESSION_NAME, +) +from prowler.providers.alibabacloud.exceptions.exceptions import ( + AlibabaCloudInvalidCredentialsError, + AlibabaCloudNoCredentialsError, + AlibabaCloudSetUpSessionError, +) +from prowler.providers.alibabacloud.lib.mutelist.mutelist import AlibabaCloudMutelist +from prowler.providers.alibabacloud.models import ( + AlibabaCloudCallerIdentity, + AlibabaCloudIdentityInfo, + AlibabaCloudSession, +) +from prowler.providers.common.models import Audit_Metadata, Connection +from prowler.providers.common.provider import Provider + + +class AlibabacloudProvider(Provider): + """ + AlibabacloudProvider class is the main class for the Alibaba Cloud provider. + + This class is responsible for initializing the Alibaba Cloud provider, setting up the session, + validating the credentials, and setting the identity. + + Attributes: + _type (str): The provider type. + _identity (AlibabaCloudIdentityInfo): The Alibaba Cloud provider identity information. + _session (AlibabaCloudSession): The Alibaba Cloud provider session. + _audit_resources (list): The list of resources to audit. + _audit_config (dict): The audit configuration. + _enabled_regions (set): The set of enabled regions. + _mutelist (AlibabaCloudMutelist): The Alibaba Cloud provider mutelist. + audit_metadata (Audit_Metadata): The audit metadata. + """ + + _type: str = "alibabacloud" + _identity: AlibabaCloudIdentityInfo + _session: AlibabaCloudSession + _audit_resources: list = [] + _audit_config: dict + _fixer_config: dict + _regions: list = [] + _mutelist: AlibabaCloudMutelist + audit_metadata: Audit_Metadata + + def __init__( + self, + role_arn: str = None, + role_session_name: str = None, + ecs_ram_role: str = None, + oidc_role_arn: str = None, + credentials_uri: str = None, + regions: list = None, + config_path: str = None, + config_content: dict = None, + mutelist_path: str = None, + mutelist_content: dict = None, + fixer_config: dict = {}, + ): + """ + Initialize the AlibabaCloudProvider. + + Args: + role_arn: ARN of the RAM role to assume + role_session_name: Session name when assuming the RAM role + ecs_ram_role: Name of the RAM role attached to an ECS instance + oidc_role_arn: ARN of the RAM role for OIDC authentication + credentials_uri: URI to retrieve credentials from an external service + regions: List of Alibaba Cloud region IDs to audit (if None, audits all available regions) + config_path: Path to the configuration file + config_content: Content of the configuration file + mutelist_path: Path to the mutelist file + mutelist_content: Content of the mutelist file + fixer_config: Fixer configuration dictionary + + Raises: + AlibabaCloudSetUpSessionError: If an error occurs during the setup process. + AlibabaCloudInvalidCredentialsError: If authentication fails. + + Usage: + - Alibaba Cloud Credentials SDK is used, so we follow their credential setup process: + - Authentication: Make sure you have properly configured your credentials with environment variables. + - export ALIBABA_CLOUD_ACCESS_KEY_ID= + - export ALIBABA_CLOUD_ACCESS_KEY_SECRET= + or use other authentication methods (ECS RAM role, OIDC, etc.) + - To create a new Alibaba Cloud provider object: + - alibabacloud = AlibabacloudProvider() # Audits all regions + - alibabacloud = AlibabacloudProvider(regions=["cn-hangzhou", "cn-shanghai"]) # Specific regions + - alibabacloud = AlibabacloudProvider(role_arn="acs:ram::...:role/ProwlerRole") + - alibabacloud = AlibabacloudProvider(ecs_ram_role="ECS-Prowler-Role") + """ + logger.info("Initializing Alibaba Cloud Provider ...") + + # Setup Alibaba Cloud Session + logger.info("Setting up Alibaba Cloud session ...") + self._session = self.setup_session( + role_arn=role_arn, + role_session_name=role_session_name, + ecs_ram_role=ecs_ram_role, + oidc_role_arn=oidc_role_arn, + credentials_uri=credentials_uri, + ) + logger.info("Alibaba Cloud session configured successfully") + + # Validate credentials + logger.info("Validating credentials ...") + caller_identity = self.validate_credentials( + session=self._session, + region=ALIBABACLOUD_DEFAULT_REGION, + ) + logger.info("Credentials validated") + + # Get profile region + profile_region = self.get_profile_region() + + # Set identity + self._identity = self.set_identity( + caller_identity=caller_identity, + profile="default", + regions=set(), + profile_region=profile_region, + ) + + # Populate account alias if available + account_alias = self.get_account_alias() + if account_alias: + self._identity.account_name = account_alias + + # Get regions + self._regions = self.get_regions_to_audit(regions) + + # Audit Config + if config_content: + self._audit_config = config_content + else: + if not config_path: + config_path = default_config_file_path + self._audit_config = load_and_validate_config_file(self._type, config_path) + + # Fixer Config + self._fixer_config = fixer_config + + # Mutelist + if mutelist_content: + self._mutelist = AlibabaCloudMutelist( + mutelist_content=mutelist_content, + account_id=self._identity.account_id, + ) + else: + if not mutelist_path: + mutelist_path = get_default_mute_file_path(self.type) + self._mutelist = AlibabaCloudMutelist( + mutelist_path=mutelist_path, + account_id=self._identity.account_id, + ) + + # Set up audit resources (for filtering) + # Note: resource_tags not yet supported in AlibabaCloud provider CLI args + self._audit_resources = [] + + # Create audit metadata + self.audit_metadata = Audit_Metadata( + services_scanned=0, + expected_checks=[], + completed_checks=0, + audit_progress=0, + ) + + Provider.set_global_provider(self) + + @property + def type(self) -> str: + return self._type + + @property + def identity(self) -> AlibabaCloudIdentityInfo: + return self._identity + + @property + def session(self): + return self._session + + @property + def audit_config(self) -> dict: + return self._audit_config + + @property + def fixer_config(self) -> dict: + return self._fixer_config + + @property + def audit_resources(self) -> list: + return self._audit_resources + + @property + def mutelist(self) -> AlibabaCloudMutelist: + return self._mutelist + + @property + def regions(self) -> list: + return self._regions + + @property + def enabled_regions(self) -> set: + """ + Backward compatibility property for existing checks. + Returns a set of region IDs. + """ + return set([r.region_id for r in self._regions]) + + @staticmethod + def setup_session( + role_arn: str = None, + role_session_name: str = None, + ecs_ram_role: str = None, + oidc_role_arn: str = None, + credentials_uri: str = None, + ) -> AlibabaCloudSession: + """ + Set up the Alibaba Cloud session. + + Args: + role_arn: ARN of the RAM role to assume + role_session_name: Session name when assuming the RAM role + ecs_ram_role: Name of the RAM role attached to an ECS instance + oidc_role_arn: ARN of the RAM role for OIDC authentication + credentials_uri: URI to retrieve credentials from an external service + + Returns: + AlibabaCloudSession object + + Raises: + AlibabaCloudSetUpSessionError: If session setup fails + """ + try: + logger.debug("Creating Alibaba Cloud session ...") + + # Create credentials configuration + config = CredConfig() + + # Check for OIDC authentication parameters + oidc_provider_arn = None + oidc_token_file = None + + if oidc_role_arn and "ALIBABA_CLOUD_OIDC_PROVIDER_ARN" in os.environ: + oidc_provider_arn = os.environ["ALIBABA_CLOUD_OIDC_PROVIDER_ARN"] + + if "ALIBABA_CLOUD_OIDC_TOKEN_FILE" in os.environ: + oidc_token_file = os.environ["ALIBABA_CLOUD_OIDC_TOKEN_FILE"] + + # Check for credentials URI + if not credentials_uri and "ALIBABA_CLOUD_CREDENTIALS_URI" in os.environ: + credentials_uri = os.environ["ALIBABA_CLOUD_CREDENTIALS_URI"] + + # Check for ECS RAM role name (for ECS instance metadata credentials) + if not ecs_ram_role and "ALIBABA_CLOUD_ECS_METADATA" in os.environ: + ecs_ram_role = os.environ["ALIBABA_CLOUD_ECS_METADATA"] + + # Check for access key credentials from environment variables only + # Support both ALIBABA_CLOUD_* and ALIYUN_* prefixes for compatibility + # Note: We intentionally do NOT support credentials via CLI arguments for security reasons + access_key_id = None + access_key_secret = None + security_token = None + + if "ALIBABA_CLOUD_ACCESS_KEY_ID" in os.environ: + access_key_id = os.environ["ALIBABA_CLOUD_ACCESS_KEY_ID"] + elif "ALIYUN_ACCESS_KEY_ID" in os.environ: + access_key_id = os.environ["ALIYUN_ACCESS_KEY_ID"] + + if "ALIBABA_CLOUD_ACCESS_KEY_SECRET" in os.environ: + access_key_secret = os.environ["ALIBABA_CLOUD_ACCESS_KEY_SECRET"] + elif "ALIYUN_ACCESS_KEY_SECRET" in os.environ: + access_key_secret = os.environ["ALIYUN_ACCESS_KEY_SECRET"] + + # Check for STS security token (for temporary credentials) + if "ALIBABA_CLOUD_SECURITY_TOKEN" in os.environ: + security_token = os.environ["ALIBABA_CLOUD_SECURITY_TOKEN"] + + # Check for RAM role assumption from CLI arguments or environment + if ( + not role_arn + and "ALIBABA_CLOUD_ROLE_ARN" in os.environ + and not oidc_provider_arn + ): + # Only use ALIBABA_CLOUD_ROLE_ARN for RAM role assumption if OIDC is not configured + role_arn = os.environ["ALIBABA_CLOUD_ROLE_ARN"] + + if not role_session_name: + if "ALIBABA_CLOUD_ROLE_SESSION_NAME" in os.environ: + role_session_name = os.environ["ALIBABA_CLOUD_ROLE_SESSION_NAME"] + else: + role_session_name = ROLE_SESSION_NAME # Default from config.py + + # Priority order for credential types: + # 1. Credentials URI (for external credential services) + # 2. OIDC role ARN (for OIDC authentication in ACK/Kubernetes) + # 3. ECS RAM role (if running on ECS instance) + # 4. RAM role assumption (with access keys) + # 5. STS temporary credentials (with access keys and token) + # 6. Permanent access keys + # 7. Default credential chain (includes config file ~/.aliyun/config.json) + + if credentials_uri: + # Use URI to retrieve credentials from external service + config.type = "credentials_uri" + config.credentials_uri = credentials_uri + logger.info(f"Using credentials URI: {credentials_uri}") + elif oidc_role_arn and oidc_provider_arn and oidc_token_file: + # Use OIDC authentication + config.type = "oidc_role_arn" + config.role_arn = oidc_role_arn + config.oidc_provider_arn = oidc_provider_arn + config.oidc_token_file_path = oidc_token_file + config.role_session_name = role_session_name + logger.info(f"Using OIDC role assumption: {oidc_role_arn}") + elif ecs_ram_role: + # Use ECS instance metadata service to get credentials + config.type = "ecs_ram_role" + config.role_name = ecs_ram_role + logger.info(f"Using ECS RAM role credentials: {ecs_ram_role}") + elif access_key_id and access_key_secret: + # If RAM role is provided, use role assumption (SDK will automatically manage STS tokens) + if role_arn: + config.type = "ram_role_arn" + config.access_key_id = access_key_id + config.access_key_secret = access_key_secret + config.role_arn = role_arn + config.role_session_name = role_session_name + logger.info( + f"Using RAM role assumption: {role_arn} with session name: {role_session_name}" + ) + # If security token is provided, use STS credentials + elif security_token: + config.type = "sts" + config.access_key_id = access_key_id + config.access_key_secret = access_key_secret + config.security_token = security_token + logger.info("Using STS temporary credentials") + else: + config.type = "access_key" + config.access_key_id = access_key_id + config.access_key_secret = access_key_secret + logger.info("Using access key credentials") + else: + # Try to use default credential chain + logger.info( + "No explicit credentials provided, using default credential chain" + ) + + # Create credential client + try: + cred_client = CredClient(config) + except Exception as error: + if "invalid type option" in str(error): + raise AlibabaCloudNoCredentialsError( + file=pathlib.Path(__file__).name, + ) + raise error + + # Verify credentials by getting them + try: + cred = cred_client.get_credential() + if not cred.get_access_key_id() or not cred.get_access_key_secret(): + raise AlibabaCloudNoCredentialsError( + file=pathlib.Path(__file__).name, + ) + except AlibabaCloudNoCredentialsError: + raise + except Exception as error: + raise AlibabaCloudInvalidCredentialsError( + file=pathlib.Path(__file__).name, + original_exception=error, + ) + + # Create and return session object + return AlibabaCloudSession(cred_client) + + except (AlibabaCloudNoCredentialsError, AlibabaCloudInvalidCredentialsError): + raise + except Exception as error: + logger.critical( + f"AlibabaCloudSetUpSessionError[{error.__traceback__.tb_lineno}]: {error}" + ) + raise AlibabaCloudSetUpSessionError( + file=pathlib.Path(__file__).name, + original_exception=error, + ) + + @staticmethod + def validate_credentials( + session: AlibabaCloudSession, + region: str = ALIBABACLOUD_DEFAULT_REGION, + ) -> AlibabaCloudCallerIdentity: + """ + Validates the Alibaba Cloud credentials using STS GetCallerIdentity. + + Args: + session: The Alibaba Cloud session object. + + Returns: + AlibabaCloudCallerIdentity: An object containing the caller identity information. + + Raises: + AlibabaCloudInvalidCredentialsError: If credentials are invalid. + """ + try: + # Get credentials + cred = session.get_credentials() + + # Create STS client to get caller identity (similar to AWS GetCallerIdentity) + sts_config = open_api_models.Config( + access_key_id=cred.access_key_id, + access_key_secret=cred.access_key_secret, + ) + if cred.security_token: + sts_config.security_token = cred.security_token + sts_config.endpoint = f"sts.{region}.aliyuncs.com" + + sts_client = StsClient(sts_config) + + caller_identity = sts_client.get_caller_identity().body + + # Parse the identity information + account_id = getattr(caller_identity, "account_id", "") + identity_type = getattr(caller_identity, "identity_type", "") + principal_id = getattr(caller_identity, "principal_id", "") + arn = getattr(caller_identity, "arn", "") + + # Log the response for debugging + logger.debug( + f"STS GetCallerIdentity response - Account ID: {account_id}, Principal ID: {principal_id}, ARN: {arn}, Identity Type: {identity_type}" + ) + + if not account_id: + raise ValueError("STS GetCallerIdentity did not return account_id") + + return AlibabaCloudCallerIdentity( + account_id=account_id, + principal_id=principal_id, + arn=arn, + identity_type=identity_type, + ) + + except Exception as sts_error: + logger.error(f"Could not get caller identity from STS: {sts_error}. ") + raise AlibabaCloudInvalidCredentialsError( + file=pathlib.Path(__file__).name, + original_exception=sts_error, + ) + + @staticmethod + def get_profile_region() -> str: + """ + Get the profile region. + + Returns: + str: The profile region + """ + # For now, return default region + # This can be enhanced to read from config file if needed + return ALIBABACLOUD_DEFAULT_REGION + + @staticmethod + def set_identity( + caller_identity: AlibabaCloudCallerIdentity, + profile: str, + regions: set, + profile_region: str, + ) -> AlibabaCloudIdentityInfo: + """ + Set the Alibaba Cloud provider identity information. + + Args: + caller_identity: The Alibaba Cloud caller identity information. + profile: The profile name. + regions: A set of regions to audit. + profile_region: The profile region. + + Returns: + AlibabaCloudIdentityInfo: The Alibaba Cloud provider identity information. + """ + logger.info( + f"Alibaba Cloud Caller Identity Account ID: {caller_identity.account_id}" + ) + logger.info(f"Alibaba Cloud Caller Identity ARN: {caller_identity.arn}") + + # Determine if this is root account or RAM user + # Root account ARN format: acs:ram::{account_id}:root + # RAM user ARN format: acs:ram::{account_id}:user/{user_name} + is_root = False + user_name = "" + user_id = caller_identity.principal_id + + if caller_identity.arn: + if ":root" in caller_identity.arn: + # This is the root account + is_root = True + user_name = "root" + elif ":user/" in caller_identity.arn: + # This is a RAM user + user_name = caller_identity.arn.split(":user/")[-1] + else: + # Fallback: use identity_type to determine + if ( + caller_identity.identity_type == "RamUser" + or caller_identity.identity_type == "User" + ): + # This is a RAM user, extract user name from principal_id if possible + # Principal ID format for RAM users is often: {user_id}@{account_id} + if "@" in caller_identity.principal_id: + # Try to get user name from RAM API using the user_id + user_name = caller_identity.principal_id.split("@")[0] + else: + user_name = caller_identity.principal_id + is_root = False + elif ( + caller_identity.identity_type == "Root" + or caller_identity.identity_type == "" + ): + # Empty identity_type or Root indicates root account + user_name = "root" + is_root = True + else: + # Unknown identity type - default to RAM user (safer assumption) + logger.warning( + f"Unknown identity type '{caller_identity.identity_type}' from ARN '{caller_identity.arn}'. " + "Assuming RAM user." + ) + user_name = ( + caller_identity.principal_id + if caller_identity.principal_id + else "unknown" + ) + is_root = False + + # Use the ARN from caller_identity if available, otherwise construct it + # Similar to AWS which uses caller_identity.arn.arn directly + if caller_identity.arn: + identity_arn = caller_identity.arn + else: + # Fallback: construct ARN if not provided + identity_arn = ( + f"acs:ram::{caller_identity.account_id}:root" + if is_root + else f"acs:ram::{caller_identity.account_id}:user/{user_name}" + ) + + logger.info(f"Alibaba Cloud Identity ARN: {identity_arn}") + + return AlibabaCloudIdentityInfo( + account_id=caller_identity.account_id, + account_name="", + user_id=user_id, + user_name=user_name, + identity_arn=identity_arn, + is_root=is_root, + profile=profile, + profile_region=profile_region, + audited_regions=regions, + ) + + def get_regions_to_audit(self, regions: list = None) -> list: + """ + get_regions_to_audit returns the list of regions to audit. + + Args: + regions: List of Alibaba Cloud region IDs to audit. + + Returns: + list: The list of AlibabaCloudRegion objects to audit. + """ + from prowler.providers.alibabacloud.models import AlibabaCloudRegion + + region_list = [] + + if regions: + # Audit specific regions provided by user + for region_id in regions: + if region_id in ALIBABACLOUD_REGIONS: + region_list.append( + AlibabaCloudRegion( + region_id=region_id, + region_name=ALIBABACLOUD_REGIONS.get(region_id, region_id), + ) + ) + else: + logger.warning(f"Invalid region: {region_id}. Skipping.") + else: + # Audit ALL available regions by default + for region_id, region_name in ALIBABACLOUD_REGIONS.items(): + region_list.append( + AlibabaCloudRegion( + region_id=region_id, + region_name=region_name, + ) + ) + + logger.info(f"Found {len(region_list)} regions to audit") + + # Update identity with audited regions + if hasattr(self, "_identity") and self._identity: + self._identity.audited_regions = set([r.region_id for r in region_list]) + + return region_list + + def get_account_alias(self) -> str: + """ + Retrieve the Alibaba Cloud account alias from RAM. + + Returns: + str: Account alias if available, otherwise empty string. + """ + try: + ram_client = self._session.client("ram") + response = ram_client.get_account_alias() + account_alias = getattr(response.body, "account_alias", "") or "" + if account_alias: + logger.info(f"Alibaba Cloud Account Alias: {account_alias}") + return account_alias + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return "" + + def setup_audit_config(self, input_config: dict) -> dict: + """ + Set up the audit configuration. + + Args: + input_config: Input configuration dictionary + + Returns: + Audit configuration dictionary + """ + # Merge with defaults + audit_config = { + "shodan_api_key": None, + **input_config, + } + return audit_config + + def print_credentials(self): + """ + Print the Alibaba Cloud credentials. + + This method prints the Alibaba Cloud credentials used by the provider. + + Example output: + ``` + Using the Alibaba Cloud credentials below: + Alibaba Cloud Account: 1234567890 + User Name: prowler-user + Regions: cn-hangzhou, cn-shanghai + ``` + """ + # Beautify audited regions + regions_str = ( + ", ".join([r.region_id for r in self._regions]) + if self._regions + else "default regions" + ) + + report_lines = [ + f"Alibaba Cloud Account: {Fore.YELLOW}{self.identity.account_id}{Style.RESET_ALL}", + f"User ID: {Fore.YELLOW}{self.identity.user_id}{Style.RESET_ALL}", + f"User Name: {Fore.YELLOW}{self.identity.user_name}{Style.RESET_ALL}", + f"Regions: {Fore.YELLOW}{regions_str}{Style.RESET_ALL}", + ] + + report_title = ( + f"{Style.BRIGHT}Using the Alibaba Cloud credentials below:{Style.RESET_ALL}" + ) + print_boxes(report_lines, report_title) + + @staticmethod + def test_connection( + role_arn: str = None, + role_session_name: str = None, + ecs_ram_role: str = None, + oidc_role_arn: str = None, + credentials_uri: str = None, + raise_on_exception: bool = True, + provider_id: str = None, + ) -> Connection: + """ + Test the connection to Alibaba Cloud with the provided credentials. + + Args: + role_arn: ARN of the RAM role to assume + role_session_name: Session name when assuming the RAM role + ecs_ram_role: Name of the RAM role attached to an ECS instance + oidc_role_arn: ARN of the RAM role for OIDC authentication + credentials_uri: URI to retrieve credentials from an external service + raise_on_exception: Whether to raise an exception if an error occurs + provider_id: The expected account ID to validate against + + Returns: + Connection: An object that contains the result of the test connection operation. + - is_connected (bool): Indicates whether the validation was successful. + - error (Exception): An exception object if an error occurs during the validation. + + Raises: + AlibabaCloudSetUpSessionError: If there is an error setting up the session. + AlibabaCloudInvalidCredentialsError: If there is an authentication error. + Exception: If there is an unexpected error. + + Examples: + >>> AlibabacloudProvider.test_connection(raise_on_exception=False) + Connection(is_connected=True, Error=None) + >>> AlibabacloudProvider.test_connection( + role_arn="acs:ram::123456789012:role/ProwlerRole", + provider_id="123456789012", + raise_on_exception=False + ) + Connection(is_connected=True, Error=None) + """ + try: + session = None + + # Setup session + session = AlibabacloudProvider.setup_session( + role_arn=role_arn, + role_session_name=role_session_name, + ecs_ram_role=ecs_ram_role, + oidc_role_arn=oidc_role_arn, + credentials_uri=credentials_uri, + ) + + # Validate credentials + caller_identity = AlibabacloudProvider.validate_credentials( + session=session, + region=ALIBABACLOUD_DEFAULT_REGION, + ) + + # Validate provider_id if provided + if provider_id and caller_identity.account_id != provider_id: + from prowler.providers.alibabacloud.exceptions.exceptions import ( + AlibabaCloudInvalidCredentialsError, + ) + + raise AlibabaCloudInvalidCredentialsError( + file=pathlib.Path(__file__).name, + message=f"Provider ID mismatch: expected '{provider_id}', got '{caller_identity.account_id}'", + ) + + logger.info( + f"Successfully connected to Alibaba Cloud account: {caller_identity.account_id}" + ) + + return Connection(is_connected=True) + + except AlibabaCloudSetUpSessionError as setup_error: + logger.error( + f"{setup_error.__class__.__name__}[{setup_error.__traceback__.tb_lineno}]: {setup_error}" + ) + if raise_on_exception: + raise setup_error + return Connection(error=setup_error) + + except AlibabaCloudInvalidCredentialsError as auth_error: + logger.error( + f"{auth_error.__class__.__name__}[{auth_error.__traceback__.tb_lineno}]: {auth_error}" + ) + if raise_on_exception: + raise auth_error + return Connection(error=auth_error) + + except Exception as error: + logger.critical( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + if raise_on_exception: + raise error + return Connection(error=error) + + def generate_regional_clients(self, service: str) -> dict: + """ + generate_regional_clients returns a dict with regional clients for the given service. + + Args: + service: The service name (e.g., 'ecs', 'vpc', 'oss'). + + Returns: + dict: A dictionary with region keys and Alibaba Cloud service client values. + + Example: + {"cn-hangzhou": alibabacloud_service_client, "cn-shanghai": alibabacloud_service_client} + """ + try: + regional_clients = {} + + # For each enabled region, create a client + for region in self._regions: + try: + client = self._session.client(service, region.region_id) + if client: + # Attach region information to the client + client.region = region.region_id + regional_clients[region.region_id] = client + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + return regional_clients + + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return {} + + def get_default_region(self, service: str) -> str: + """ + Get the default region for a service. + + Args: + service: The service name + + Returns: + The default region ID + """ + # Return the first enabled region or the default + if self.enabled_regions: + return sorted(list(self.enabled_regions))[0] + return ALIBABACLOUD_DEFAULT_REGION + + def get_checks_to_execute_by_audit_resources(self): + """ + Get the checks to execute based on audit resources. + + Returns: + Set of check names to execute + """ + # This would filter checks based on resources to audit + # For now, return empty set (no filtering) + return set() + + @staticmethod + def get_regions() -> dict: + """ + Get the available Alibaba Cloud regions. + + Returns: + dict: A dictionary of region IDs and region names. + + Example: + >>> AlibabacloudProvider.get_regions() + {"cn-hangzhou": "China (Hangzhou)", "cn-shanghai": "China (Shanghai)", ...} + """ + return ALIBABACLOUD_REGIONS diff --git a/prowler/providers/alibabacloud/config.py b/prowler/providers/alibabacloud/config.py new file mode 100644 index 0000000000..122e2a1b26 --- /dev/null +++ b/prowler/providers/alibabacloud/config.py @@ -0,0 +1,41 @@ +"""Alibaba Cloud Provider Configuration Constants""" + +ALIBABACLOUD_DEFAULT_REGION = "cn-hangzhou" +ROLE_SESSION_NAME = "ProwlerAssessmentSession" + +# Alibaba Cloud SDK Configuration +ALIBABACLOUD_SDK_READ_TIMEOUT = 60 # seconds +ALIBABACLOUD_SDK_CONNECT_TIMEOUT = 10 # seconds + +# Alibaba Cloud Regions - Only publicly accessible regions +# Note: Some regions may require special approval or are not globally available +ALIBABACLOUD_REGIONS = { + # China Regions + "cn-qingdao": "China (Qingdao)", + "cn-beijing": "China (Beijing)", + "cn-zhangjiakou": "China (Zhangjiakou)", + "cn-huhehaote": "China (Hohhot)", + "cn-wulanchabu": "China (Ulanqab)", + "cn-hangzhou": "China (Hangzhou)", + "cn-shanghai": "China (Shanghai)", + "cn-shenzhen": "China (Shenzhen)", + "cn-heyuan": "China (Heyuan)", + "cn-guangzhou": "China (Guangzhou)", + "cn-chengdu": "China (Chengdu)", + "cn-hongkong": "China (Hong Kong)", + # Asia-Pacific Regions + "ap-northeast-1": "Japan (Tokyo)", + "ap-northeast-2": "South Korea (Seoul)", + "ap-southeast-1": "Singapore", + "ap-southeast-3": "Malaysia (Kuala Lumpur)", + "ap-southeast-5": "Indonesia (Jakarta)", + "ap-southeast-6": "Philippines (Manila)", + "ap-southeast-7": "Thailand (Bangkok)", + # US Regions + "us-east-1": "US (Virginia)", + "us-west-1": "US (Silicon Valley)", + # Europe & Middle East Regions + "eu-west-1": "UK (London)", + "me-east-1": "UAE (Dubai)", + "eu-central-1": "Germany (Frankfurt)", +} diff --git a/prowler/providers/alibabacloud/exceptions/__init__.py b/prowler/providers/alibabacloud/exceptions/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/alibabacloud/exceptions/exceptions.py b/prowler/providers/alibabacloud/exceptions/exceptions.py new file mode 100644 index 0000000000..9cd921124e --- /dev/null +++ b/prowler/providers/alibabacloud/exceptions/exceptions.py @@ -0,0 +1,116 @@ +from prowler.exceptions.exceptions import ProwlerException + + +# Exceptions codes from 10000 to 10999 are reserved for AlibabaCloud exceptions +class AlibabaCloudBaseException(ProwlerException): + """Base class for Alibaba Cloud Provider exceptions""" + + ALIBABACLOUD_ERROR_CODES = { + (10000, "AlibabaCloudClientError"): { + "message": "Alibaba Cloud ClientError occurred", + "remediation": "Check your Alibaba Cloud client configuration and permissions.", + }, + (10001, "AlibabaCloudNoCredentialsError"): { + "message": "No credentials found for Alibaba Cloud provider", + "remediation": "Verify that Alibaba Cloud credentials are properly set up. Access Key ID and Access Key Secret are required.", + }, + (10002, "AlibabaCloudInvalidCredentialsError"): { + "message": "Invalid credentials provided for Alibaba Cloud provider", + "remediation": "Check your Alibaba Cloud credentials and ensure they are valid and have proper permissions.", + }, + (10003, "AlibabaCloudSetUpSessionError"): { + "message": "Failed to set up session for Alibaba Cloud provider", + "remediation": "Check the Alibaba Cloud session setup and ensure it is properly configured.", + }, + (10004, "AlibabaCloudAssumeRoleError"): { + "message": "Failed to assume role for Alibaba Cloud provider", + "remediation": "Check the Alibaba Cloud assume role configuration and ensure it is properly set up.", + }, + (10005, "AlibabaCloudInvalidRegionError"): { + "message": "Invalid region specified for Alibaba Cloud provider", + "remediation": "Check the region and ensure it is a valid region for Alibaba Cloud.", + }, + (10006, "AlibabaCloudArgumentTypeValidationError"): { + "message": "Alibaba Cloud argument type validation error", + "remediation": "Check the provided argument types specific to Alibaba Cloud and ensure they meet the required format.", + }, + (10007, "AlibabaCloudHTTPError"): { + "message": "Alibaba Cloud HTTP/API error", + "remediation": "Check the Alibaba Cloud API request and response, and ensure the service is accessible.", + }, + } + + def __init__(self, code, file=None, original_exception=None, message=None): + error_info = self.ALIBABACLOUD_ERROR_CODES.get((code, self.__class__.__name__)) + if message: + error_info["message"] = message + super().__init__( + code, + source="AlibabaCloud", + file=file, + original_exception=original_exception, + error_info=error_info, + ) + + +class AlibabaCloudCredentialsError(AlibabaCloudBaseException): + """Base class for Alibaba Cloud credentials errors.""" + + def __init__(self, code, file=None, original_exception=None, message=None): + super().__init__(code, file, original_exception, message) + + +class AlibabaCloudClientError(AlibabaCloudCredentialsError): + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + 10000, file=file, original_exception=original_exception, message=message + ) + + +class AlibabaCloudNoCredentialsError(AlibabaCloudCredentialsError): + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + 10001, file=file, original_exception=original_exception, message=message + ) + + +class AlibabaCloudInvalidCredentialsError(AlibabaCloudCredentialsError): + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + 10002, file=file, original_exception=original_exception, message=message + ) + + +class AlibabaCloudSetUpSessionError(AlibabaCloudBaseException): + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + 10003, file=file, original_exception=original_exception, message=message + ) + + +class AlibabaCloudAssumeRoleError(AlibabaCloudBaseException): + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + 10004, file=file, original_exception=original_exception, message=message + ) + + +class AlibabaCloudInvalidRegionError(AlibabaCloudBaseException): + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + 10005, file=file, original_exception=original_exception, message=message + ) + + +class AlibabaCloudArgumentTypeValidationError(AlibabaCloudBaseException): + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + 10006, file=file, original_exception=original_exception, message=message + ) + + +class AlibabaCloudHTTPError(AlibabaCloudBaseException): + def __init__(self, file=None, original_exception=None, message=None): + super().__init__( + 10007, file=file, original_exception=original_exception, message=message + ) diff --git a/prowler/providers/alibabacloud/lib/__init__.py b/prowler/providers/alibabacloud/lib/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/alibabacloud/lib/arguments/__init__.py b/prowler/providers/alibabacloud/lib/arguments/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/alibabacloud/lib/arguments/arguments.py b/prowler/providers/alibabacloud/lib/arguments/arguments.py new file mode 100644 index 0000000000..88bc1ca150 --- /dev/null +++ b/prowler/providers/alibabacloud/lib/arguments/arguments.py @@ -0,0 +1,58 @@ +def init_parser(self): + """Init the Alibaba Cloud Provider CLI parser""" + alibabacloud_parser = self.subparsers.add_parser( + "alibabacloud", + parents=[self.common_providers_parser], + help="Alibaba Cloud Provider", + ) + + # Authentication Methods + alibabacloud_auth_subparser = alibabacloud_parser.add_argument_group( + "Authentication Modes" + ) + alibabacloud_auth_subparser.add_argument( + "--role-arn", + nargs="?", + default=None, + help="ARN of the RAM role to assume (e.g., acs:ram::123456789012:role/ProwlerAuditRole). Requires access keys to be set via environment variables (ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET). The provider will automatically obtain and refresh STS tokens. Can also use ALIBABA_CLOUD_ROLE_ARN environment variable", + ) + alibabacloud_auth_subparser.add_argument( + "--role-session-name", + nargs="?", + default=None, + help="Session name when assuming the RAM role. Defaults to ProwlerAssessmentSession. Can also use ALIBABA_CLOUD_ROLE_SESSION_NAME environment variable", + ) + alibabacloud_auth_subparser.add_argument( + "--ecs-ram-role", + nargs="?", + default=None, + help="Name of the RAM role attached to an ECS instance. When specified, credentials are obtained from the ECS instance metadata service. Can also use ALIBABA_CLOUD_ECS_METADATA environment variable", + ) + alibabacloud_auth_subparser.add_argument( + "--oidc-role-arn", + nargs="?", + default=None, + help="ARN of the RAM role for OIDC authentication. Requires OIDC provider ARN and token file to be set via environment variables (ALIBABA_CLOUD_OIDC_PROVIDER_ARN and ALIBABA_CLOUD_OIDC_TOKEN_FILE). Can also use ALIBABA_CLOUD_ROLE_ARN environment variable", + ) + alibabacloud_auth_subparser.add_argument( + "--credentials-uri", + nargs="?", + default=None, + help="URI to retrieve credentials from an external service. The URI must return credentials in the required JSON format. Can also use ALIBABA_CLOUD_CREDENTIALS_URI environment variable", + ) + + # Alibaba Cloud Regions + alibabacloud_regions_subparser = alibabacloud_parser.add_argument_group( + "Alibaba Cloud Regions" + ) + alibabacloud_regions_subparser.add_argument( + "--region", + "--filter-region", + "-f", + nargs="+", + dest="regions", + help="Alibaba Cloud region IDs to run Prowler against (e.g., cn-hangzhou, cn-shanghai)", + ) + + # Set the provider + alibabacloud_parser.set_defaults(provider="alibabacloud") diff --git a/prowler/providers/alibabacloud/lib/mutelist/__init__.py b/prowler/providers/alibabacloud/lib/mutelist/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/alibabacloud/lib/mutelist/mutelist.py b/prowler/providers/alibabacloud/lib/mutelist/mutelist.py new file mode 100644 index 0000000000..d61a9f7a49 --- /dev/null +++ b/prowler/providers/alibabacloud/lib/mutelist/mutelist.py @@ -0,0 +1,175 @@ +from prowler.lib.check.models import CheckReportAlibabaCloud +from prowler.lib.logger import logger +from prowler.lib.mutelist.mutelist import Mutelist +from prowler.lib.outputs.utils import unroll_tags + + +class AlibabaCloudMutelist(Mutelist): + """ + AlibabaCloudMutelist class extends the base Mutelist for Alibaba Cloud-specific functionality. + + This class handles muting/filtering of findings for Alibaba Cloud resources. + + Attributes: + account_id: The Alibaba Cloud account ID + mutelist: The parsed mutelist data + """ + + def __init__( + self, + mutelist_path: str = None, + mutelist_content: dict = None, + account_id: str = "", + ): + """ + Initialize the AlibabaCloudMutelist. + + Args: + mutelist_path: Path to the mutelist file + mutelist_content: Dictionary containing mutelist content + account_id: The Alibaba Cloud account ID + """ + self.account_id = account_id + super().__init__( + mutelist_path=mutelist_path or "", + mutelist_content=mutelist_content or {}, + ) + + def is_finding_muted( + self, + finding: CheckReportAlibabaCloud, + account_id: str, + ) -> bool: + """ + Check if a finding is muted based on the mutelist. + + Args: + finding: The finding object to check (should have check_metadata, region, resource_id, resource_tags). + account_id: The Alibaba Cloud account ID to use for mutelist evaluation. + + Returns: + bool: True if the finding is muted, False otherwise. + """ + try: + check_id = finding.check_metadata.CheckID + region = finding.region if hasattr(finding, "region") else "" + resource_id = finding.resource_id if hasattr(finding, "resource_id") else "" + resource_tags = {} + + # Handle resource tags + if hasattr(finding, "resource_tags") and finding.resource_tags: + # Keep as dict for tag matching logic; do not unroll to string + resource_tags = unroll_tags(finding.resource_tags) + + return self.is_muted( + account_id, + check_id, + region, + resource_id, + resource_tags, + ) + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return False + + def is_muted( + self, + account_id: str, + check_id: str, + region: str, + resource_id: str, + resource_tags: dict = None, + ) -> bool: + """ + Check if a finding should be muted. + + Args: + account_id: The Alibaba Cloud account ID + check_id: The check ID + region: The region ID + resource_id: The resource ID + resource_tags: Dictionary of resource tags + + Returns: + True if the finding should be muted, False otherwise + """ + if not self.mutelist: + return False + + try: + # Check account-level mutes + accounts = self.mutelist.get("Accounts", {}) + if not accounts: + return False + + # Check for wildcard or specific account + account_mutelist = accounts.get("*", {}) + if account_id in accounts: + # Merge with specific account rules + specific_account = accounts.get(account_id, {}) + account_mutelist = {**account_mutelist, **specific_account} + + if not account_mutelist: + return False + + # Get checks for this account + checks = account_mutelist.get("Checks", {}) + + # Check for wildcard or specific check + check_mutelist = checks.get("*", {}) + if check_id in checks: + specific_check = checks.get(check_id, {}) + check_mutelist = {**check_mutelist, **specific_check} + + if not check_mutelist: + return False + + # Check regions + regions = check_mutelist.get("Regions", []) + if regions and "*" not in regions and region not in regions: + return False + + # Check resources + resources = check_mutelist.get("Resources", []) + if resources: + if "*" not in resources and resource_id not in resources: + return False + + # Check tags + tags = check_mutelist.get("Tags", []) + if tags and resource_tags: + # Check if any tag matches + tag_match = False + for tag_filter in tags: + # Tag filter format: "key=value" or "key=*" + if "=" in tag_filter: + key, value = tag_filter.split("=", 1) + if key in resource_tags: + if value == "*" or resource_tags[key] == value: + tag_match = True + break + + if not tag_match: + return False + + # Check exceptions (resources that should NOT be muted) + exceptions = check_mutelist.get("Exceptions", {}) + if exceptions: + exception_resources = exceptions.get("Resources", []) + if resource_id in exception_resources: + return False + + exception_regions = exceptions.get("Regions", []) + if region in exception_regions: + return False + + # If we passed all checks, the finding is muted + return True + + except Exception as error: + logger.error( + f"Error checking mutelist: {error.__class__.__name__}: {error}" + ) + return False diff --git a/prowler/providers/alibabacloud/lib/service/__init__.py b/prowler/providers/alibabacloud/lib/service/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/alibabacloud/lib/service/service.py b/prowler/providers/alibabacloud/lib/service/service.py new file mode 100644 index 0000000000..b58c06416a --- /dev/null +++ b/prowler/providers/alibabacloud/lib/service/service.py @@ -0,0 +1,113 @@ +from concurrent.futures import ThreadPoolExecutor, as_completed +from typing import Any, Dict + +from prowler.lib.logger import logger + +MAX_WORKERS = 10 + + +class AlibabaCloudService: + """ + The AlibabaCloudService class offers a parent class for each Alibaba Cloud Service to generate: + - Alibaba Cloud Regional Clients + - Shared information like the account ID, the checks audited + - Thread pool for the __threading_call__ + - Handles if the service is Regional or Global + """ + + def __init__(self, service: str, provider, global_service: bool = False): + """ + Initialize the AlibabaCloudService. + + Args: + service: The service name (e.g., 'RAM', 'ECS', 'OSS') + provider: The AlibabaCloudProvider instance + global_service: Whether this is a global service (default: False) + """ + # Audit Information + self.provider = provider + self.audited_account = provider.identity.account_id + self.audited_account_name = provider.identity.account_name + self.audit_resources = provider.audit_resources + self.audited_checks = provider.audit_metadata.expected_checks + self.audit_config = provider.audit_config + + # Session + self.session = provider.session + + # Service name + self.service = service.lower() if not service.islower() else service + + # Generate Regional Clients + self.regional_clients: Dict[str, Any] = {} + if not global_service: + self.regional_clients = provider.generate_regional_clients(self.service) + + # Get default region and client + self.region = provider.get_default_region(self.service) + self.client = self.session.client(self.service, self.region) + + # Thread pool for __threading_call__ + self.thread_pool = ThreadPoolExecutor(max_workers=MAX_WORKERS) + + def __get_session__(self): + """Get the session.""" + return self.session + + def __get_client__(self, region: str = None): + """ + Get a client for the specified region or the default region. + + Args: + region: The region to get the client for (optional) + + Returns: + A client instance for the service + """ + if region and region in self.regional_clients: + return self.regional_clients[region] + return self.client + + def __threading_call__(self, call, iterator=None): + """ + Execute a function across multiple regions or items using threads. + + Args: + call: The function to call + iterator: The items to iterate over (default: regional clients) + """ + # Use the provided iterator, or default to self.regional_clients + items = iterator if iterator is not None else self.regional_clients.values() + # Determine the total count for logging + item_count = ( + len(list(items)) if iterator is not None else len(self.regional_clients) + ) + + # Trim leading and trailing underscores from the call's name + call_name = call.__name__.strip("_") + # Add Capitalization + call_name = " ".join([x.capitalize() for x in call_name.split("_")]) + + # Print a message based on the call's name + if iterator is None: + logger.info( + f"{self.service.upper()} - Starting threads for '{call_name}' function across {item_count} regions..." + ) + else: + logger.info( + f"{self.service.upper()} - Starting threads for '{call_name}' function to process {item_count} items..." + ) + + # Re-create the iterator for submission if it was a generator + items = iterator if iterator is not None else self.regional_clients.values() + + # Submit tasks to the thread pool + futures = [self.thread_pool.submit(call, item) for item in items] + + # Wait for all tasks to complete + for future in as_completed(futures): + try: + future.result() # Raises exceptions from the thread, if any + except Exception: + # Handle exceptions if necessary + pass # Currently handled within the called function diff --git a/prowler/providers/alibabacloud/models.py b/prowler/providers/alibabacloud/models.py new file mode 100644 index 0000000000..cbb57044fc --- /dev/null +++ b/prowler/providers/alibabacloud/models.py @@ -0,0 +1,266 @@ +from datetime import datetime +from typing import Optional + +from alibabacloud_actiontrail20200706.client import Client as ActionTrailClient +from alibabacloud_cs20151215.client import Client as CSClient +from alibabacloud_ecs20140526.client import Client as EcsClient +from alibabacloud_oss20190517.client import Client as OssClient +from alibabacloud_ram20150501.client import Client as RamClient +from alibabacloud_rds20140815.client import Client as RdsClient +from alibabacloud_sas20181203.client import Client as SasClient +from alibabacloud_sls20201230.client import Client as SlsClient +from alibabacloud_tea_openapi import models as open_api_models +from alibabacloud_vpc20160428.client import Client as VpcClient +from pydantic.v1 import BaseModel + +from prowler.lib.logger import logger +from prowler.providers.alibabacloud.config import ( + ALIBABACLOUD_DEFAULT_REGION, + ALIBABACLOUD_SDK_CONNECT_TIMEOUT, + ALIBABACLOUD_SDK_READ_TIMEOUT, +) +from prowler.providers.common.models import ProviderOutputOptions + + +class AlibabaCloudCallerIdentity(BaseModel): + """ + AlibabaCloudCallerIdentity stores the caller identity information from STS GetCallerIdentity. + + Attributes: + account_id: The Alibaba Cloud account ID + principal_id: The principal ID (user ID or root account ID) + arn: The ARN-like identifier for the identity + identity_type: The type of identity (e.g., "RamUser", "Root") + """ + + account_id: str + principal_id: str + arn: str + identity_type: str = "" + + +class AlibabaCloudIdentityInfo(BaseModel): + """ + AlibabaCloudIdentityInfo stores the Alibaba Cloud account identity information. + + Attributes: + account_id: The Alibaba Cloud account ID + account_name: The Alibaba Cloud account name (if available) + user_id: The RAM user ID or root account ID + user_name: The RAM user name or "root" for root account + identity_arn: The ARN-like identifier for the identity + profile: The profile name used for authentication + profile_region: The default region from the profile + audited_regions: Set of regions to be audited + is_root: Whether this is the root account (True) or a RAM user (False) + """ + + account_id: str + account_name: str + user_id: str + user_name: str + identity_arn: str + profile: str + profile_region: str + audited_regions: set[str] + is_root: bool = False + + +class AlibabaCloudCredentials(BaseModel): + """ + AlibabaCloudCredentials stores the Alibaba Cloud credentials. + + Attributes: + access_key_id: The Access Key ID + access_key_secret: The Access Key Secret + security_token: The Security Token (for STS temporary credentials) + expiration: The expiration time for temporary credentials + """ + + access_key_id: str + access_key_secret: str + security_token: Optional[str] = None + expiration: Optional[datetime] = None + + +class AlibabaCloudAssumeRoleInfo(BaseModel): + """ + AlibabaCloudAssumeRoleInfo stores the information for assuming a RAM role. + + Attributes: + role_arn: The ARN of the role to assume + role_session_name: The session name for the assumed role + session_duration: The duration of the assumed role session (in seconds) + external_id: The external ID for role assumption + region: The region for STS endpoint + """ + + role_arn: str + role_session_name: str + session_duration: int + external_id: Optional[str] = None + region: str = "cn-hangzhou" + + +class AlibabaCloudRegion(BaseModel): + """ + AlibabaCloudRegion stores information about an Alibaba Cloud region. + + Attributes: + region_id: The region identifier (e.g., cn-hangzhou, cn-shanghai) + region_name: The human-readable region name + region_endpoint: The API endpoint for the region + """ + + region_id: str + region_name: str + region_endpoint: Optional[str] = None + + +class AlibabaCloudSession: + """ + AlibabaCloudSession stores the Alibaba Cloud session and credentials. + + This class provides methods to get credentials and create service clients. + """ + + def __init__(self, cred_client): + """ + Initialize the Alibaba Cloud session. + + Args: + cred_client: The Alibaba Cloud credentials client + """ + self.cred_client = cred_client + self._credentials = None + + def get_credentials(self): + """ + Get the Alibaba Cloud credentials. + + Returns: + AlibabaCloudCredentials object + """ + if self._credentials is None: + cred = self.cred_client.get_credential() + self._credentials = AlibabaCloudCredentials( + access_key_id=cred.get_access_key_id(), + access_key_secret=cred.get_access_key_secret(), + security_token=cred.get_security_token(), + ) + return self._credentials + + def client(self, service: str, region: str = None): + """ + Create a service client for the given service and region. + + Args: + service: The service name (e.g., 'ram') + region: The region (optional, some services are global) + + Returns: + A client instance for the specified service + """ + + # Get credentials + cred = self.get_credentials() + + # Create client configuration with timeout settings + config = open_api_models.Config( + access_key_id=cred.access_key_id, + access_key_secret=cred.access_key_secret, + read_timeout=ALIBABACLOUD_SDK_READ_TIMEOUT + * 1000, # Convert to milliseconds + connect_timeout=ALIBABACLOUD_SDK_CONNECT_TIMEOUT + * 1000, # Convert to milliseconds + ) + if cred.security_token: + config.security_token = cred.security_token + + # Set endpoint based on service + if service == "ram": + config.endpoint = "ram.aliyuncs.com" + return RamClient(config) + elif service == "vpc": + # VPC endpoint is regional: vpc.{region}.aliyuncs.com + if region: + config.endpoint = f"vpc.{region}.aliyuncs.com" + else: + config.endpoint = f"vpc.{ALIBABACLOUD_DEFAULT_REGION}.aliyuncs.com" + return VpcClient(config) + elif service == "ecs": + # ECS endpoint is regional: ecs.{region}.aliyuncs.com + if region: + config.endpoint = f"ecs.{region}.aliyuncs.com" + else: + config.endpoint = f"ecs.{ALIBABACLOUD_DEFAULT_REGION}.aliyuncs.com" + return EcsClient(config) + elif service == "sas" or service == "securitycenter": + # SAS (Security Center) endpoint is regional: sas.{region}.aliyuncs.com + if region: + config.endpoint = f"sas.{region}.aliyuncs.com" + else: + config.endpoint = f"sas.{ALIBABACLOUD_DEFAULT_REGION}.aliyuncs.com" + return SasClient(config) + elif service == "oss": + if region: + config.endpoint = f"oss-{region}.aliyuncs.com" + config.region_id = region + else: + config.endpoint = f"oss-{ALIBABACLOUD_DEFAULT_REGION}.aliyuncs.com" + config.region_id = ALIBABACLOUD_DEFAULT_REGION + return OssClient(config) + elif service == "actiontrail": + # ActionTrail endpoint is regional: actiontrail.{region}.aliyuncs.com + if region: + config.endpoint = f"actiontrail.{region}.aliyuncs.com" + else: + config.endpoint = ( + f"actiontrail.{ALIBABACLOUD_DEFAULT_REGION}.aliyuncs.com" + ) + return ActionTrailClient(config) + elif service == "cs": + if region: + config.endpoint = f"cs.{region}.aliyuncs.com" + else: + config.endpoint = f"cs.{ALIBABACLOUD_DEFAULT_REGION}.aliyuncs.com" + return CSClient(config) + elif service == "rds": + if region: + config.endpoint = f"rds.{region}.aliyuncs.com" + else: + config.endpoint = f"rds.{ALIBABACLOUD_DEFAULT_REGION}.aliyuncs.com" + return RdsClient(config) + elif service == "sls": + if region: + config.endpoint = f"{region}.log.aliyuncs.com" + else: + config.endpoint = f"{ALIBABACLOUD_DEFAULT_REGION}.log.aliyuncs.com" + return SlsClient(config) + else: + # For other services, implement as needed + logger.warning(f"Service {service} not yet implemented") + return None + + +class AlibabaCloudOutputOptions(ProviderOutputOptions): + """ + AlibabaCloudOutputOptions extends ProviderOutputOptions for Alibaba Cloud specific output options. + """ + + def __init__(self, arguments, bulk_checks_metadata, identity): + # Call parent class init + super().__init__(arguments, bulk_checks_metadata) + + # Set default output filename if not provided + if ( + not hasattr(arguments, "output_filename") + or arguments.output_filename is None + ): + from prowler.config.config import output_file_timestamp + + self.output_filename = ( + f"prowler-output-{identity.account_id}-{output_file_timestamp}" + ) + else: + self.output_filename = arguments.output_filename diff --git a/prowler/providers/alibabacloud/services/__init__.py b/prowler/providers/alibabacloud/services/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/alibabacloud/services/actiontrail/__init__.py b/prowler/providers/alibabacloud/services/actiontrail/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/alibabacloud/services/actiontrail/actiontrail_client.py b/prowler/providers/alibabacloud/services/actiontrail/actiontrail_client.py new file mode 100644 index 0000000000..a7adc532f4 --- /dev/null +++ b/prowler/providers/alibabacloud/services/actiontrail/actiontrail_client.py @@ -0,0 +1,6 @@ +from prowler.providers.alibabacloud.services.actiontrail.actiontrail_service import ( + ActionTrail, +) +from prowler.providers.common.provider import Provider + +actiontrail_client = ActionTrail(Provider.get_global_provider()) diff --git a/prowler/providers/alibabacloud/services/actiontrail/actiontrail_multi_region_enabled/__init__.py b/prowler/providers/alibabacloud/services/actiontrail/actiontrail_multi_region_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..d68a5cd352 --- /dev/null +++ b/prowler/providers/alibabacloud/services/actiontrail/actiontrail_multi_region_enabled/actiontrail_multi_region_enabled.metadata.json @@ -0,0 +1,39 @@ +{ + "Provider": "alibabacloud", + "CheckID": "actiontrail_multi_region_enabled", + "CheckTitle": "ActionTrail are configured to export copies of all Log entries", + "CheckType": [ + "Unusual logon", + "Cloud threat detection" + ], + "ServiceName": "actiontrail", + "SubServiceName": "", + "ResourceIdTemplate": "acs:actiontrail::account-id:trail", + "Severity": "critical", + "ResourceType": "AlibabaCloudActionTrail", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun actiontrail CreateTrail --Name --OssBucketName --RoleName aliyunactiontraildefaultrole --SlsProjectArn --SlsWriteRoleArn --EventRW ", + "NativeIaC": "", + "Other": "", + "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**", + "Url": "https://hub.prowler.com/check/actiontrail_multi_region_enabled" + } + }, + "Categories": [ + "logging" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/actiontrail/actiontrail_multi_region_enabled/actiontrail_multi_region_enabled.py b/prowler/providers/alibabacloud/services/actiontrail/actiontrail_multi_region_enabled/actiontrail_multi_region_enabled.py new file mode 100644 index 0000000000..c6b1764165 --- /dev/null +++ b/prowler/providers/alibabacloud/services/actiontrail/actiontrail_multi_region_enabled/actiontrail_multi_region_enabled.py @@ -0,0 +1,81 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.actiontrail.actiontrail_client import ( + actiontrail_client, +) + + +class actiontrail_multi_region_enabled(Check): + """Check if ActionTrail is configured to export copies of all log entries.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + # Check if there's at least one multi-region trail that is enabled + multi_region_trails = [] + for trail in actiontrail_client.trails.values(): + if trail.trail_region == "All" and trail.status == "Enable": + multi_region_trails.append(trail) + + # Create a single report for the overall check + report = CheckReportAlibabaCloud(metadata=self.metadata(), resource={}) + report.region = actiontrail_client.region + report.resource_id = actiontrail_client.audited_account + report.resource_arn = ( + f"acs:actiontrail::{actiontrail_client.audited_account}:trail" + ) + + if multi_region_trails: + # At least one multi-region trail is enabled + trail_names = [trail.name for trail in multi_region_trails] + report.status = "PASS" + report.status_extended = ( + f"ActionTrail is configured with {len(multi_region_trails)} multi-region trail(s) " + f"that are enabled: {', '.join(trail_names)}. " + "These trails export copies of all log entries across all regions." + ) + else: + # Check if there are any trails at all + if actiontrail_client.trails: + # There are trails but none are multi-region or enabled + enabled_trails = [ + t + for t in actiontrail_client.trails.values() + if t.status == "Enable" + ] + multi_region_trails_disabled = [ + t + for t in actiontrail_client.trails.values() + if t.trail_region == "All" and t.status != "Enable" + ] + + if enabled_trails and not multi_region_trails_disabled: + report.status = "FAIL" + report.status_extended = ( + f"ActionTrail has {len(enabled_trails)} enabled trail(s), but none are configured " + "for multi-region logging (TrailRegion is not set to 'All'). " + "Multi-region trails are required to capture events from all regions." + ) + elif multi_region_trails_disabled: + trail_names = [t.name for t in multi_region_trails_disabled] + report.status = "FAIL" + report.status_extended = ( + f"ActionTrail has multi-region trail(s) but they are disabled: {', '.join(trail_names)}. " + "Enable the multi-region trail(s) to export copies of all log entries." + ) + else: + report.status = "FAIL" + report.status_extended = ( + "ActionTrail has trails configured, but none are enabled or configured for multi-region logging. " + "At least one trail with TrailRegion set to 'All' and Status set to 'Enable' is required." + ) + else: + # No trails configured at all + report.status = "FAIL" + report.status_extended = ( + "ActionTrail is not configured. No trails exist. " + "Create at least one multi-region trail (TrailRegion='All') and enable it " + "to export copies of all log entries across all regions." + ) + + findings.append(report) + return findings diff --git a/prowler/providers/alibabacloud/services/actiontrail/actiontrail_oss_bucket_not_publicly_accessible/__init__.py b/prowler/providers/alibabacloud/services/actiontrail/actiontrail_oss_bucket_not_publicly_accessible/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..6de44f787c --- /dev/null +++ b/prowler/providers/alibabacloud/services/actiontrail/actiontrail_oss_bucket_not_publicly_accessible/actiontrail_oss_bucket_not_publicly_accessible.metadata.json @@ -0,0 +1,40 @@ +{ + "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" + ], + "ServiceName": "actiontrail", + "SubServiceName": "", + "ResourceIdTemplate": "acs:oss::account-id:bucket-name", + "Severity": "critical", + "ResourceType": "AlibabaCloudOSSBucket", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "ossutil set-acl oss:// private -b", + "NativeIaC": "", + "Other": "", + "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", + "Url": "https://hub.prowler.com/check/actiontrail_oss_bucket_not_publicly_accessible" + } + }, + "Categories": [ + "logging" + ], + "DependsOn": [ + "oss_bucket_not_publicly_accessible" + ], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/actiontrail/actiontrail_oss_bucket_not_publicly_accessible/actiontrail_oss_bucket_not_publicly_accessible.py b/prowler/providers/alibabacloud/services/actiontrail/actiontrail_oss_bucket_not_publicly_accessible/actiontrail_oss_bucket_not_publicly_accessible.py new file mode 100644 index 0000000000..c994220c95 --- /dev/null +++ b/prowler/providers/alibabacloud/services/actiontrail/actiontrail_oss_bucket_not_publicly_accessible/actiontrail_oss_bucket_not_publicly_accessible.py @@ -0,0 +1,119 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.actiontrail.actiontrail_client import ( + actiontrail_client, +) +from prowler.providers.alibabacloud.services.oss.oss_client import oss_client + + +def _is_policy_public(policy_document: dict) -> bool: + """ + Check if a bucket policy allows public access. + + A policy is considered public if it has a statement with: + - Effect: "Allow" + - Principal: ["*"] (or contains "*") + - No Condition elements + + Args: + policy_document: The parsed policy document as a dictionary. + + Returns: + bool: True if policy allows public access, False otherwise. + """ + if not policy_document: + return False + + statements = policy_document.get("Statement", []) + if not isinstance(statements, list): + statements = [statements] + + for statement in statements: + effect = statement.get("Effect", "") + principal = statement.get("Principal", []) + condition = statement.get("Condition") + + # If there's a condition, it's not truly public + if condition: + continue + + if effect == "Allow": + # Check if Principal is "*" or contains "*" + if isinstance(principal, list): + if "*" in principal: + return True + elif principal == "*": + return True + + return False + + +class actiontrail_oss_bucket_not_publicly_accessible(Check): + """Check if the OSS bucket used to store ActionTrail logs is not publicly accessible.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + # Get all ActionTrail trails + for trail in actiontrail_client.trails.values(): + # Only check trails that have an OSS bucket configured + if not trail.oss_bucket_name: + continue + + # Find the OSS bucket used by this trail + bucket = None + for oss_bucket in oss_client.buckets.values(): + if oss_bucket.name == trail.oss_bucket_name: + bucket = oss_bucket + break + + # Create report for this trail's OSS bucket + report = CheckReportAlibabaCloud(metadata=self.metadata(), resource=trail) + report.region = trail.home_region + report.resource_id = trail.oss_bucket_name + report.resource_arn = ( + f"acs:oss::{actiontrail_client.audited_account}:{trail.oss_bucket_name}" + ) + + if not bucket: + # Bucket not found in OSS service (might not have permissions or bucket doesn't exist) + report.status = "MANUAL" + report.status_extended = ( + f"ActionTrail trail {trail.name} uses OSS bucket {trail.oss_bucket_name}, " + "but the bucket could not be found or accessed. Please verify the bucket exists " + "and that you have permissions to access it." + ) + findings.append(report) + continue + + # Check bucket ACL + acl_public = False + if bucket.acl and bucket.acl != "private": + if bucket.acl in ["public-read", "public-read-write"]: + acl_public = True + + # Check bucket policy + policy_public = _is_policy_public(bucket.policy) + + # Determine status + if acl_public or policy_public: + report.status = "FAIL" + issues = [] + if acl_public: + issues.append(f"Bucket ACL is set to {bucket.acl}") + if policy_public: + issues.append("Bucket policy allows public access (Principal: '*')") + report.status_extended = ( + f"OSS bucket {trail.oss_bucket_name} used by ActionTrail trail {trail.name} " + f"is publicly accessible. {'; '.join(issues)}. " + "ActionTrail logs contain sensitive information and should not be publicly accessible." + ) + else: + report.status = "PASS" + report.status_extended = ( + f"OSS bucket {trail.oss_bucket_name} used by ActionTrail trail {trail.name} " + f"is not publicly accessible. ACL is {bucket.acl} and bucket policy does not allow public access." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/actiontrail/actiontrail_service.py b/prowler/providers/alibabacloud/services/actiontrail/actiontrail_service.py new file mode 100644 index 0000000000..2922472502 --- /dev/null +++ b/prowler/providers/alibabacloud/services/actiontrail/actiontrail_service.py @@ -0,0 +1,110 @@ +from datetime import datetime +from typing import Optional + +from alibabacloud_actiontrail20200706 import models as actiontrail_models +from pydantic.v1 import BaseModel + +from prowler.lib.logger import logger +from prowler.lib.scan_filters.scan_filters import is_resource_filtered +from prowler.providers.alibabacloud.lib.service.service import AlibabaCloudService + + +class ActionTrail(AlibabaCloudService): + """ + ActionTrail service class for Alibaba Cloud. + + This class provides methods to interact with Alibaba Cloud ActionTrail service + to retrieve trails and their configuration. + """ + + def __init__(self, provider): + # Call AlibabaCloudService's __init__ + # ActionTrail is a regional service + super().__init__(__class__.__name__, provider, global_service=False) + + # Fetch ActionTrail resources + self.trails = {} + self.__threading_call__(self._describe_trails) + + def _describe_trails(self, regional_client): + """List all ActionTrail trails.""" + region = getattr(regional_client, "region", "unknown") + logger.info(f"ActionTrail - Describing trails in {region}...") + try: + # Use Tea SDK client (ActionTrail is regional service) + request = actiontrail_models.DescribeTrailsRequest() + response = regional_client.describe_trails(request) + + if response and response.body and response.body.trail_list: + # trail_list is already a list, not an object with a trail attribute + trails_list = response.body.trail_list + if not isinstance(trails_list, list): + trails_list = [trails_list] + + for trail_data in trails_list: + trail_name = getattr(trail_data, "name", "") + if not trail_name: + continue + + # Get trail region (can be specific region or "All") + trail_region = getattr(trail_data, "trail_region", "") + home_region = getattr(trail_data, "home_region", "") + status = getattr(trail_data, "status", "") + + # Create ARN + arn = f"acs:actiontrail::{self.audited_account}:trail/{trail_name}" + + if not self.audit_resources or is_resource_filtered( + arn, self.audit_resources + ): + # Parse creation date if available + creation_date = None + creation_date_str = getattr(trail_data, "create_time", None) + if creation_date_str: + try: + # ActionTrail date format: "2024-02-02T10:02:11Z" or similar + creation_date = datetime.strptime( + creation_date_str.replace("Z", "+00:00"), + "%Y-%m-%dT%H:%M:%S%z", + ) + except (ValueError, AttributeError): + creation_date = datetime.strptime( + creation_date_str.replace("Z", "+00:00"), + "%Y-%m-%dT%H:%M:%S.%f%z", + ) + + self.trails[arn] = Trail( + arn=arn, + name=trail_name, + home_region=home_region, + trail_region=trail_region, + status=status, + oss_bucket_name=getattr(trail_data, "oss_bucket_name", ""), + oss_bucket_location=getattr( + trail_data, "oss_bucket_location", "" + ), + sls_project_arn=getattr(trail_data, "sls_project_arn", ""), + event_rw=getattr(trail_data, "event_rw", ""), + creation_date=creation_date, + ) + + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + +# Service Models +class Trail(BaseModel): + """ActionTrail Trail model.""" + + arn: str + name: str + home_region: str + trail_region: str # "All" for multi-region, or specific region name + status: str # "Enable" or "Disable" + oss_bucket_name: str = "" + oss_bucket_location: str = "" + sls_project_arn: str = "" + event_rw: str = "" # "All", "Read", "Write" + creation_date: Optional[datetime] = None diff --git a/prowler/providers/alibabacloud/services/cs/__init__.py b/prowler/providers/alibabacloud/services/cs/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/alibabacloud/services/cs/cs_client.py b/prowler/providers/alibabacloud/services/cs/cs_client.py new file mode 100644 index 0000000000..287a7baa59 --- /dev/null +++ b/prowler/providers/alibabacloud/services/cs/cs_client.py @@ -0,0 +1,4 @@ +from prowler.providers.alibabacloud.services.cs.cs_service import CS +from prowler.providers.common.provider import Provider + +cs_client = CS(Provider.get_global_provider()) diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cloudmonitor_enabled/__init__.py b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cloudmonitor_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..3cf8abf580 --- /dev/null +++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cloudmonitor_enabled/cs_kubernetes_cloudmonitor_enabled.metadata.json @@ -0,0 +1,38 @@ +{ + "Provider": "alibabacloud", + "CheckID": "cs_kubernetes_cloudmonitor_enabled", + "CheckTitle": "CloudMonitor is set to Enabled on Kubernetes Engine Clusters", + "CheckType": [ + "Threat detection during container runtime" + ], + "ServiceName": "cs", + "SubServiceName": "", + "ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}", + "Severity": "medium", + "ResourceType": "AlibabaCloudKubernetesCluster", + "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.", + "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" + ], + "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.", + "NativeIaC": "", + "Other": "", + "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", + "Url": "https://hub.prowler.com/check/cs_kubernetes_cloudmonitor_enabled" + } + }, + "Categories": [ + "logging" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cloudmonitor_enabled/cs_kubernetes_cloudmonitor_enabled.py b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cloudmonitor_enabled/cs_kubernetes_cloudmonitor_enabled.py new file mode 100644 index 0000000000..58611a5657 --- /dev/null +++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cloudmonitor_enabled/cs_kubernetes_cloudmonitor_enabled.py @@ -0,0 +1,26 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.cs.cs_client import cs_client + + +class cs_kubernetes_cloudmonitor_enabled(Check): + """Check if CloudMonitor is enabled on Kubernetes Engine Clusters.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + for cluster in cs_client.clusters: + report = CheckReportAlibabaCloud(metadata=self.metadata(), resource=cluster) + report.region = cluster.region + report.resource_id = cluster.id + report.resource_arn = f"acs:cs:{cluster.region}:{cs_client.audited_account}:cluster/{cluster.id}" + + if cluster.cloudmonitor_enabled: + report.status = "PASS" + report.status_extended = f"Kubernetes cluster {cluster.name} has CloudMonitor Agent enabled on all node pools." + else: + report.status = "FAIL" + report.status_extended = f"Kubernetes cluster {cluster.name} does not have CloudMonitor Agent enabled on all node pools." + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_recent/__init__.py b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_recent/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..1173550c98 --- /dev/null +++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_recent/cs_kubernetes_cluster_check_recent.metadata.json @@ -0,0 +1,38 @@ +{ + "Provider": "alibabacloud", + "CheckID": "cs_kubernetes_cluster_check_recent", + "CheckTitle": "Cluster Check triggered within configured period for Kubernetes Clusters", + "CheckType": [ + "Threat detection during container runtime" + ], + "ServiceName": "cs", + "SubServiceName": "", + "ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}", + "Severity": "medium", + "ResourceType": "AlibabaCloudKubernetesCluster", + "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**.", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://help.aliyun.com/document_detail/114882.html", + "https://www.trendmicro.com/cloudoneconformity/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.", + "NativeIaC": "", + "Other": "", + "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)", + "Url": "https://hub.prowler.com/check/cs_kubernetes_cluster_check_recent" + } + }, + "Categories": [ + "cluster-security" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_recent/cs_kubernetes_cluster_check_recent.py b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_recent/cs_kubernetes_cluster_check_recent.py new file mode 100644 index 0000000000..39fc4d27b9 --- /dev/null +++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_recent/cs_kubernetes_cluster_check_recent.py @@ -0,0 +1,62 @@ +from datetime import datetime, timedelta, timezone + +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.cs.cs_client import cs_client + + +class cs_kubernetes_cluster_check_recent(Check): + """Check if Cluster Check is triggered within the configured number of days.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + # Get configurable max days from audit config (default: 7 days) + max_cluster_check_days = cs_client.audit_config.get("max_cluster_check_days", 7) + + # Calculate the threshold date + threshold_date = datetime.now(timezone.utc) - timedelta( + days=max_cluster_check_days + ) + + for cluster in cs_client.clusters: + report = CheckReportAlibabaCloud(metadata=self.metadata(), resource=cluster) + report.region = cluster.region + report.resource_id = cluster.id + report.resource_arn = f"acs:cs:{cluster.region}:{cs_client.audited_account}:cluster/{cluster.id}" + + if cluster.last_check_time: + # Ensure last_check_time is timezone-aware + last_check = cluster.last_check_time + if last_check.tzinfo is None: + # If naive datetime, assume UTC + last_check = last_check.replace(tzinfo=timezone.utc) + + # Calculate days since last check + days_since_check = (datetime.now(timezone.utc) - last_check).days + + if last_check >= threshold_date: + report.status = "PASS" + report.status_extended = ( + f"Kubernetes cluster {cluster.name} has had a successful cluster check " + f"within the last {max_cluster_check_days} days " + f"(last check: {cluster.last_check_time.strftime('%Y-%m-%d %H:%M:%S UTC')}, " + f"{days_since_check} days ago)." + ) + else: + report.status = "FAIL" + report.status_extended = ( + f"Kubernetes cluster {cluster.name} has not had a successful cluster check " + f"within the last {max_cluster_check_days} days " + f"(last check: {cluster.last_check_time.strftime('%Y-%m-%d %H:%M:%S UTC')}, " + f"{days_since_check} days ago)." + ) + else: + report.status = "FAIL" + report.status_extended = ( + f"Kubernetes cluster {cluster.name} has no successful cluster check history. " + f"Cluster checks should be triggered at least once every {max_cluster_check_days} days." + ) + + findings.append(report) + + return findings 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 new file mode 100644 index 0000000000..a99edb10b0 --- /dev/null +++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_weekly/cs_kubernetes_cluster_check_weekly.metadata.json @@ -0,0 +1,38 @@ +{ + "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" + ], + "ServiceName": "cs", + "SubServiceName": "", + "ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}", + "Severity": "medium", + "ResourceType": "AlibabaCloudKubernetesCluster", + "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**.", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://help.aliyun.com/document_detail/114882.html", + "https://www.trendmicro.com/cloudoneconformity/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.", + "NativeIaC": "", + "Other": "", + "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", + "Url": "https://hub.prowler.com/check/cs_kubernetes_cluster_check_weekly" + } + }, + "Categories": [ + "cluster-security" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_weekly/cs_kubernetes_cluster_check_weekly.py b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_weekly/cs_kubernetes_cluster_check_weekly.py new file mode 100644 index 0000000000..d63881d929 --- /dev/null +++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_weekly/cs_kubernetes_cluster_check_weekly.py @@ -0,0 +1,62 @@ +from datetime import datetime, timedelta, timezone + +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.cs.cs_client import cs_client + + +class cs_kubernetes_cluster_check_weekly(Check): + """Check if Cluster Check is triggered at least once per week for Kubernetes Clusters.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + # Get configurable max days from audit config (default: 7 days) + max_cluster_check_days = cs_client.audit_config.get("max_cluster_check_days", 7) + + # Calculate the threshold date + threshold_date = datetime.now(timezone.utc) - timedelta( + days=max_cluster_check_days + ) + + for cluster in cs_client.clusters: + report = CheckReportAlibabaCloud(metadata=self.metadata(), resource=cluster) + report.region = cluster.region + report.resource_id = cluster.id + report.resource_arn = f"acs:cs:{cluster.region}:{cs_client.audited_account}:cluster/{cluster.id}" + + if cluster.last_check_time: + # Ensure last_check_time is timezone-aware + last_check = cluster.last_check_time + if last_check.tzinfo is None: + # If naive datetime, assume UTC + last_check = last_check.replace(tzinfo=timezone.utc) + + # Calculate days since last check + days_since_check = (datetime.now(timezone.utc) - last_check).days + + if last_check >= threshold_date: + report.status = "PASS" + report.status_extended = ( + f"Kubernetes cluster {cluster.name} has had a successful cluster check " + f"within the last {max_cluster_check_days} days " + f"(last check: {cluster.last_check_time.strftime('%Y-%m-%d %H:%M:%S UTC')}, " + f"{days_since_check} days ago)." + ) + else: + report.status = "FAIL" + report.status_extended = ( + f"Kubernetes cluster {cluster.name} has not had a successful cluster check " + f"within the last {max_cluster_check_days} days " + f"(last check: {cluster.last_check_time.strftime('%Y-%m-%d %H:%M:%S UTC')}, " + f"{days_since_check} days ago)." + ) + else: + report.status = "FAIL" + report.status_extended = ( + f"Kubernetes cluster {cluster.name} has no successful cluster check history. " + f"Cluster checks should be triggered at least once every {max_cluster_check_days} days." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_dashboard_disabled/__init__.py b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_dashboard_disabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..bf41a1300a --- /dev/null +++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_dashboard_disabled/cs_kubernetes_dashboard_disabled.metadata.json @@ -0,0 +1,39 @@ +{ + "Provider": "alibabacloud", + "CheckID": "cs_kubernetes_dashboard_disabled", + "CheckTitle": "Kubernetes web UI / Dashboard is not enabled", + "CheckType": [ + "Threat detection during container runtime", + "Unusual logon" + ], + "ServiceName": "cs", + "SubServiceName": "", + "ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}", + "Severity": "high", + "ResourceType": "AlibabaCloudKubernetesCluster", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "Use kubectl to delete the dashboard deployment: kubectl delete deployment kubernetes-dashboard -n kube-system", + "NativeIaC": "", + "Other": "", + "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", + "Url": "https://hub.prowler.com/check/cs_kubernetes_dashboard_disabled" + } + }, + "Categories": [ + "cluster-security" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_dashboard_disabled/cs_kubernetes_dashboard_disabled.py b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_dashboard_disabled/cs_kubernetes_dashboard_disabled.py new file mode 100644 index 0000000000..d53b2b4284 --- /dev/null +++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_dashboard_disabled/cs_kubernetes_dashboard_disabled.py @@ -0,0 +1,26 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.cs.cs_client import cs_client + + +class cs_kubernetes_dashboard_disabled(Check): + """Check if Kubernetes web UI / Dashboard is disabled.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + for cluster in cs_client.clusters: + report = CheckReportAlibabaCloud(metadata=self.metadata(), resource=cluster) + report.region = cluster.region + report.resource_id = cluster.id + report.resource_arn = f"acs:cs:{cluster.region}:{cs_client.audited_account}:cluster/{cluster.id}" + + if not cluster.dashboard_enabled: + report.status = "PASS" + report.status_extended = f"Kubernetes cluster {cluster.name} does not have the Kubernetes Dashboard enabled." + else: + report.status = "FAIL" + report.status_extended = f"Kubernetes cluster {cluster.name} has the Kubernetes Dashboard enabled." + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_eni_multiple_ip_enabled/__init__.py b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_eni_multiple_ip_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..ac454450e4 --- /dev/null +++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_eni_multiple_ip_enabled/cs_kubernetes_eni_multiple_ip_enabled.metadata.json @@ -0,0 +1,39 @@ +{ + "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" + ], + "ServiceName": "cs", + "SubServiceName": "", + "ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}", + "Severity": "medium", + "ResourceType": "AlibabaCloudKubernetesCluster", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "Terway network plugin must be selected during cluster creation to support ENI multiple IP mode.", + "NativeIaC": "", + "Other": "", + "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.", + "Url": "https://hub.prowler.com/check/cs_kubernetes_eni_multiple_ip_enabled" + } + }, + "Categories": [ + "cluster-security" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_eni_multiple_ip_enabled/cs_kubernetes_eni_multiple_ip_enabled.py b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_eni_multiple_ip_enabled/cs_kubernetes_eni_multiple_ip_enabled.py new file mode 100644 index 0000000000..30e40ab8bd --- /dev/null +++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_eni_multiple_ip_enabled/cs_kubernetes_eni_multiple_ip_enabled.py @@ -0,0 +1,26 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.cs.cs_client import cs_client + + +class cs_kubernetes_eni_multiple_ip_enabled(Check): + """Check if ENI multiple IP mode is supported on Kubernetes Engine Clusters.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + for cluster in cs_client.clusters: + report = CheckReportAlibabaCloud(metadata=self.metadata(), resource=cluster) + report.region = cluster.region + report.resource_id = cluster.id + report.resource_arn = f"acs:cs:{cluster.region}:{cs_client.audited_account}:cluster/{cluster.id}" + + if cluster.eni_multiple_ip_enabled: + report.status = "PASS" + report.status_extended = f"Kubernetes cluster {cluster.name} supports ENI multiple IP mode via Terway plugin." + else: + report.status = "FAIL" + report.status_extended = f"Kubernetes cluster {cluster.name} does not support ENI multiple IP mode (requires Terway network plugin)." + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_log_service_enabled/__init__.py b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_log_service_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..463b3dd1aa --- /dev/null +++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_log_service_enabled/cs_kubernetes_log_service_enabled.metadata.json @@ -0,0 +1,40 @@ +{ + "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" + ], + "ServiceName": "cs", + "SubServiceName": "", + "ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}", + "Severity": "high", + "ResourceType": "AlibabaCloudKubernetesCluster", + "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.", + "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" + ], + "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.", + "NativeIaC": "", + "Other": "", + "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**", + "Url": "https://hub.prowler.com/check/cs_kubernetes_log_service_enabled" + } + }, + "Categories": [ + "logging", + "forensics-ready" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_log_service_enabled/cs_kubernetes_log_service_enabled.py b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_log_service_enabled/cs_kubernetes_log_service_enabled.py new file mode 100644 index 0000000000..a622122a0d --- /dev/null +++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_log_service_enabled/cs_kubernetes_log_service_enabled.py @@ -0,0 +1,26 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.cs.cs_client import cs_client + + +class cs_kubernetes_log_service_enabled(Check): + """Check if Log Service is enabled on Kubernetes Engine Clusters.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + for cluster in cs_client.clusters: + report = CheckReportAlibabaCloud(metadata=self.metadata(), resource=cluster) + report.region = cluster.region + report.resource_id = cluster.id + report.resource_arn = f"acs:cs:{cluster.region}:{cs_client.audited_account}:cluster/{cluster.id}" + + if cluster.log_service_enabled: + report.status = "PASS" + report.status_extended = f"Kubernetes cluster {cluster.name} has Log Service enabled with project: {cluster.audit_project_name}." + else: + report.status = "FAIL" + report.status_extended = f"Kubernetes cluster {cluster.name} does not have Log Service enabled." + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_network_policy_enabled/__init__.py b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_network_policy_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..26f5066264 --- /dev/null +++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_network_policy_enabled/cs_kubernetes_network_policy_enabled.metadata.json @@ -0,0 +1,39 @@ +{ + "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" + ], + "ServiceName": "cs", + "SubServiceName": "", + "ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}", + "Severity": "medium", + "ResourceType": "AlibabaCloudKubernetesCluster", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "Network Policy support (Terway) must be selected during cluster creation.", + "NativeIaC": "", + "Other": "", + "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.", + "Url": "https://hub.prowler.com/check/cs_kubernetes_network_policy_enabled" + } + }, + "Categories": [ + "cluster-security" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_network_policy_enabled/cs_kubernetes_network_policy_enabled.py b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_network_policy_enabled/cs_kubernetes_network_policy_enabled.py new file mode 100644 index 0000000000..6681f395d7 --- /dev/null +++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_network_policy_enabled/cs_kubernetes_network_policy_enabled.py @@ -0,0 +1,26 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.cs.cs_client import cs_client + + +class cs_kubernetes_network_policy_enabled(Check): + """Check if Network policy is enabled on Kubernetes Engine Clusters.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + for cluster in cs_client.clusters: + report = CheckReportAlibabaCloud(metadata=self.metadata(), resource=cluster) + report.region = cluster.region + report.resource_id = cluster.id + report.resource_arn = f"acs:cs:{cluster.region}:{cs_client.audited_account}:cluster/{cluster.id}" + + if cluster.network_policy_enabled: + report.status = "PASS" + report.status_extended = f"Kubernetes cluster {cluster.name} has Network Policy enabled via Terway plugin." + else: + report.status = "FAIL" + report.status_extended = f"Kubernetes cluster {cluster.name} does not have Network Policy enabled (requires Terway network plugin)." + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_private_cluster_enabled/__init__.py b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_private_cluster_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..f587cc03fa --- /dev/null +++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_private_cluster_enabled/cs_kubernetes_private_cluster_enabled.metadata.json @@ -0,0 +1,39 @@ +{ + "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" + ], + "ServiceName": "cs", + "SubServiceName": "", + "ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}", + "Severity": "medium", + "ResourceType": "AlibabaCloudKubernetesCluster", + "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.", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://help.aliyun.com/document_detail/100380.html", + "https://www.trendmicro.com/cloudoneconformity/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.", + "NativeIaC": "", + "Other": "", + "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", + "Url": "https://hub.prowler.com/check/cs_kubernetes_private_cluster_enabled" + } + }, + "Categories": [ + "cluster-security" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_private_cluster_enabled/cs_kubernetes_private_cluster_enabled.py b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_private_cluster_enabled/cs_kubernetes_private_cluster_enabled.py new file mode 100644 index 0000000000..3bbc49bd25 --- /dev/null +++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_private_cluster_enabled/cs_kubernetes_private_cluster_enabled.py @@ -0,0 +1,26 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.cs.cs_client import cs_client + + +class cs_kubernetes_private_cluster_enabled(Check): + """Check if Kubernetes Cluster is created with Private cluster enabled.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + for cluster in cs_client.clusters: + report = CheckReportAlibabaCloud(metadata=self.metadata(), resource=cluster) + report.region = cluster.region + report.resource_id = cluster.id + report.resource_arn = f"acs:cs:{cluster.region}:{cs_client.audited_account}:cluster/{cluster.id}" + + if cluster.private_cluster_enabled: + report.status = "PASS" + report.status_extended = f"Kubernetes cluster {cluster.name} is a private cluster (no public API endpoint)." + else: + report.status = "FAIL" + report.status_extended = f"Kubernetes cluster {cluster.name} has a public API endpoint exposed." + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_rbac_enabled/__init__.py b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_rbac_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..641d9db6e1 --- /dev/null +++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_rbac_enabled/cs_kubernetes_rbac_enabled.metadata.json @@ -0,0 +1,40 @@ +{ + "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" + ], + "ServiceName": "cs", + "SubServiceName": "", + "ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}", + "Severity": "high", + "ResourceType": "AlibabaCloudKubernetesCluster", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "RBAC is enabled by default on new ACK clusters. Verify cluster authorization configuration.", + "NativeIaC": "", + "Other": "", + "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", + "Url": "https://hub.prowler.com/check/cs_kubernetes_rbac_enabled" + } + }, + "Categories": [ + "identity-access" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/cs/cs_kubernetes_rbac_enabled/cs_kubernetes_rbac_enabled.py b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_rbac_enabled/cs_kubernetes_rbac_enabled.py new file mode 100644 index 0000000000..1315469e26 --- /dev/null +++ b/prowler/providers/alibabacloud/services/cs/cs_kubernetes_rbac_enabled/cs_kubernetes_rbac_enabled.py @@ -0,0 +1,28 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.cs.cs_client import cs_client + + +class cs_kubernetes_rbac_enabled(Check): + """Check if RBAC authorization is enabled on Kubernetes Engine Clusters.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + for cluster in cs_client.clusters: + report = CheckReportAlibabaCloud(metadata=self.metadata(), resource=cluster) + report.region = cluster.region + report.resource_id = cluster.id + report.resource_arn = f"acs:cs:{cluster.region}:{cs_client.audited_account}:cluster/{cluster.id}" + + if cluster.rbac_enabled: + report.status = "PASS" + report.status_extended = ( + f"Kubernetes cluster {cluster.name} has RBAC authorization enabled." + ) + else: + report.status = "FAIL" + report.status_extended = f"Kubernetes cluster {cluster.name} does not have RBAC authorization enabled or is using legacy ABAC authorization." + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/cs/cs_service.py b/prowler/providers/alibabacloud/services/cs/cs_service.py new file mode 100644 index 0000000000..7645f3edf2 --- /dev/null +++ b/prowler/providers/alibabacloud/services/cs/cs_service.py @@ -0,0 +1,354 @@ +from datetime import datetime +from typing import Optional + +from alibabacloud_cs20151215 import models as cs_models +from pydantic.v1 import BaseModel + +from prowler.lib.logger import logger +from prowler.lib.scan_filters.scan_filters import is_resource_filtered +from prowler.providers.alibabacloud.lib.service.service import AlibabaCloudService + + +class CS(AlibabaCloudService): + """ + CS (Container Service) class for Alibaba Cloud Kubernetes (ACK). + + This class provides methods to interact with Alibaba Cloud Container Service + to retrieve ACK clusters and their configurations. + """ + + def __init__(self, provider): + # Call AlibabaCloudService's __init__ + super().__init__(__class__.__name__, provider, global_service=False) + + # Fetch CS resources + self.clusters = [] + self.__threading_call__(self._describe_clusters) + + def _describe_clusters(self, regional_client): + """List all ACK clusters and fetch their details in a specific region.""" + region = getattr(regional_client, "region", "unknown") + logger.info(f"CS - Describing Kubernetes clusters in {region}...") + + try: + # DescribeClustersV1 returns cluster list + request = cs_models.DescribeClustersV1Request() + response = regional_client.describe_clusters_v1(request) + + if response and response.body and response.body.clusters: + for cluster_data in response.body.clusters: + cluster_id = getattr(cluster_data, "cluster_id", "") + + if not self.audit_resources or is_resource_filtered( + cluster_id, self.audit_resources + ): + # Get detailed information for each cluster + cluster_detail = self._get_cluster_detail( + regional_client, cluster_id + ) + + if cluster_detail: + # Extract audit project name from meta_data + meta_data = cluster_detail.get("meta_data", {}) + audit_project_name = meta_data.get("AuditProjectName", "") + + # Check RBAC status - by default RBAC is enabled on ACK clusters + # We check if there are any indicators that RBAC is disabled + rbac_enabled = self._check_rbac_enabled( + cluster_detail, region + ) + + # Get node pools to check CloudMonitor + cloudmonitor_enabled = self._check_cloudmonitor_enabled( + regional_client, cluster_id + ) + + # Check if cluster checks have been run in the last week + last_check_time = self._get_last_cluster_check( + regional_client, cluster_id + ) + + # Check addons for dashboard, network policy, etc. + addons_status = self._check_cluster_addons( + cluster_detail, region + ) + + # Check for public API server endpoint + public_access_enabled = self._check_public_access( + cluster_detail, region + ) + + self.clusters.append( + Cluster( + id=cluster_id, + name=getattr(cluster_data, "name", cluster_id), + region=region, + cluster_type=getattr( + cluster_data, "cluster_type", "" + ), + state=getattr(cluster_data, "state", ""), + audit_project_name=audit_project_name, + log_service_enabled=bool(audit_project_name), + cloudmonitor_enabled=cloudmonitor_enabled, + rbac_enabled=rbac_enabled, + last_check_time=last_check_time, + dashboard_enabled=addons_status[ + "dashboard_enabled" + ], + network_policy_enabled=addons_status[ + "network_policy_enabled" + ], + eni_multiple_ip_enabled=addons_status[ + "eni_multiple_ip_enabled" + ], + private_cluster_enabled=not public_access_enabled, + ) + ) + + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def _get_cluster_detail(self, regional_client, cluster_id: str) -> dict: + """Get detailed information for a specific cluster.""" + try: + # DescribeClusterDetail returns detailed cluster information + request = cs_models.DescribeClusterDetailRequest() + response = regional_client.describe_cluster_detail(cluster_id, request) + + if response and response.body: + # Convert response body to dict + body = response.body + result = {"meta_data": {}} + + # Check if meta_data exists in the response + if hasattr(body, "meta_data"): + meta_data = body.meta_data + if meta_data: + result["meta_data"] = dict(meta_data) + + return result + + return {} + + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return {} + + def _check_cloudmonitor_enabled(self, regional_client, cluster_id: str) -> bool: + """Check if CloudMonitor is enabled on cluster node pools.""" + try: + # DescribeClusterNodePools returns node pool information + request = cs_models.DescribeClusterNodePoolsRequest() + response = regional_client.describe_cluster_node_pools(cluster_id, request) + + if response and response.body and response.body.nodepools: + nodepools = response.body.nodepools + + # Check if ALL node pools have CloudMonitor enabled + # If any node pool has cms_enabled=false, the cluster fails + for nodepool in nodepools: + kubernetes_config = getattr(nodepool, "kubernetes_config", None) + if kubernetes_config: + cms_enabled = getattr(kubernetes_config, "cms_enabled", False) + if not cms_enabled: + return False + + # All node pools have CloudMonitor enabled + return True if nodepools else False + + return False + + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return False + + def _check_rbac_enabled(self, cluster_detail: dict, region: str) -> bool: + """ + Check if RBAC is enabled on the cluster. + + By default, RBAC is enabled on ACK clusters and ABAC is disabled. + We check for any indicators that RBAC might be disabled or legacy auth enabled. + """ + try: + # Check if cluster has RBAC enabled (default is true for ACK clusters) + # Look for security_options or parameters that indicate RBAC status + + # Check meta_data for any RBAC-related settings + meta_data = cluster_detail.get("meta_data", {}) + + # If there's an explicit RBAC disabled flag, check it + if "RBACEnabled" in meta_data: + return meta_data.get("RBACEnabled", "true") in ["true", "True", True] + + # Check parameters for authorization mode + parameters = cluster_detail.get("parameters", {}) + if parameters: + # Check if there's an authorization mode parameter + auth_mode = parameters.get("authorization_mode", "RBAC") + if "ABAC" in auth_mode and "RBAC" not in auth_mode: + # Legacy ABAC-only mode + return False + + # By default, RBAC is enabled on ACK clusters + # If we don't find explicit indicators that it's disabled, assume it's enabled + return True + + except Exception as error: + logger.error( + f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + # Default to True as RBAC is enabled by default on ACK + return True + + def _get_last_cluster_check(self, regional_client, cluster_id: str): + """ + Get the most recent successful cluster check time. + + Returns the finished_at timestamp of the most recent successful cluster check, + or None if no successful checks found. + """ + try: + # DescribeClusterChecks returns cluster check history + request = cs_models.DescribeClusterChecksRequest() + response = regional_client.describe_cluster_checks(cluster_id, request) + + if response and response.body and response.body.checks: + checks = response.body.checks + + # Find the most recent successful check + most_recent_check = None + + for check in checks: + status = getattr(check, "status", "") + finished_at = getattr(check, "finished_at", None) + + if status == "Succeeded" and finished_at: + # Parse the timestamp + if most_recent_check is None or finished_at > most_recent_check: + most_recent_check = finished_at + + return most_recent_check + + return None + + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return None + + def _check_cluster_addons(self, cluster_detail: dict, region: str) -> dict: + """ + Check cluster addons for various security configurations. + + Returns: + dict: { + "dashboard_enabled": bool, + "network_policy_enabled": bool, + "eni_multiple_ip_enabled": bool + } + """ + result = { + "dashboard_enabled": False, + "network_policy_enabled": False, + "eni_multiple_ip_enabled": False, + } + + try: + meta_data = cluster_detail.get("meta_data", {}) + + # Check Addons list in meta_data + # Note: Addons structure from API is typically a string representation of JSON or a list + # Based on sample: "Addons": [{"name": "gateway-api", ...}, ...] + addons = meta_data.get("Addons", []) + + # If addons is string, try to parse it? + # The SDK typically handles this conversion, but let's be safe + if isinstance(addons, str): + import json + + try: + addons = json.loads(addons) + except Exception: + addons = [] + + for addon in addons: + name = addon.get("name", "") + disabled = addon.get("disabled", False) + + # Check 7.5: Kubernetes Dashboard + if name == "kubernetes-dashboard" and not disabled: + result["dashboard_enabled"] = True + + # Check 7.7 & 7.8: Terway network plugin + if name == "terway-eniip" or name == "terway": + result["network_policy_enabled"] = True + result["eni_multiple_ip_enabled"] = True + + return result + + except Exception as error: + logger.error( + f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return result + + def _check_public_access(self, cluster_detail: dict, region: str) -> bool: + """ + Check if cluster API server is accessible from public internet. + + Returns: + bool: True if public access is enabled, False otherwise. + """ + try: + # Check master_url in cluster detail + master_url = cluster_detail.get("master_url", "") + + # If master_url contains a public IP or DNS, public access is enabled + # Private clusters typically don't expose a public endpoint or have specific settings + + # Check endpoint_public in parameters + parameters = cluster_detail.get("parameters", {}) + endpoint_public = parameters.get("endpoint_public", "") + + if endpoint_public: + return True + + # If we can't find explicit indicator, check if master_url is present + # This is a heuristic - typical ACK public clusters expose a master_url + if master_url: + return True + + return False + + except Exception as error: + logger.error( + f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return False + + +# Models for CS service +class Cluster(BaseModel): + """ACK Cluster model.""" + + id: str + name: str + region: str + cluster_type: str + state: str + audit_project_name: str = "" + log_service_enabled: bool = False + cloudmonitor_enabled: bool = False + rbac_enabled: bool = True # Default is True for ACK clusters + last_check_time: Optional[datetime] = None + dashboard_enabled: bool = False + network_policy_enabled: bool = False + eni_multiple_ip_enabled: bool = False + private_cluster_enabled: bool = False diff --git a/prowler/providers/alibabacloud/services/ecs/__init__.py b/prowler/providers/alibabacloud/services/ecs/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/alibabacloud/services/ecs/ecs_attached_disk_encrypted/__init__.py b/prowler/providers/alibabacloud/services/ecs/ecs_attached_disk_encrypted/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..80bf866b99 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ecs/ecs_attached_disk_encrypted/ecs_attached_disk_encrypted.metadata.json @@ -0,0 +1,38 @@ +{ + "Provider": "alibabacloud", + "CheckID": "ecs_attached_disk_encrypted", + "CheckTitle": "Virtual Machines disk are encrypted", + "CheckType": [ + "Sensitive file tampering" + ], + "ServiceName": "ecs", + "SubServiceName": "", + "ResourceIdTemplate": "acs:ecs:region:account-id:disk/{disk-id}", + "Severity": "high", + "ResourceType": "AlibabaCloudECSDisk", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun ecs CreateDisk --DiskName --Size --Encrypted true --KmsKeyId ", + "NativeIaC": "", + "Other": "", + "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.", + "Url": "https://hub.prowler.com/check/ecs_attached_disk_encrypted" + } + }, + "Categories": [ + "encryption" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/ecs/ecs_attached_disk_encrypted/ecs_attached_disk_encrypted.py b/prowler/providers/alibabacloud/services/ecs/ecs_attached_disk_encrypted/ecs_attached_disk_encrypted.py new file mode 100644 index 0000000000..28e7ebe787 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ecs/ecs_attached_disk_encrypted/ecs_attached_disk_encrypted.py @@ -0,0 +1,38 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.ecs.ecs_client import ecs_client + + +class ecs_attached_disk_encrypted(Check): + """Check if attached disks are encrypted.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + for disk in ecs_client.disks: + # Only check attached disks + if disk.is_attached: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=disk + ) + report.region = disk.region + report.resource_id = disk.id + report.resource_arn = ( + f"acs:ecs:{disk.region}:{ecs_client.audited_account}:disk/{disk.id}" + ) + + if disk.is_encrypted: + report.status = "PASS" + report.status_extended = ( + f"Disk {disk.name if disk.name else disk.id} attached to instance " + f"{disk.attached_instance_id} is encrypted." + ) + else: + report.status = "FAIL" + report.status_extended = ( + f"Disk {disk.name if disk.name else disk.id} attached to instance " + f"{disk.attached_instance_id} is not encrypted." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/ecs/ecs_client.py b/prowler/providers/alibabacloud/services/ecs/ecs_client.py new file mode 100644 index 0000000000..f94d0dc15d --- /dev/null +++ b/prowler/providers/alibabacloud/services/ecs/ecs_client.py @@ -0,0 +1,4 @@ +from prowler.providers.alibabacloud.services.ecs.ecs_service import ECS +from prowler.providers.common.provider import Provider + +ecs_client = ECS(Provider.get_global_provider()) diff --git a/prowler/providers/alibabacloud/services/ecs/ecs_instance_endpoint_protection_installed/__init__.py b/prowler/providers/alibabacloud/services/ecs/ecs_instance_endpoint_protection_installed/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..d58dba4a2e --- /dev/null +++ b/prowler/providers/alibabacloud/services/ecs/ecs_instance_endpoint_protection_installed/ecs_instance_endpoint_protection_installed.metadata.json @@ -0,0 +1,41 @@ +{ + "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" + ], + "ServiceName": "ecs", + "SubServiceName": "", + "ResourceIdTemplate": "acs:ecs:region:account-id:instance/{instance-id}", + "Severity": "high", + "ResourceType": "AlibabaCloudECSInstance", + "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.", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://www.trendmicro.com/cloudoneconformity/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", + "NativeIaC": "", + "Other": "", + "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**", + "Url": "https://hub.prowler.com/check/ecs_instance_endpoint_protection_installed" + } + }, + "Categories": [ + "forensics-ready" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/ecs/ecs_instance_endpoint_protection_installed/ecs_instance_endpoint_protection_installed.py b/prowler/providers/alibabacloud/services/ecs/ecs_instance_endpoint_protection_installed/ecs_instance_endpoint_protection_installed.py new file mode 100644 index 0000000000..913d8bbbbc --- /dev/null +++ b/prowler/providers/alibabacloud/services/ecs/ecs_instance_endpoint_protection_installed/ecs_instance_endpoint_protection_installed.py @@ -0,0 +1,47 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.ecs.ecs_client import ecs_client +from prowler.providers.alibabacloud.services.securitycenter.securitycenter_client import ( + securitycenter_client, +) + + +class ecs_instance_endpoint_protection_installed(Check): + """Check if endpoint protection for all Virtual Machines is installed.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + # Check each ECS instance for Security Center agent + for instance in ecs_client.instances: + # Only check running instances + if instance.status.lower() not in ["running", "starting"]: + continue + + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=instance + ) + report.region = instance.region + report.resource_id = instance.id + report.resource_arn = f"acs:ecs:{instance.region}:{ecs_client.audited_account}:instance/{instance.id}" + + # Check if Security Center agent is installed + instance_key = f"{instance.region}:{instance.id}" + agent = securitycenter_client.instance_agents.get(instance_key) + + if agent: + if agent.agent_installed and agent.agent_status == "online": + report.status = "PASS" + report.status_extended = ( + f"ECS instance {instance.name if instance.name else instance.id} " + "has Security Center agent installed and online." + ) + else: + report.status = "FAIL" + report.status_extended = ( + f"ECS instance {instance.name if instance.name else instance.id} " + f"does not have Security Center agent installed or agent is {agent.agent_status}." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/ecs/ecs_instance_latest_os_patches_applied/__init__.py b/prowler/providers/alibabacloud/services/ecs/ecs_instance_latest_os_patches_applied/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..1b164dad77 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ecs/ecs_instance_latest_os_patches_applied/ecs_instance_latest_os_patches_applied.metadata.json @@ -0,0 +1,38 @@ +{ + "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" + ], + "ServiceName": "ecs", + "SubServiceName": "", + "ResourceIdTemplate": "acs:ecs:region:account-id:instance/{instance-id}", + "Severity": "high", + "ResourceType": "AlibabaCloudECSInstance", + "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**.", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://www.trendmicro.com/cloudoneconformity/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", + "NativeIaC": "", + "Other": "", + "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", + "Url": "https://hub.prowler.com/check/ecs_instance_latest_os_patches_applied" + } + }, + "Categories": [ + "vulnerabilities" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/ecs/ecs_instance_latest_os_patches_applied/ecs_instance_latest_os_patches_applied.py b/prowler/providers/alibabacloud/services/ecs/ecs_instance_latest_os_patches_applied/ecs_instance_latest_os_patches_applied.py new file mode 100644 index 0000000000..f9684abd07 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ecs/ecs_instance_latest_os_patches_applied/ecs_instance_latest_os_patches_applied.py @@ -0,0 +1,50 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.ecs.ecs_client import ecs_client +from prowler.providers.alibabacloud.services.securitycenter.securitycenter_client import ( + securitycenter_client, +) + + +class ecs_instance_latest_os_patches_applied(Check): + """Check if the latest OS patches for all Virtual Machines are applied.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + # Check each ECS instance for vulnerabilities + for instance in ecs_client.instances: + # Only check running instances + if instance.status.lower() not in ["running", "starting"]: + continue + + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=instance + ) + report.region = instance.region + report.resource_id = instance.id + report.resource_arn = f"acs:ecs:{instance.region}:{ecs_client.audited_account}:instance/{instance.id}" + + # Check if instance has vulnerabilities + instance_key = f"{instance.region}:{instance.id}" + vulnerability = securitycenter_client.instance_vulnerabilities.get( + instance_key + ) + + if vulnerability: + if vulnerability.has_vulnerabilities: + report.status = "FAIL" + report.status_extended = ( + f"ECS instance {instance.name if instance.name else instance.id} " + f"has {vulnerability.vulnerability_count} unpatched vulnerabilities. " + "Latest OS patches are not applied." + ) + else: + report.status = "PASS" + report.status_extended = ( + f"ECS instance {instance.name if instance.name else instance.id} " + "has all latest OS patches applied." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/ecs/ecs_instance_no_legacy_network/__init__.py b/prowler/providers/alibabacloud/services/ecs/ecs_instance_no_legacy_network/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..5689c45bd6 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ecs/ecs_instance_no_legacy_network/ecs_instance_no_legacy_network.metadata.json @@ -0,0 +1,38 @@ +{ + "Provider": "alibabacloud", + "CheckID": "ecs_instance_no_legacy_network", + "CheckTitle": "Legacy networks does not exist", + "CheckType": [ + "Suspicious network connection" + ], + "ServiceName": "ecs", + "SubServiceName": "", + "ResourceIdTemplate": "acs:ecs:region:account-id:instance/{instance-id}", + "Severity": "medium", + "ResourceType": "AlibabaCloudECSInstance", + "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**.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun ecs CreateInstance --InstanceName --ImageId --InstanceType --VSwitchId ", + "NativeIaC": "", + "Other": "", + "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**", + "Url": "https://hub.prowler.com/check/ecs_instance_no_legacy_network" + } + }, + "Categories": [ + "trust-boundaries" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/ecs/ecs_instance_no_legacy_network/ecs_instance_no_legacy_network.py b/prowler/providers/alibabacloud/services/ecs/ecs_instance_no_legacy_network/ecs_instance_no_legacy_network.py new file mode 100644 index 0000000000..ca1eba9efb --- /dev/null +++ b/prowler/providers/alibabacloud/services/ecs/ecs_instance_no_legacy_network/ecs_instance_no_legacy_network.py @@ -0,0 +1,34 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.ecs.ecs_client import ecs_client + + +class ecs_instance_no_legacy_network(Check): + """Check if ECS instances are not using legacy (classic) network.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + for instance in ecs_client.instances: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=instance + ) + report.region = instance.region + report.resource_id = instance.id + report.resource_arn = f"acs:ecs:{instance.region}:{ecs_client.audited_account}:instance/{instance.id}" + + if instance.network_type == "classic": + report.status = "FAIL" + report.status_extended = ( + f"ECS instance {instance.name if instance.name else instance.id} " + f"is using legacy (classic) network type." + ) + else: + report.status = "PASS" + report.status_extended = ( + f"ECS instance {instance.name if instance.name else instance.id} " + f"is using VPC network type." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_rdp_internet/__init__.py b/prowler/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_rdp_internet/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..85a9305511 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_rdp_internet/ecs_securitygroup_restrict_rdp_internet.metadata.json @@ -0,0 +1,39 @@ +{ + "Provider": "alibabacloud", + "CheckID": "ecs_securitygroup_restrict_rdp_internet", + "CheckTitle": "RDP access is restricted from the internet", + "CheckType": [ + "Unusual logon", + "Suspicious network connection" + ], + "ServiceName": "ecs", + "SubServiceName": "", + "ResourceIdTemplate": "acs:ecs:region:account-id:security-group/{security-group-id}", + "Severity": "high", + "ResourceType": "AlibabaCloudECSSecurityGroup", + "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**.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun ecs RevokeSecurityGroup --SecurityGroupId --IpProtocol tcp --PortRange 3389/3389 --SourceCidrIp 0.0.0.0/0", + "NativeIaC": "", + "Other": "", + "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**", + "Url": "https://hub.prowler.com/check/ecs_securitygroup_restrict_rdp_internet" + } + }, + "Categories": [ + "internet-exposed" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_rdp_internet/ecs_securitygroup_restrict_rdp_internet.py b/prowler/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_rdp_internet/ecs_securitygroup_restrict_rdp_internet.py new file mode 100644 index 0000000000..72e579bbc6 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_rdp_internet/ecs_securitygroup_restrict_rdp_internet.py @@ -0,0 +1,68 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.ecs.ecs_client import ecs_client +from prowler.providers.alibabacloud.services.ecs.lib.security_groups import ( + is_public_cidr, + port_in_range, +) + + +class ecs_securitygroup_restrict_rdp_internet(Check): + """Check if security groups restrict RDP (port 3389) access from the internet.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + check_port = 3389 # RDP port + + for sg_arn, security_group in ecs_client.security_groups.items(): + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=security_group + ) + report.region = security_group.region + report.resource_id = security_group.id + report.resource_arn = security_group.arn + + # Check ingress rules for unrestricted access to RDP port + has_unrestricted_access = False + + for ingress_rule in security_group.ingress_rules: + # Check if rule allows traffic (policy == "accept") + if ingress_rule.get("policy", "accept") != "accept": + continue + + # Check protocol (tcp for RDP) + protocol = ingress_rule.get("ip_protocol", "").lower() + if protocol not in ["tcp", "all"]: + continue + + # Check if source is public (0.0.0.0/0) + source_cidr = ingress_rule.get("source_cidr_ip", "") + if not is_public_cidr(source_cidr): + continue + + # Check if port range includes RDP port + port_range = ingress_rule.get("port_range", "") + + if protocol == "all": + # If protocol is "all", all ports are open + has_unrestricted_access = True + break + elif port_in_range(port_range, check_port): + has_unrestricted_access = True + break + + if has_unrestricted_access: + report.status = "FAIL" + report.status_extended = ( + f"Security group {security_group.name} ({security_group.id}) " + f"has Microsoft RDP port 3389 open to the internet (0.0.0.0/0)." + ) + else: + report.status = "PASS" + report.status_extended = ( + f"Security group {security_group.name} ({security_group.id}) " + f"does not have Microsoft RDP port 3389 open to the internet." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_ssh_internet/__init__.py b/prowler/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_ssh_internet/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..a6a21cd3d1 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_ssh_internet/ecs_securitygroup_restrict_ssh_internet.metadata.json @@ -0,0 +1,39 @@ +{ + "Provider": "alibabacloud", + "CheckID": "ecs_securitygroup_restrict_ssh_internet", + "CheckTitle": "SSH access is restricted from the internet", + "CheckType": [ + "Unusual logon", + "Suspicious network connection" + ], + "ServiceName": "ecs", + "SubServiceName": "", + "ResourceIdTemplate": "acs:ecs:region:account-id:security-group/{security-group-id}", + "Severity": "high", + "ResourceType": "AlibabaCloudECSSecurityGroup", + "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**.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun ecs RevokeSecurityGroup --SecurityGroupId --IpProtocol tcp --PortRange 22/22 --SourceCidrIp 0.0.0.0/0", + "NativeIaC": "", + "Other": "", + "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**", + "Url": "https://hub.prowler.com/check/ecs_securitygroup_restrict_ssh_internet" + } + }, + "Categories": [ + "internet-exposed" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_ssh_internet/ecs_securitygroup_restrict_ssh_internet.py b/prowler/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_ssh_internet/ecs_securitygroup_restrict_ssh_internet.py new file mode 100644 index 0000000000..9dfdd182e1 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_ssh_internet/ecs_securitygroup_restrict_ssh_internet.py @@ -0,0 +1,68 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.ecs.ecs_client import ecs_client +from prowler.providers.alibabacloud.services.ecs.lib.security_groups import ( + is_public_cidr, + port_in_range, +) + + +class ecs_securitygroup_restrict_ssh_internet(Check): + """Check if security groups restrict SSH (port 22) access from the internet.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + check_port = 22 # SSH port + + for sg_arn, security_group in ecs_client.security_groups.items(): + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=security_group + ) + report.region = security_group.region + report.resource_id = security_group.id + report.resource_arn = security_group.arn + + # Check ingress rules for unrestricted access to SSH port + has_unrestricted_access = False + + for ingress_rule in security_group.ingress_rules: + # Check if rule allows traffic (policy == "accept") + if ingress_rule.get("policy", "accept") != "accept": + continue + + # Check protocol (tcp for SSH) + protocol = ingress_rule.get("ip_protocol", "").lower() + if protocol not in ["tcp", "all"]: + continue + + # Check if source is public (0.0.0.0/0) + source_cidr = ingress_rule.get("source_cidr_ip", "") + if not is_public_cidr(source_cidr): + continue + + # Check if port range includes SSH port + port_range = ingress_rule.get("port_range", "") + + if protocol == "all": + # If protocol is "all", all ports are open + has_unrestricted_access = True + break + elif port_in_range(port_range, check_port): + has_unrestricted_access = True + break + + if has_unrestricted_access: + report.status = "FAIL" + report.status_extended = ( + f"Security group {security_group.name} ({security_group.id}) " + f"has SSH port 22 open to the internet (0.0.0.0/0)." + ) + else: + report.status = "PASS" + report.status_extended = ( + f"Security group {security_group.name} ({security_group.id}) " + f"does not have SSH port 22 open to the internet." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/ecs/ecs_service.py b/prowler/providers/alibabacloud/services/ecs/ecs_service.py new file mode 100644 index 0000000000..f6433a51dd --- /dev/null +++ b/prowler/providers/alibabacloud/services/ecs/ecs_service.py @@ -0,0 +1,380 @@ +from datetime import datetime +from typing import Optional + +from alibabacloud_ecs20140526 import models as ecs_models +from pydantic.v1 import BaseModel + +from prowler.lib.logger import logger +from prowler.lib.scan_filters.scan_filters import is_resource_filtered +from prowler.providers.alibabacloud.lib.service.service import AlibabaCloudService + + +class ECS(AlibabaCloudService): + """ + ECS (Elastic Compute Service) service class for Alibaba Cloud. + + This class provides methods to interact with Alibaba Cloud ECS service + to retrieve instances, security groups, etc. + """ + + def __init__(self, provider): + # Call AlibabaCloudService's __init__ + super().__init__(__class__.__name__, provider, global_service=False) + + # Fetch ECS resources + self.instances = [] + self.__threading_call__(self._describe_instances) + self.security_groups = {} + self.__threading_call__(self._describe_security_groups) + self.disks = [] + self.__threading_call__(self._describe_disks) + + def _describe_instances(self, regional_client): + """List all ECS instances in the region.""" + region = getattr(regional_client, "region", "unknown") + logger.info(f"ECS - Describing Instances in {region}...") + + try: + request = ecs_models.DescribeInstancesRequest() + request.region_id = region + # Get all instances (paginated) + page_number = 1 + page_size = 50 + + while True: + request.page_number = page_number + request.page_size = page_size + response = regional_client.describe_instances(request) + + if response and response.body and response.body.instances: + instances_data = response.body.instances.instance + if not instances_data: + break + + for instance_data in instances_data: + instance_id = instance_data.instance_id + if not self.audit_resources or is_resource_filtered( + instance_id, self.audit_resources + ): + # Check network type + # InstanceNetworkType can be "classic" or "vpc" + # If VpcAttributes exists, it's VPC; if not, it might be classic + network_type = "vpc" # Default to VPC + vpc_attributes = getattr( + instance_data, "vpc_attributes", None + ) + instance_network_type = getattr( + instance_data, "instance_network_type", None + ) + + # Determine network type + if instance_network_type: + network_type = instance_network_type + elif not vpc_attributes: + # If no VPC attributes, it's likely classic network + network_type = "classic" + + vpc_id = "" + if vpc_attributes: + vpc_id = getattr(vpc_attributes, "vpc_id", "") + + self.instances.append( + Instance( + id=instance_id, + name=getattr( + instance_data, "instance_name", instance_id + ), + region=region, + status=getattr(instance_data, "status", ""), + instance_type=getattr( + instance_data, "instance_type", "" + ), + network_type=network_type, + vpc_id=vpc_id, + create_time=getattr( + instance_data, "creation_time", None + ), + ) + ) + + # Check if there are more pages + total_count = getattr(response.body, "total_count", 0) + if page_number * page_size >= total_count: + break + page_number += 1 + else: + break + + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def _describe_security_groups(self, regional_client): + """List all security groups and their rules in the region.""" + region = getattr(regional_client, "region", "unknown") + logger.info(f"ECS - Describing Security Groups in {region}...") + + try: + request = ecs_models.DescribeSecurityGroupsRequest() + request.region_id = region + # Get all security groups (paginated) + page_number = 1 + page_size = 50 + + while True: + request.page_number = page_number + request.page_size = page_size + response = regional_client.describe_security_groups(request) + + if response and response.body and response.body.security_groups: + security_groups_data = response.body.security_groups.security_group + if not security_groups_data: + break + + for sg_data in security_groups_data: + sg_id = sg_data.security_group_id + if not self.audit_resources or is_resource_filtered( + sg_id, self.audit_resources + ): + # Get security group rules + ingress_rules = [] + egress_rules = [] + + # Get ingress rules + try: + rules_request = ( + ecs_models.DescribeSecurityGroupAttributeRequest() + ) + rules_request.security_group_id = sg_id + rules_request.region_id = region + rules_request.direction = "ingress" + rules_response = ( + regional_client.describe_security_group_attribute( + rules_request + ) + ) + + if ( + rules_response + and rules_response.body + and rules_response.body.permissions + ): + permissions = ( + rules_response.body.permissions.permission + ) + if permissions: + for rule in permissions: + ingress_rules.append( + { + "port_range": getattr( + rule, "port_range", "" + ), + "source_cidr_ip": getattr( + rule, "source_cidr_ip", "" + ), + "ip_protocol": getattr( + rule, "ip_protocol", "" + ), + "policy": getattr( + rule, "policy", "accept" + ), + } + ) + except Exception as error: + logger.warning( + f"Could not get ingress rules for security group {sg_id}: {error}" + ) + + # Get egress rules + try: + rules_request = ( + ecs_models.DescribeSecurityGroupAttributeRequest() + ) + rules_request.security_group_id = sg_id + rules_request.region_id = region + rules_request.direction = "egress" + rules_response = ( + regional_client.describe_security_group_attribute( + rules_request + ) + ) + + if ( + rules_response + and rules_response.body + and rules_response.body.permissions + ): + permissions = ( + rules_response.body.permissions.permission + ) + if permissions: + for rule in permissions: + egress_rules.append( + { + "port_range": getattr( + rule, "port_range", "" + ), + "dest_cidr_ip": getattr( + rule, "dest_cidr_ip", "" + ), + "ip_protocol": getattr( + rule, "ip_protocol", "" + ), + "policy": getattr( + rule, "policy", "accept" + ), + } + ) + except Exception as error: + logger.warning( + f"Could not get egress rules for security group {sg_id}: {error}" + ) + + sg_arn = f"acs:ecs:{region}:{self.audited_account}:security-group/{sg_id}" + self.security_groups[sg_arn] = SecurityGroup( + id=sg_id, + name=getattr(sg_data, "security_group_name", sg_id), + region=region, + arn=sg_arn, + vpc_id=getattr(sg_data, "vpc_id", ""), + description=getattr(sg_data, "description", ""), + ingress_rules=ingress_rules, + egress_rules=egress_rules, + ) + + # Check if there are more pages + total_count = getattr(response.body, "total_count", 0) + if page_number * page_size >= total_count: + break + page_number += 1 + else: + break + + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def _describe_disks(self, regional_client): + """List all disks in the region.""" + region = getattr(regional_client, "region", "unknown") + logger.info(f"ECS - Describing Disks in {region}...") + + try: + request = ecs_models.DescribeDisksRequest() + request.region_id = region + # Get all disks (paginated) + page_number = 1 + page_size = 50 + + while True: + request.page_number = page_number + request.page_size = page_size + response = regional_client.describe_disks(request) + + if response and response.body and response.body.disks: + disks_data = response.body.disks.disk + if not disks_data: + break + + for disk_data in disks_data: + disk_id = disk_data.disk_id + if not self.audit_resources or is_resource_filtered( + disk_id, self.audit_resources + ): + # Check if disk is attached + attached_instance_id = getattr(disk_data, "instance_id", "") + is_attached = bool(attached_instance_id) + + # Check encryption status + # In Alibaba Cloud, encryption can be indicated by: + # 1. encrypted field (boolean) + # 2. encryption_algorithm field (non-empty string) + # 3. kms_key_id field (non-empty string) + encrypted = getattr(disk_data, "encrypted", False) + encryption_algorithm = getattr( + disk_data, "encryption_algorithm", "" + ) + kms_key_id = getattr(disk_data, "kms_key_id", "") + + # Disk is encrypted if any of these conditions are true + is_encrypted = ( + encrypted + or bool(encryption_algorithm) + or bool(kms_key_id) + ) + + self.disks.append( + Disk( + id=disk_id, + name=getattr(disk_data, "disk_name", disk_id), + region=region, + status=getattr(disk_data, "status", ""), + disk_category=getattr(disk_data, "category", ""), + size=getattr(disk_data, "size", 0), + is_attached=is_attached, + attached_instance_id=attached_instance_id, + is_encrypted=is_encrypted, + encryption_algorithm=encryption_algorithm or "", + create_time=getattr( + disk_data, "creation_time", None + ), + ) + ) + + # Check if there are more pages + total_count = getattr(response.body, "total_count", 0) + if page_number * page_size >= total_count: + break + page_number += 1 + else: + break + + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + +# Models for ECS service +class Instance(BaseModel): + """ECS Instance model.""" + + id: str + name: str + region: str + status: str + instance_type: str + network_type: str # "classic" or "vpc" + vpc_id: str = "" + create_time: Optional[datetime] = None + + +class SecurityGroup(BaseModel): + """ECS Security Group model.""" + + id: str + name: str + region: str + arn: str + vpc_id: str = "" + description: str = "" + ingress_rules: list[dict] = [] + egress_rules: list[dict] = [] + + +class Disk(BaseModel): + """ECS Disk model.""" + + id: str + name: str + region: str + status: str + disk_category: str + size: int + is_attached: bool + attached_instance_id: str = "" + is_encrypted: bool + encryption_algorithm: str = "" + create_time: Optional[datetime] = None diff --git a/prowler/providers/alibabacloud/services/ecs/ecs_unattached_disk_encrypted/__init__.py b/prowler/providers/alibabacloud/services/ecs/ecs_unattached_disk_encrypted/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..6287933aeb --- /dev/null +++ b/prowler/providers/alibabacloud/services/ecs/ecs_unattached_disk_encrypted/ecs_unattached_disk_encrypted.metadata.json @@ -0,0 +1,38 @@ +{ + "Provider": "alibabacloud", + "CheckID": "ecs_unattached_disk_encrypted", + "CheckTitle": "Unattached disks are encrypted", + "CheckType": [ + "Sensitive file tampering" + ], + "ServiceName": "ecs", + "SubServiceName": "", + "ResourceIdTemplate": "acs:ecs:region:account-id:disk/{disk-id}", + "Severity": "high", + "ResourceType": "AlibabaCloudECSDisk", + "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**.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun ecs CreateDisk --DiskName --Size --Encrypted true --KmsKeyId ", + "NativeIaC": "", + "Other": "", + "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.", + "Url": "https://hub.prowler.com/check/ecs_unattached_disk_encrypted" + } + }, + "Categories": [ + "encryption" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/ecs/ecs_unattached_disk_encrypted/ecs_unattached_disk_encrypted.py b/prowler/providers/alibabacloud/services/ecs/ecs_unattached_disk_encrypted/ecs_unattached_disk_encrypted.py new file mode 100644 index 0000000000..0f809be3ca --- /dev/null +++ b/prowler/providers/alibabacloud/services/ecs/ecs_unattached_disk_encrypted/ecs_unattached_disk_encrypted.py @@ -0,0 +1,38 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.ecs.ecs_client import ecs_client + + +class ecs_unattached_disk_encrypted(Check): + """Check if unattached disks are encrypted.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + for disk in ecs_client.disks: + # Only check unattached disks + if not disk.is_attached: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=disk + ) + report.region = disk.region + report.resource_id = disk.id + report.resource_arn = ( + f"acs:ecs:{disk.region}:{ecs_client.audited_account}:disk/{disk.id}" + ) + + if disk.is_encrypted: + report.status = "PASS" + report.status_extended = ( + f"Unattached disk {disk.name if disk.name else disk.id} " + f"is encrypted." + ) + else: + report.status = "FAIL" + report.status_extended = ( + f"Unattached disk {disk.name if disk.name else disk.id} " + f"is not encrypted." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/ecs/lib/security_groups.py b/prowler/providers/alibabacloud/services/ecs/lib/security_groups.py new file mode 100644 index 0000000000..08efedebb3 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ecs/lib/security_groups.py @@ -0,0 +1,23 @@ +def is_public_cidr(cidr: str) -> bool: + """Return True when the CIDR represents public/unrestricted access.""" + return cidr in ("0.0.0.0/0", "::/0") + + +def port_in_range(port_range: str, target_port: int) -> bool: + """ + Check if target_port is within the provided port range. + + Port range examples: + - "3389/3389" -> single port range + - "22" -> single port + """ + if not port_range: + return False + + try: + if "/" in port_range: + from_port, to_port = map(int, port_range.split("/")) + return from_port <= target_port <= to_port + return int(port_range) == target_port + except (ValueError, AttributeError): + return False diff --git a/prowler/providers/alibabacloud/services/oss/__init__.py b/prowler/providers/alibabacloud/services/oss/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/alibabacloud/services/oss/oss_bucket_logging_enabled/__init__.py b/prowler/providers/alibabacloud/services/oss/oss_bucket_logging_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..d56d211abc --- /dev/null +++ b/prowler/providers/alibabacloud/services/oss/oss_bucket_logging_enabled/oss_bucket_logging_enabled.metadata.json @@ -0,0 +1,39 @@ +{ + "Provider": "alibabacloud", + "CheckID": "oss_bucket_logging_enabled", + "CheckTitle": "Logging is enabled for OSS buckets", + "CheckType": [ + "Sensitive file tampering", + "Cloud threat detection" + ], + "ServiceName": "oss", + "SubServiceName": "", + "ResourceIdTemplate": "acs:oss::account-id:bucket-name", + "Severity": "medium", + "ResourceType": "AlibabaCloudOSSBucket", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "ossutil logging --method put oss:// --target-bucket --target-prefix ", + "NativeIaC": "", + "Other": "", + "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**", + "Url": "https://hub.prowler.com/check/oss_bucket_logging_enabled" + } + }, + "Categories": [ + "logging" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/oss/oss_bucket_logging_enabled/oss_bucket_logging_enabled.py b/prowler/providers/alibabacloud/services/oss/oss_bucket_logging_enabled/oss_bucket_logging_enabled.py new file mode 100644 index 0000000000..b747367aa4 --- /dev/null +++ b/prowler/providers/alibabacloud/services/oss/oss_bucket_logging_enabled/oss_bucket_logging_enabled.py @@ -0,0 +1,37 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.oss.oss_client import oss_client + + +class oss_bucket_logging_enabled(Check): + """Check if logging is enabled for OSS buckets.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + for bucket in oss_client.buckets.values(): + report = CheckReportAlibabaCloud(metadata=self.metadata(), resource=bucket) + report.region = bucket.region + report.resource_id = bucket.name + report.resource_arn = bucket.arn + + if bucket.logging_enabled: + report.status = "PASS" + if bucket.logging_target_bucket: + report.status_extended = ( + f"OSS bucket {bucket.name} has logging enabled. " + f"Logs are stored in bucket '{bucket.logging_target_bucket}' " + f"with prefix {bucket.logging_target_prefix}." + ) + else: + report.status_extended = ( + f"OSS bucket {bucket.name} has logging enabled." + ) + else: + report.status = "FAIL" + report.status_extended = ( + f"OSS bucket {bucket.name} does not have logging enabled." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/oss/oss_bucket_not_publicly_accessible/__init__.py b/prowler/providers/alibabacloud/services/oss/oss_bucket_not_publicly_accessible/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..ff9210b20e --- /dev/null +++ b/prowler/providers/alibabacloud/services/oss/oss_bucket_not_publicly_accessible/oss_bucket_not_publicly_accessible.metadata.json @@ -0,0 +1,39 @@ +{ + "Provider": "alibabacloud", + "CheckID": "oss_bucket_not_publicly_accessible", + "CheckTitle": "OSS bucket is not anonymously or publicly accessible", + "CheckType": [ + "Sensitive file tampering", + "Cloud threat detection" + ], + "ServiceName": "oss", + "SubServiceName": "", + "ResourceIdTemplate": "acs:oss::account-id:bucket-name", + "Severity": "critical", + "ResourceType": "AlibabaCloudOSSBucket", + "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**.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun oss PutBucketAcl --bucket --acl private", + "NativeIaC": "", + "Other": "", + "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**", + "Url": "https://hub.prowler.com/check/oss_bucket_not_publicly_accessible" + } + }, + "Categories": [ + "internet-exposed" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/oss/oss_bucket_not_publicly_accessible/oss_bucket_not_publicly_accessible.py b/prowler/providers/alibabacloud/services/oss/oss_bucket_not_publicly_accessible/oss_bucket_not_publicly_accessible.py new file mode 100644 index 0000000000..1fc378abc3 --- /dev/null +++ b/prowler/providers/alibabacloud/services/oss/oss_bucket_not_publicly_accessible/oss_bucket_not_publicly_accessible.py @@ -0,0 +1,89 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.oss.oss_client import oss_client + + +def _is_policy_public(policy_document: dict) -> bool: + """ + Check if a bucket policy allows public access. + + A policy is considered public if it has a statement with: + - Effect: "Allow" + - Principal: ["*"] (or contains "*") + - No Condition elements + + Args: + policy_document: The parsed policy document as a dictionary. + + Returns: + bool: True if policy allows public access, False otherwise. + """ + if not policy_document: + return False + + statements = policy_document.get("Statement", []) + if not isinstance(statements, list): + statements = [statements] + + for statement in statements: + effect = statement.get("Effect", "") + principal = statement.get("Principal", []) + condition = statement.get("Condition") + + # If there's a condition, it's not truly public + if condition: + continue + + if effect == "Allow": + # Check if Principal is "*" or contains "*" + if isinstance(principal, list): + if "*" in principal: + return True + elif principal == "*": + return True + + return False + + +class oss_bucket_not_publicly_accessible(Check): + """Check if OSS bucket is not anonymously or publicly accessible.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + for bucket in oss_client.buckets.values(): + report = CheckReportAlibabaCloud(metadata=self.metadata(), resource=bucket) + report.region = bucket.region + report.resource_id = bucket.name + report.resource_arn = bucket.arn + + # Check bucket ACL + acl_public = False + if bucket.acl and bucket.acl != "private": + if bucket.acl in ["public-read", "public-read-write"]: + acl_public = True + + # Check bucket policy + policy_public = _is_policy_public(bucket.policy) + + # Determine status + if acl_public or policy_public: + report.status = "FAIL" + issues = [] + if acl_public: + issues.append(f"Bucket ACL is set to {bucket.acl}") + if policy_public: + issues.append("Bucket policy allows public access (Principal: '*')") + report.status_extended = ( + f"OSS bucket {bucket.name} is publicly accessible. " + + "; ".join(issues) + ) + else: + report.status = "PASS" + report.status_extended = ( + f"OSS bucket {bucket.name} is not publicly accessible. " + f"ACL is {bucket.acl} and bucket policy does not allow public access." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/oss/oss_bucket_secure_transport_enabled/__init__.py b/prowler/providers/alibabacloud/services/oss/oss_bucket_secure_transport_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..31fafbf8d0 --- /dev/null +++ b/prowler/providers/alibabacloud/services/oss/oss_bucket_secure_transport_enabled/oss_bucket_secure_transport_enabled.metadata.json @@ -0,0 +1,38 @@ +{ + "Provider": "alibabacloud", + "CheckID": "oss_bucket_secure_transport_enabled", + "CheckTitle": "Secure transfer required is set to Enabled", + "CheckType": [ + "Sensitive file tampering" + ], + "ServiceName": "oss", + "SubServiceName": "", + "ResourceIdTemplate": "acs:oss::account-id:bucket-name", + "Severity": "high", + "ResourceType": "AlibabaCloudOSSBucket", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "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**", + "Url": "https://hub.prowler.com/check/oss_bucket_secure_transport_enabled" + } + }, + "Categories": [ + "encryption" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/oss/oss_bucket_secure_transport_enabled/oss_bucket_secure_transport_enabled.py b/prowler/providers/alibabacloud/services/oss/oss_bucket_secure_transport_enabled/oss_bucket_secure_transport_enabled.py new file mode 100644 index 0000000000..1a37cb49aa --- /dev/null +++ b/prowler/providers/alibabacloud/services/oss/oss_bucket_secure_transport_enabled/oss_bucket_secure_transport_enabled.py @@ -0,0 +1,87 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.oss.oss_client import oss_client + + +def _is_secure_transport_enforced(policy_document: dict) -> bool: + """ + Check if a bucket policy enforces secure transport (HTTPS only). + + A policy enforces secure transport if it has: + - "Condition": {"Bool": {"acs:SecureTransport": ["true"]}} with "Effect": "Allow" + OR + - "Condition": {"Bool": {"acs:SecureTransport": ["false"]}} with "Effect": "Deny" + + Args: + policy_document: The parsed policy document as a dictionary. + + Returns: + bool: True if secure transport is enforced, False otherwise. + """ + if not policy_document: + return False + + statements = policy_document.get("Statement", []) + if not isinstance(statements, list): + statements = [statements] + + for statement in statements: + effect = statement.get("Effect", "") + condition = statement.get("Condition", {}) + + if not condition: + continue + + # Check for SecureTransport condition + bool_condition = condition.get("Bool", {}) + secure_transport = bool_condition.get("acs:SecureTransport", []) + + if secure_transport: + # Check if it's a list or single value + if isinstance(secure_transport, list): + secure_transport_value = ( + secure_transport[0] if secure_transport else None + ) + else: + secure_transport_value = secure_transport + + # Secure transport is enforced if: + # 1. Effect: Allow with SecureTransport: true (only HTTPS allowed) + # 2. Effect: Deny with SecureTransport: false (HTTP denied) + if effect == "Allow" and secure_transport_value == "true": + return True + elif effect == "Deny" and secure_transport_value == "false": + return True + + return False + + +class oss_bucket_secure_transport_enabled(Check): + """Check if 'Secure transfer required' is set to 'Enabled' for OSS buckets.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + for bucket in oss_client.buckets.values(): + report = CheckReportAlibabaCloud(metadata=self.metadata(), resource=bucket) + report.region = bucket.region + report.resource_id = bucket.name + report.resource_arn = bucket.arn + + # Check if secure transport is enforced via bucket policy + secure_transport_enforced = _is_secure_transport_enforced(bucket.policy) + + if secure_transport_enforced: + report.status = "PASS" + report.status_extended = ( + f"OSS bucket {bucket.name} has secure transfer required enabled." + ) + else: + report.status = "FAIL" + if bucket.policy: + report.status_extended = f"OSS bucket {bucket.name} does not have secure transfer required enabled." + else: + report.status_extended = f"OSS bucket {bucket.name} does not have secure transfer required enabled." + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/oss/oss_client.py b/prowler/providers/alibabacloud/services/oss/oss_client.py new file mode 100644 index 0000000000..6de4f0874a --- /dev/null +++ b/prowler/providers/alibabacloud/services/oss/oss_client.py @@ -0,0 +1,4 @@ +from prowler.providers.alibabacloud.services.oss.oss_service import OSS +from prowler.providers.common.provider import Provider + +oss_client = OSS(Provider.get_global_provider()) diff --git a/prowler/providers/alibabacloud/services/oss/oss_service.py b/prowler/providers/alibabacloud/services/oss/oss_service.py new file mode 100644 index 0000000000..49db1b870c --- /dev/null +++ b/prowler/providers/alibabacloud/services/oss/oss_service.py @@ -0,0 +1,317 @@ +import base64 +import hashlib +import hmac +import json +from datetime import datetime +from email.utils import formatdate +from threading import Lock +from typing import Optional +from xml.etree import ElementTree + +import requests +from pydantic.v1 import BaseModel + +from prowler.lib.logger import logger +from prowler.lib.scan_filters.scan_filters import is_resource_filtered +from prowler.providers.alibabacloud.lib.service.service import AlibabaCloudService + + +class OSS(AlibabaCloudService): + """ + OSS (Object Storage Service) service class for Alibaba Cloud. + + This class provides methods to interact with Alibaba Cloud OSS service + to retrieve buckets, ACLs, and policies. + """ + + def __init__(self, provider): + # Call AlibabaCloudService's __init__ + # Treat as regional for client generation consistency with other services + super().__init__(__class__.__name__, provider, global_service=False) + self._buckets_lock = Lock() + + # Fetch OSS resources + self.buckets = {} + self.__threading_call__(self._list_buckets) + self.__threading_call__(self._get_bucket_acl, self.buckets.values()) + self.__threading_call__(self._get_bucket_policy, self.buckets.values()) + self.__threading_call__(self._get_bucket_logging, self.buckets.values()) + + def _list_buckets(self, regional_client=None): + region = "unknown" + try: + regional_client = regional_client or self.client + region = getattr(regional_client, "region", self.region) + endpoint = f"oss-{region}.aliyuncs.com" + endpoint_label = f"region {region}" + + credentials = self.session.get_credentials() + + date_str = formatdate(usegmt=True) + headers = { + "Date": date_str, + "Host": endpoint, + } + canonical_headers = [] + if credentials.security_token: + headers["x-oss-security-token"] = credentials.security_token + canonical_headers.append( + f"x-oss-security-token:{credentials.security_token}" + ) + + canonical_headers_str = "" + if canonical_headers: + canonical_headers.sort() + canonical_headers_str = "\n".join(canonical_headers) + "\n" + + string_to_sign = f"GET\n\n\n{date_str}\n{canonical_headers_str}/" + signature = base64.b64encode( + hmac.new( + credentials.access_key_secret.encode("utf-8"), + string_to_sign.encode("utf-8"), + hashlib.sha1, + ).digest() + ).decode() + headers["Authorization"] = f"OSS {credentials.access_key_id}:{signature}" + + url = f"https://{endpoint}/" + response = requests.get(url, headers=headers, timeout=10) + if response.status_code != 200: + logger.error( + f"OSS - HTTP listing {endpoint_label} returned {response.status_code}: {response.text}" + ) + return + + try: + xml_root = ElementTree.fromstring(response.text) + except ElementTree.ParseError as error: + logger.error( + f"OSS - HTTP listing {endpoint_label} XML parse error: {error}" + ) + return + + for bucket_elem in xml_root.findall(".//Bucket"): + bucket_name = bucket_elem.findtext("Name", default="") + if not bucket_name: + continue + location = bucket_elem.findtext("Location", default=self.region) + arn = f"acs:oss::{self.audited_account}:{bucket_name}" + if self.audit_resources and not is_resource_filtered( + arn, self.audit_resources + ): + continue + + creation_str = bucket_elem.findtext("CreationDate") + with self._buckets_lock: + self.buckets[arn] = Bucket( + arn=arn, + name=bucket_name, + region=self._normalize_bucket_region(location), + creation_date=self._parse_creation_date(creation_str), + ) + except Exception as error: + logger.error( + f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return + + def _get_bucket_acl(self, bucket): + """Get bucket ACL.""" + logger.info(f"OSS - Getting ACL for bucket {bucket.name}...") + try: + # Get OSS client for the bucket's region + # OSS bucket operations use regional endpoint: oss-{region}.aliyuncs.com + oss_client = self.session.client("oss", bucket.region) + + # Get bucket ACL + response = oss_client.get_bucket_acl(bucket.name) + + if response and response.body: + # ACL can be retrieved from the response + # The ACL value is typically in the response body + acl_value = getattr(response.body, "acl", None) + if acl_value: + # ACL values: private, public-read, public-read-write + bucket.acl = acl_value + else: + # Try to get from access_control_list if available + acl_list = getattr(response.body, "access_control_list", None) + if acl_list: + grant = getattr(acl_list, "grant", None) + if grant: + # Check grants to determine ACL type + if isinstance(grant, list): + # Check if any grant has public access + for g in grant: + permission = getattr(g, "permission", "") + if permission in ["READ", "FULL_CONTROL"]: + if permission == "READ": + bucket.acl = "public-read" + else: + bucket.acl = "public-read-write" + break + else: + bucket.acl = "private" + else: + permission = getattr(grant, "permission", "") + if permission == "READ": + bucket.acl = "public-read" + elif permission == "FULL_CONTROL": + bucket.acl = "public-read-write" + else: + bucket.acl = "private" + else: + bucket.acl = "private" + else: + bucket.acl = "private" + else: + bucket.acl = "private" + + except Exception as error: + logger.error( + f"{bucket.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def _get_bucket_policy(self, bucket): + """Get bucket policy.""" + logger.info(f"OSS - Getting policy for bucket {bucket.name}...") + try: + oss_client = self.session.client("oss", bucket.region) + + response = oss_client.get_bucket_policy(bucket.name) + + if response and response.body: + if response.body: + try: + bucket.policy = json.loads(response.body) + except json.JSONDecodeError: + bucket.policy = {} + else: + bucket.policy = {} + else: + bucket.policy = {} + + except Exception as error: + # If bucket policy doesn't exist, that's OK - it means no public access via policy + error_code = getattr(error, "code", "") + if error_code in ["NoSuchBucketPolicy", "NoSuchBucket"]: + bucket.policy = {} + else: + logger.error( + f"{bucket.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + bucket.policy = {} + + def _get_bucket_logging(self, bucket): + """Get bucket logging configuration using OSS SDK.""" + logger.info(f"OSS - Getting logging configuration for bucket {bucket.name}...") + try: + oss_client = self.session.client("oss", bucket.region) + + response = oss_client.get_bucket_logging(bucket.name) + + if response and response.body: + logging_enabled = None + if hasattr(response.body, "logging_enabled"): + logging_enabled = response.body.logging_enabled + elif hasattr(response.body, "loggingenabled"): + logging_enabled = response.body.loggingenabled + elif hasattr(response.body, "bucket_logging"): + logging_enabled = response.body.bucket_logging + + if logging_enabled: + target_bucket = None + target_prefix = None + + for attr_name in [ + "target_bucket", + "targetBucket", + "target_bucket_name", + "targetBucketName", + ]: + if hasattr(logging_enabled, attr_name): + target_bucket = getattr(logging_enabled, attr_name) + break + + for attr_name in [ + "target_prefix", + "targetPrefix", + "target_prefix_name", + "targetPrefixName", + ]: + if hasattr(logging_enabled, attr_name): + target_prefix = getattr(logging_enabled, attr_name) + break + + if target_bucket: + bucket.logging_enabled = True + bucket.logging_target_bucket = ( + str(target_bucket) if target_bucket else "" + ) + bucket.logging_target_prefix = ( + str(target_prefix) if target_prefix else "" + ) + else: + bucket.logging_enabled = False + bucket.logging_target_bucket = "" + bucket.logging_target_prefix = "" + else: + bucket.logging_enabled = False + bucket.logging_target_bucket = "" + bucket.logging_target_prefix = "" + else: + bucket.logging_enabled = False + bucket.logging_target_bucket = "" + bucket.logging_target_prefix = "" + + except Exception as error: + logger.error( + f"{bucket.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + @staticmethod + def _normalize_bucket_region(bucket_location: str) -> str: + """Normalize OSS bucket location values to region IDs.""" + if not bucket_location: + return "" + + normalized_location = bucket_location.lower() + + # Remove protocol/hostname suffix if an endpoint was returned + if ".aliyuncs.com" in normalized_location: + normalized_location = normalized_location.split(".aliyuncs.com")[0] + + # Strip leading OSS prefix (e.g., oss-ap-southeast-1 -> ap-southeast-1) + if normalized_location.startswith("oss-"): + normalized_location = normalized_location.replace("oss-", "", 1) + + return normalized_location + + @staticmethod + def _parse_creation_date(creation_date_str: Optional[str]) -> Optional[datetime]: + """Parse OSS bucket creation date strings into datetime objects.""" + if not creation_date_str: + return None + + for date_format in ("%Y-%m-%dT%H:%M:%S.%f%z", "%Y-%m-%dT%H:%M:%S%z"): + try: + return datetime.strptime( + creation_date_str.replace("Z", "+00:00"), date_format + ) + except (ValueError, AttributeError): + continue + return None + + +class Bucket(BaseModel): + """OSS Bucket model.""" + + arn: str + name: str + region: str + acl: Optional[str] = None # private, public-read, public-read-write + policy: dict = {} + logging_enabled: bool = False + logging_target_bucket: str = "" + logging_target_prefix: str = "" + creation_date: Optional[datetime] = None diff --git a/prowler/providers/alibabacloud/services/ram/__init__.py b/prowler/providers/alibabacloud/services/ram/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/alibabacloud/services/ram/ram_client.py b/prowler/providers/alibabacloud/services/ram/ram_client.py new file mode 100644 index 0000000000..f95941bef0 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_client.py @@ -0,0 +1,4 @@ +from prowler.providers.alibabacloud.services.ram.ram_service import RAM +from prowler.providers.common.provider import Provider + +ram_client = RAM(Provider.get_global_provider()) diff --git a/prowler/providers/alibabacloud/services/ram/ram_no_root_access_key/__init__.py b/prowler/providers/alibabacloud/services/ram/ram_no_root_access_key/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..3463199997 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_no_root_access_key/ram_no_root_access_key.metadata.json @@ -0,0 +1,39 @@ +{ + "Provider": "alibabacloud", + "CheckID": "ram_no_root_access_key", + "CheckTitle": "No root account access key exists", + "CheckType": [ + "Unusual logon", + "Cloud threat detection" + ], + "ServiceName": "ram", + "SubServiceName": "", + "ResourceIdTemplate": "acs:ram::account-id:root", + "Severity": "critical", + "ResourceType": "AlibabaCloudRAMAccessKey", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun ram DeleteAccessKey --UserAccessKeyId ", + "NativeIaC": "", + "Other": "", + "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", + "Url": "https://hub.prowler.com/check/ram_no_root_access_key" + } + }, + "Categories": [ + "internet-exposed" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/ram/ram_no_root_access_key/ram_no_root_access_key.py b/prowler/providers/alibabacloud/services/ram/ram_no_root_access_key/ram_no_root_access_key.py new file mode 100644 index 0000000000..a4290b1d51 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_no_root_access_key/ram_no_root_access_key.py @@ -0,0 +1,33 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.ram.ram_client import ram_client + + +class ram_no_root_access_key(Check): + """Check if root account has no access keys.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + report = CheckReportAlibabaCloud(metadata=self.metadata(), resource={}) + report.region = ram_client.region + report.resource_id = "" + report.resource_arn = f"acs:ram::{ram_client.audited_account}:root" + + # Check if we're authenticated as root account + # Use the is_root flag from identity (set via STS GetCallerIdentity) + is_root = ram_client.provider.identity.is_root + + if not is_root: + # If authenticated as RAM user, we can't verify root account access keys + report.status = "MANUAL" + report.status_extended = "Cannot verify root account access keys: authenticated as RAM user. This check requires root account credentials." + elif ram_client.root_access_keys: + report.status = "FAIL" + report.status_extended = "Root account has access keys." + else: + report.status = "PASS" + report.status_extended = "Root account does not have access keys." + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/ram/ram_password_policy_lowercase/__init__.py b/prowler/providers/alibabacloud/services/ram/ram_password_policy_lowercase/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..a379c6865e --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_password_policy_lowercase/ram_password_policy_lowercase.metadata.json @@ -0,0 +1,39 @@ +{ + "Provider": "alibabacloud", + "CheckID": "ram_password_policy_lowercase", + "CheckTitle": "RAM password policy requires at least one lowercase letter", + "CheckType": [ + "Unusual logon", + "Abnormal account" + ], + "ServiceName": "ram", + "SubServiceName": "", + "ResourceIdTemplate": "acs:ram::account-id:password-policy", + "Severity": "medium", + "ResourceType": "AlibabaCloudRAMPasswordPolicy", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun ram SetPasswordPolicy --RequireLowercaseCharacters true", + "NativeIaC": "", + "Other": "", + "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**", + "Url": "https://hub.prowler.com/check/ram_password_policy_lowercase" + } + }, + "Categories": [ + "secrets" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/ram/ram_password_policy_lowercase/ram_password_policy_lowercase.py b/prowler/providers/alibabacloud/services/ram/ram_password_policy_lowercase/ram_password_policy_lowercase.py new file mode 100644 index 0000000000..64331ee7e6 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_password_policy_lowercase/ram_password_policy_lowercase.py @@ -0,0 +1,32 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.ram.ram_client import ram_client + + +class ram_password_policy_lowercase(Check): + """Check if RAM password policy requires at least one lowercase letter.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + if ram_client.password_policy: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=ram_client.password_policy + ) + report.region = ram_client.region + report.resource_id = f"{ram_client.audited_account}-password-policy" + report.resource_arn = ( + f"acs:ram::{ram_client.audited_account}:password-policy" + ) + + if ram_client.password_policy.require_lowercase_characters: + report.status = "PASS" + report.status_extended = ( + "RAM password policy requires at least one lowercase letter." + ) + else: + report.status = "FAIL" + report.status_extended = "RAM password policy does not require at least one lowercase letter." + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/ram/ram_password_policy_max_login_attempts/__init__.py b/prowler/providers/alibabacloud/services/ram/ram_password_policy_max_login_attempts/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..f4c6c8585b --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_password_policy_max_login_attempts/ram_password_policy_max_login_attempts.metadata.json @@ -0,0 +1,39 @@ +{ + "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" + ], + "ServiceName": "ram", + "SubServiceName": "", + "ResourceIdTemplate": "acs:ram::account-id:password-policy", + "Severity": "medium", + "ResourceType": "AlibabaCloudRAMPasswordPolicy", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun ram SetPasswordPolicy --MaxLoginAttemps 5", + "NativeIaC": "", + "Other": "", + "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**", + "Url": "https://hub.prowler.com/check/ram_password_policy_max_login_attempts" + } + }, + "Categories": [ + "secrets" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/ram/ram_password_policy_max_login_attempts/ram_password_policy_max_login_attempts.py b/prowler/providers/alibabacloud/services/ram/ram_password_policy_max_login_attempts/ram_password_policy_max_login_attempts.py new file mode 100644 index 0000000000..ad4b7ce7df --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_password_policy_max_login_attempts/ram_password_policy_max_login_attempts.py @@ -0,0 +1,32 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.ram.ram_client import ram_client + + +class ram_password_policy_max_login_attempts(Check): + """Check if RAM password policy temporarily blocks logon after 5 incorrect logon attempts within an hour.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + if ram_client.password_policy: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=ram_client.password_policy + ) + report.region = ram_client.region + report.resource_id = f"{ram_client.audited_account}-password-policy" + report.resource_arn = ( + f"acs:ram::{ram_client.audited_account}:password-policy" + ) + if ram_client.password_policy.max_login_attempts >= 5: + report.status = "PASS" + report.status_extended = "RAM password policy temporarily blocks logon after 5 incorrect logon attempts within an hour." + elif ram_client.password_policy.max_login_attempts == 0: + report.status = "FAIL" + report.status_extended = "RAM password policy does not temporarily block logon after incorrect attempts (max login attempts is disabled)." + else: + report.status = "FAIL" + report.status_extended = f"RAM password policy temporarily blocks logon after {ram_client.password_policy.max_login_attempts} incorrect logon attempts, which is not the recommended value of 5." + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/ram/ram_password_policy_max_password_age/__init__.py b/prowler/providers/alibabacloud/services/ram/ram_password_policy_max_password_age/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..5b9f50d300 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_password_policy_max_password_age/ram_password_policy_max_password_age.metadata.json @@ -0,0 +1,39 @@ +{ + "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" + ], + "ServiceName": "ram", + "SubServiceName": "", + "ResourceIdTemplate": "acs:ram::account-id:password-policy", + "Severity": "medium", + "ResourceType": "AlibabaCloudRAMPasswordPolicy", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun ram SetPasswordPolicy --MaxPasswordAge 365", + "NativeIaC": "", + "Other": "", + "Terraform": "resource \"alicloud_ram_password_policy\" \"example\" {\n max_password_age = 90\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**", + "Url": "https://hub.prowler.com/check/ram_password_policy_max_password_age" + } + }, + "Categories": [ + "secrets" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/ram/ram_password_policy_max_password_age/ram_password_policy_max_password_age.py b/prowler/providers/alibabacloud/services/ram/ram_password_policy_max_password_age/ram_password_policy_max_password_age.py new file mode 100644 index 0000000000..003750898a --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_password_policy_max_password_age/ram_password_policy_max_password_age.py @@ -0,0 +1,35 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.ram.ram_client import ram_client + + +class ram_password_policy_max_password_age(Check): + """Check if RAM password policy expires passwords in 365 days or greater.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + if ram_client.password_policy: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=ram_client.password_policy + ) + report.region = ram_client.region + report.resource_id = f"{ram_client.audited_account}-password-policy" + report.resource_arn = ( + f"acs:ram::{ram_client.audited_account}:password-policy" + ) + + # If max_password_age is 0, it means password expiration is disabled (which is acceptable) + # If it's set, it should be 365 or greater + if ram_client.password_policy.max_password_age == 0: + report.status = "PASS" + report.status_extended = "RAM password policy does not expire passwords (password expiration is disabled)." + elif ram_client.password_policy.max_password_age >= 365: + report.status = "PASS" + report.status_extended = f"RAM password policy expires passwords after {ram_client.password_policy.max_password_age} days." + else: + report.status = "FAIL" + report.status_extended = f"RAM password policy expires passwords after {ram_client.password_policy.max_password_age} days, which is less than the recommended 365 days." + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/ram/ram_password_policy_minimum_length/__init__.py b/prowler/providers/alibabacloud/services/ram/ram_password_policy_minimum_length/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..05b63af5d6 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_password_policy_minimum_length/ram_password_policy_minimum_length.metadata.json @@ -0,0 +1,39 @@ +{ + "Provider": "alibabacloud", + "CheckID": "ram_password_policy_minimum_length", + "CheckTitle": "RAM password policy requires minimum length of 14 or greater", + "CheckType": [ + "Unusual logon", + "Abnormal account" + ], + "ServiceName": "ram", + "SubServiceName": "", + "ResourceIdTemplate": "acs:ram::account-id:password-policy", + "Severity": "medium", + "ResourceType": "AlibabaCloudRAMPasswordPolicy", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun ram SetPasswordPolicy --MinimumPasswordLength 14", + "NativeIaC": "", + "Other": "", + "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**", + "Url": "https://hub.prowler.com/check/ram_password_policy_minimum_length" + } + }, + "Categories": [ + "secrets" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/ram/ram_password_policy_minimum_length/ram_password_policy_minimum_length.py b/prowler/providers/alibabacloud/services/ram/ram_password_policy_minimum_length/ram_password_policy_minimum_length.py new file mode 100644 index 0000000000..ec9066b877 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_password_policy_minimum_length/ram_password_policy_minimum_length.py @@ -0,0 +1,30 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.ram.ram_client import ram_client + + +class ram_password_policy_minimum_length(Check): + """Check if RAM password policy requires minimum length of 14 or greater.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + if ram_client.password_policy: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=ram_client.password_policy + ) + report.region = ram_client.region + report.resource_id = f"{ram_client.audited_account}-password-policy" + report.resource_arn = ( + f"acs:ram::{ram_client.audited_account}:password-policy" + ) + + if ram_client.password_policy.minimum_password_length >= 14: + report.status = "PASS" + report.status_extended = f"RAM password policy requires minimum length of {ram_client.password_policy.minimum_password_length} characters." + else: + report.status = "FAIL" + report.status_extended = f"RAM password policy requires minimum length of {ram_client.password_policy.minimum_password_length} characters, which is less than the recommended 14 characters." + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/ram/ram_password_policy_number/__init__.py b/prowler/providers/alibabacloud/services/ram/ram_password_policy_number/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..2a5ccb14ef --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_password_policy_number/ram_password_policy_number.metadata.json @@ -0,0 +1,39 @@ +{ + "Provider": "alibabacloud", + "CheckID": "ram_password_policy_number", + "CheckTitle": "RAM password policy require at least one number", + "CheckType": [ + "Unusual logon", + "Abnormal account" + ], + "ServiceName": "ram", + "SubServiceName": "", + "ResourceIdTemplate": "acs:ram::account-id:password-policy", + "Severity": "medium", + "ResourceType": "AlibabaCloudRAMPasswordPolicy", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun ram SetPasswordPolicy --RequireNumbers true", + "NativeIaC": "", + "Other": "", + "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**", + "Url": "https://hub.prowler.com/check/ram_password_policy_number" + } + }, + "Categories": [ + "secrets" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/ram/ram_password_policy_password_reuse_prevention/__init__.py b/prowler/providers/alibabacloud/services/ram/ram_password_policy_password_reuse_prevention/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..5be2c0cd4a --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_password_policy_password_reuse_prevention/ram_password_policy_password_reuse_prevention.metadata.json @@ -0,0 +1,39 @@ +{ + "Provider": "alibabacloud", + "CheckID": "ram_password_policy_password_reuse_prevention", + "CheckTitle": "RAM password policy prevents password reuse", + "CheckType": [ + "Unusual logon", + "Abnormal account" + ], + "ServiceName": "ram", + "SubServiceName": "", + "ResourceIdTemplate": "acs:ram::account-id:password-policy", + "Severity": "medium", + "ResourceType": "AlibabaCloudRAMPasswordPolicy", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun ram SetPasswordPolicy --PasswordReusePrevention 5", + "NativeIaC": "", + "Other": "", + "Terraform": "resource \"alicloud_ram_password_policy\" \"example\" {\n password_reuse_prevention = 24\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**", + "Url": "https://hub.prowler.com/check/ram_password_policy_password_reuse_prevention" + } + }, + "Categories": [ + "secrets" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/ram/ram_password_policy_password_reuse_prevention/ram_password_policy_password_reuse_prevention.py b/prowler/providers/alibabacloud/services/ram/ram_password_policy_password_reuse_prevention/ram_password_policy_password_reuse_prevention.py new file mode 100644 index 0000000000..a61b67be48 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_password_policy_password_reuse_prevention/ram_password_policy_password_reuse_prevention.py @@ -0,0 +1,35 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.ram.ram_client import ram_client + + +class ram_password_policy_password_reuse_prevention(Check): + """Check if RAM password policy prevents password reuse.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + if ram_client.password_policy: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=ram_client.password_policy + ) + report.region = ram_client.region + report.resource_id = f"{ram_client.audited_account}-password-policy" + report.resource_arn = ( + f"acs:ram::{ram_client.audited_account}:password-policy" + ) + + if ram_client.password_policy.password_reuse_prevention >= 5: + report.status = "PASS" + report.status_extended = f"RAM password policy prevents password reuse (history: {ram_client.password_policy.password_reuse_prevention} passwords)." + else: + report.status = "FAIL" + if ram_client.password_policy.password_reuse_prevention == 0: + report.status_extended = ( + "RAM password policy does not prevent password reuse." + ) + else: + report.status_extended = f"RAM password policy prevents reuse of only {ram_client.password_policy.password_reuse_prevention} previous passwords, which is less than the recommended 5." + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/ram/ram_password_policy_symbol/__init__.py b/prowler/providers/alibabacloud/services/ram/ram_password_policy_symbol/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..50c4ab28e3 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_password_policy_symbol/ram_password_policy_symbol.metadata.json @@ -0,0 +1,39 @@ +{ + "Provider": "alibabacloud", + "CheckID": "ram_password_policy_symbol", + "CheckTitle": "RAM password policy require at least one symbol", + "CheckType": [ + "Unusual logon", + "Abnormal account" + ], + "ServiceName": "ram", + "SubServiceName": "", + "ResourceIdTemplate": "acs:ram::account-id:password-policy", + "Severity": "medium", + "ResourceType": "AlibabaCloudRAMPasswordPolicy", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun ram SetPasswordPolicy --RequireSymbols true", + "NativeIaC": "", + "Other": "", + "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**", + "Url": "https://hub.prowler.com/check/ram_password_policy_symbol" + } + }, + "Categories": [ + "secrets" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/ram/ram_password_policy_symbol/ram_password_policy_symbol.py b/prowler/providers/alibabacloud/services/ram/ram_password_policy_symbol/ram_password_policy_symbol.py new file mode 100644 index 0000000000..8ba14dda99 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_password_policy_symbol/ram_password_policy_symbol.py @@ -0,0 +1,34 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.ram.ram_client import ram_client + + +class ram_password_policy_symbol(Check): + """Check if RAM password policy requires at least one symbol.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + if ram_client.password_policy: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=ram_client.password_policy + ) + report.region = ram_client.region + report.resource_id = f"{ram_client.audited_account}-password-policy" + report.resource_arn = ( + f"acs:ram::{ram_client.audited_account}:password-policy" + ) + + if ram_client.password_policy.require_symbols: + report.status = "PASS" + report.status_extended = ( + "RAM password policy requires at least one symbol." + ) + else: + report.status = "FAIL" + report.status_extended = ( + "RAM password policy does not require at least one symbol." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/ram/ram_password_policy_uppercase/__init__.py b/prowler/providers/alibabacloud/services/ram/ram_password_policy_uppercase/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..7974dbe8c3 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_password_policy_uppercase/ram_password_policy_uppercase.metadata.json @@ -0,0 +1,39 @@ +{ + "Provider": "alibabacloud", + "CheckID": "ram_password_policy_uppercase", + "CheckTitle": "RAM password policy requires at least one uppercase letter", + "CheckType": [ + "Unusual logon", + "Abnormal account" + ], + "ServiceName": "ram", + "SubServiceName": "", + "ResourceIdTemplate": "acs:ram::account-id:password-policy", + "Severity": "medium", + "ResourceType": "AlibabaCloudRAMPasswordPolicy", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun ram SetPasswordPolicy --RequireUppercaseCharacters true", + "NativeIaC": "", + "Other": "", + "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**", + "Url": "https://hub.prowler.com/check/ram_password_policy_uppercase" + } + }, + "Categories": [ + "secrets" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/ram/ram_password_policy_uppercase/ram_password_policy_uppercase.py b/prowler/providers/alibabacloud/services/ram/ram_password_policy_uppercase/ram_password_policy_uppercase.py new file mode 100644 index 0000000000..01cbff7d7c --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_password_policy_uppercase/ram_password_policy_uppercase.py @@ -0,0 +1,32 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.ram.ram_client import ram_client + + +class ram_password_policy_uppercase(Check): + """Check if RAM password policy requires at least one uppercase letter.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + if ram_client.password_policy: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=ram_client.password_policy + ) + report.region = ram_client.region + report.resource_id = f"{ram_client.audited_account}-password-policy" + report.resource_arn = ( + f"acs:ram::{ram_client.audited_account}:password-policy" + ) + + if ram_client.password_policy.require_uppercase_characters: + report.status = "PASS" + report.status_extended = ( + "RAM password policy requires at least one uppercase letter." + ) + else: + report.status = "FAIL" + report.status_extended = "RAM password policy does not require at least one uppercase letter." + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/ram/ram_policy_attached_only_to_group_or_roles/__init__.py b/prowler/providers/alibabacloud/services/ram/ram_policy_attached_only_to_group_or_roles/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..65e8853f25 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_policy_attached_only_to_group_or_roles/ram_policy_attached_only_to_group_or_roles.metadata.json @@ -0,0 +1,39 @@ +{ + "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" + ], + "ServiceName": "ram", + "SubServiceName": "", + "ResourceIdTemplate": "acs:ram::account-id:user/{user-name}", + "Severity": "low", + "ResourceType": "AlibabaCloudRAMUser", + "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**.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun ram DetachPolicyFromUser --PolicyName --PolicyType --UserName ", + "NativeIaC": "", + "Other": "", + "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", + "Url": "https://hub.prowler.com/check/ram_policy_attached_only_to_group_or_roles" + } + }, + "Categories": [ + "identity-access" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/ram/ram_policy_attached_only_to_group_or_roles/ram_policy_attached_only_to_group_or_roles.py b/prowler/providers/alibabacloud/services/ram/ram_policy_attached_only_to_group_or_roles/ram_policy_attached_only_to_group_or_roles.py new file mode 100644 index 0000000000..4528ea8005 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_policy_attached_only_to_group_or_roles/ram_policy_attached_only_to_group_or_roles.py @@ -0,0 +1,35 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.ram.ram_client import ram_client + + +class ram_policy_attached_only_to_group_or_roles(Check): + """Check if RAM policies are attached only to groups or roles, not directly to users.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + for user in ram_client.users: + report = CheckReportAlibabaCloud(metadata=self.metadata(), resource=user) + report.region = ram_client.region + report.resource_id = user.name + report.resource_arn = ( + f"acs:ram::{ram_client.audited_account}:user/{user.name}" + ) + + if user.attached_policies: + report.status = "FAIL" + policy_names = [policy.policy_name for policy in user.attached_policies] + report.status_extended = ( + f"RAM user {user.name} has {len(user.attached_policies)} " + f"policies directly attached: {', '.join(policy_names)}. " + f"Policies should be attached to groups or roles instead." + ) + findings.append(report) + else: + report.status = "PASS" + report.status_extended = ( + f"RAM user {user.name} has no policies directly attached." + ) + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/ram/ram_policy_no_administrative_privileges/__init__.py b/prowler/providers/alibabacloud/services/ram/ram_policy_no_administrative_privileges/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..a76b5f68b1 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_policy_no_administrative_privileges/ram_policy_no_administrative_privileges.metadata.json @@ -0,0 +1,39 @@ +{ + "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" + ], + "ServiceName": "ram", + "SubServiceName": "", + "ResourceIdTemplate": "acs:ram::account-id:policy/{policy-name}", + "Severity": "critical", + "ResourceType": "AlibabaCloudRAMPolicy", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun ram DetachPolicyFromUser --PolicyName --PolicyType Custom --UserName ", + "NativeIaC": "", + "Other": "", + "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", + "Url": "https://hub.prowler.com/check/ram_policy_no_administrative_privileges" + } + }, + "Categories": [ + "identity-access" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/ram/ram_policy_no_administrative_privileges/ram_policy_no_administrative_privileges.py b/prowler/providers/alibabacloud/services/ram/ram_policy_no_administrative_privileges/ram_policy_no_administrative_privileges.py new file mode 100644 index 0000000000..c7be6c855c --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_policy_no_administrative_privileges/ram_policy_no_administrative_privileges.py @@ -0,0 +1,73 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.ram.ram_client import ram_client + + +def check_admin_access(policy_document: dict) -> bool: + """ + Check if the policy document allows full administrative privileges. + + Args: + policy_document: The policy document as a dictionary. + + Returns: + bool: True if the policy allows admin access (Effect: Allow, Action: *, Resource: *), False otherwise. + """ + if not policy_document: + return False + + statements = policy_document.get("Statement", []) + if not isinstance(statements, list): + statements = [statements] + + for statement in statements: + effect = statement.get("Effect") + action = statement.get("Action") + resource = statement.get("Resource") + + # Check if statement has Effect: Allow, Action: *, Resource: * + if effect == "Allow": + # Action can be a string or a list + actions = action if isinstance(action, list) else [action] if action else [] + # Resource can be a string or a list + resources = ( + resource + if isinstance(resource, list) + else [resource] if resource else [] + ) + + # Check if Action contains "*" and Resource contains "*" + if "*" in actions and "*" in resources: + return True + + return False + + +class ram_policy_no_administrative_privileges(Check): + """Check if RAM policies that allow full '*:*' administrative privileges are not created.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + for policy in ram_client.policies.values(): + # Check only for custom policies that are attached + if policy.policy_type == "Custom" and policy.attachment_count > 0: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=policy + ) + report.region = ram_client.region + report.resource_id = policy.name + report.resource_arn = ( + f"acs:ram::{ram_client.audited_account}:policy/{policy.name}" + ) + + report.status = "PASS" + report.status_extended = f"Custom policy {policy.name} is attached but does not allow '*:*' administrative privileges." + + if policy.document: + if check_admin_access(policy.document): + report.status = "FAIL" + report.status_extended = f"Custom policy {policy.name} is attached and allows '*:*' administrative privileges." + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/ram/ram_rotate_access_key_90_days/__init__.py b/prowler/providers/alibabacloud/services/ram/ram_rotate_access_key_90_days/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..674b7cdab5 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_rotate_access_key_90_days/ram_rotate_access_key_90_days.metadata.json @@ -0,0 +1,39 @@ +{ + "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" + ], + "ServiceName": "ram", + "SubServiceName": "", + "ResourceIdTemplate": "acs:ram::account-id:user/{user-name}/accesskey/{access-key-id}", + "Severity": "medium", + "ResourceType": "AlibabaCloudRAMAccessKey", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun ram CreateAccessKey --UserName && aliyun ram UpdateAccessKey --UserAccessKeyId --Status Inactive --UserName && aliyun ram DeleteAccessKey --UserAccessKeyId --UserName ", + "NativeIaC": "", + "Other": "", + "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", + "Url": "https://hub.prowler.com/check/ram_rotate_access_key_90_days" + } + }, + "Categories": [ + "secrets" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/ram/ram_rotate_access_key_90_days/ram_rotate_access_key_90_days.py b/prowler/providers/alibabacloud/services/ram/ram_rotate_access_key_90_days/ram_rotate_access_key_90_days.py new file mode 100644 index 0000000000..0427e50794 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_rotate_access_key_90_days/ram_rotate_access_key_90_days.py @@ -0,0 +1,58 @@ +from datetime import datetime, timedelta, timezone + +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.ram.ram_client import ram_client + + +class ram_rotate_access_key_90_days(Check): + """Check if access keys are rotated every 90 days or less.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + # Use UTC timezone-aware datetime for consistent comparison + now = datetime.now(timezone.utc) + ninety_days_ago = now - timedelta(days=90) + + for user in ram_client.users: + if user.access_keys: + for access_key in user.access_keys: + # Only check active access keys + if access_key.status == "Active": + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=user + ) + report.region = ram_client.region + report.resource_id = access_key.access_key_id + report.resource_arn = f"acs:ram::{ram_client.audited_account}:user/{user.name}/accesskey/{access_key.access_key_id}" + + if access_key.create_date: + # Ensure create_date is timezone-aware for comparison + create_date = access_key.create_date + if create_date.tzinfo is None: + # If naive, assume UTC + create_date = create_date.replace(tzinfo=timezone.utc) + + if create_date < ninety_days_ago: + report.status = "FAIL" + days_old = (now - create_date).days + report.status_extended = ( + f"Access key {access_key.access_key_id} for user {user.name} " + f"has not been rotated in {days_old} days (more than 90 days)." + ) + else: + report.status = "PASS" + days_old = (now - create_date).days + report.status_extended = ( + f"Access key {access_key.access_key_id} for user {user.name} " + f"was created {days_old} days ago (within 90 days)." + ) + else: + report.status = "PASS" + report.status_extended = ( + f"Access key {access_key.access_key_id} for user {user.name} " + f"creation date is not available." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/ram/ram_service.py b/prowler/providers/alibabacloud/services/ram/ram_service.py new file mode 100644 index 0000000000..0d52c492e4 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_service.py @@ -0,0 +1,478 @@ +import json +from datetime import datetime +from typing import Optional + +from alibabacloud_ram20150501 import models as ram_models +from pydantic.v1 import BaseModel + +from prowler.lib.logger import logger +from prowler.lib.scan_filters.scan_filters import is_resource_filtered +from prowler.providers.alibabacloud.lib.service.service import AlibabaCloudService + + +class RAM(AlibabaCloudService): + """ + RAM (Resource Access Management) service class for Alibaba Cloud. + + This class provides methods to interact with Alibaba Cloud RAM service + to retrieve users, access keys, MFA devices, password policies, etc. + """ + + def __init__(self, provider): + # Call AlibabaCloudService's __init__ + super().__init__(__class__.__name__, provider, global_service=True) + + # Fetch RAM resources + self.users = self._list_users() + self.password_policy = self._get_password_policy() + self.mfa_devices = self._list_virtual_mfa_devices() + self.groups = self._list_groups() + self.policies = self._list_policies() + + # Enrich users with additional information + self._get_user_mfa_devices() + self._get_user_access_keys() + self._get_user_login_profile() + self._list_policies_for_user() + self._list_groups_for_user() + + # Get root account access keys + self.root_access_keys = self._get_root_access_keys() + + # Get policy documents + self._get_policy_documents() + + def _list_users(self): + """List all RAM users.""" + logger.info("RAM - Listing Users...") + users = [] + + try: + request = ram_models.ListUsersRequest() + response = self.client.list_users(request) + + if response and response.body and response.body.users: + for user_data in response.body.users.user: + if not self.audit_resources or is_resource_filtered( + user_data.user_name, self.audit_resources + ): + users.append( + User( + name=user_data.user_name, + user_id=user_data.user_id, + display_name=getattr(user_data, "display_name", ""), + create_date=getattr(user_data, "create_date", None), + update_date=getattr(user_data, "update_date", None), + ) + ) + + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + return users + + def _get_password_policy(self): + """Get password policy settings.""" + logger.info("RAM - Getting Password Policy...") + + try: + response = self.client.get_password_policy() + + if response and response.body and response.body.password_policy: + policy = response.body.password_policy + return PasswordPolicy( + minimum_password_length=getattr( + policy, "minimum_password_length", 8 + ), + require_lowercase_characters=getattr( + policy, "require_lowercase_characters", False + ), + require_uppercase_characters=getattr( + policy, "require_uppercase_characters", False + ), + require_numbers=getattr(policy, "require_numbers", False), + require_symbols=getattr(policy, "require_symbols", False), + hard_expiry=getattr(policy, "hard_expiry", False), + max_password_age=getattr(policy, "max_password_age", 0), + password_reuse_prevention=getattr( + policy, "password_reuse_prevention", 0 + ), + max_login_attempts=getattr(policy, "max_login_attemps", 0), + ) + return None + + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return None + + def _list_virtual_mfa_devices(self): + """List all virtual MFA devices.""" + logger.info("RAM - Listing Virtual MFA Devices...") + mfa_devices = [] + + try: + response = self.client.list_virtual_mfadevices() + + if response and response.body and response.body.virtual_mfadevices: + for device in response.body.virtual_mfadevices.virtual_mfadevice: + mfa_devices.append( + MFADevice( + serial_number=device.serial_number, + user_name=( + getattr(device, "user", {}).get("user_name", "") + if hasattr(device, "user") + else "" + ), + enable_date=getattr(device, "activate_date", None), + ) + ) + + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + return mfa_devices + + def _list_groups(self): + """List all RAM groups.""" + logger.info("RAM - Listing Groups...") + groups = [] + + try: + request = ram_models.ListGroupsRequest() + response = self.client.list_groups(request) + + if response and response.body and response.body.groups: + for group_data in response.body.groups.group: + groups.append( + Group( + name=group_data.group_name, + group_id=getattr(group_data, "group_id", ""), + create_date=getattr(group_data, "create_date", None), + ) + ) + + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + return groups + + def _list_policies(self): + """List all RAM policies.""" + logger.info("RAM - Listing Policies...") + policies = {} + + try: + # List custom policies + request = ram_models.ListPoliciesRequest(policy_type="Custom") + response = self.client.list_policies(request) + + if response and response.body and response.body.policies: + for policy_data in response.body.policies.policy: + policy_name = policy_data.policy_name + policies[policy_name] = Policy( + name=policy_name, + policy_type="Custom", + description=getattr(policy_data, "description", ""), + create_date=getattr(policy_data, "create_date", None), + update_date=getattr(policy_data, "update_date", None), + attachment_count=getattr(policy_data, "attachment_count", 0), + ) + + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + return policies + + def _get_policy_documents(self): + """Get policy documents for all custom policies.""" + logger.info("RAM - Getting Policy Documents...") + + for policy_name, policy in self.policies.items(): + if policy.policy_type == "Custom": + try: + request = ram_models.GetPolicyRequest( + policy_name=policy_name, policy_type="Custom" + ) + response = self.client.get_policy(request) + + if response and response.body and response.body.policy: + policy_data = response.body.policy + # Get the default policy version + default_version = getattr(policy_data, "default_version", None) + if default_version: + # Get the policy version document + version_request = ram_models.GetPolicyVersionRequest( + policy_name=policy_name, + policy_type="Custom", + version_id=default_version, + ) + version_response = self.client.get_policy_version( + version_request + ) + if ( + version_response + and version_response.body + and version_response.body.policy_version + ): + policy_doc_str = getattr( + version_response.body.policy_version, + "policy_document", + None, + ) + if policy_doc_str: + try: + policy.document = json.loads(policy_doc_str) + except json.JSONDecodeError: + logger.warning( + f"Could not parse policy document for {policy_name}" + ) + policy.document = None + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + policy.document = None + + def _get_user_mfa_devices(self): + """Get MFA devices for each user.""" + logger.info("RAM - Getting User MFA Devices...") + + for user in self.users: + user.mfa_devices = [] + for device in self.mfa_devices: + if device.user_name == user.name: + user.mfa_devices.append(device) + + def _get_user_access_keys(self): + """Get access keys for each user.""" + logger.info("RAM - Getting User Access Keys...") + + for user in self.users: + try: + request = ram_models.ListAccessKeysRequest(user_name=user.name) + response = self.client.list_access_keys(request) + + user.access_keys = [] + if response and response.body and response.body.access_keys: + for key_data in response.body.access_keys.access_key: + user.access_keys.append( + AccessKey( + access_key_id=key_data.access_key_id, + status=key_data.status, + create_date=getattr(key_data, "create_date", None), + ) + ) + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + user.access_keys = [] + + def _get_user_login_profile(self): + """Get login profile for each user to check console access.""" + logger.info("RAM - Getting User Login Profiles...") + + for user in self.users: + try: + request = ram_models.GetLoginProfileRequest(user_name=user.name) + response = self.client.get_login_profile(request) + + if response and response.body and response.body.login_profile: + profile = response.body.login_profile + user.has_console_access = True + user.password_last_used = getattr( + profile, "password_last_used", None + ) + user.mfa_bind_required = getattr(profile, "mfabind_required", False) + + except Exception: + # User doesn't have console access + user.has_console_access = False + user.password_last_used = None + user.mfa_bind_required = False + + def _list_policies_for_user(self): + """List policies attached to each user.""" + logger.info("RAM - Listing Policies for Users...") + + for user in self.users: + try: + request = ram_models.ListPoliciesForUserRequest(user_name=user.name) + response = self.client.list_policies_for_user(request) + + user.attached_policies = [] + if response and response.body and response.body.policies: + for policy_data in response.body.policies.policy: + user.attached_policies.append( + AttachedPolicy( + policy_name=policy_data.policy_name, + policy_type=policy_data.policy_type, + attach_date=getattr(policy_data, "attach_date", None), + ) + ) + + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + user.attached_policies = [] + + def _list_groups_for_user(self): + """List groups for each user.""" + logger.info("RAM - Listing Groups for Users...") + + for user in self.users: + try: + request = ram_models.ListGroupsForUserRequest(user_name=user.name) + response = self.client.list_groups_for_user(request) + + user.groups = [] + if response and response.body and response.body.groups: + for group_data in response.body.groups.group: + user.groups.append(group_data.group_name) + + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + user.groups = [] + + def _get_root_access_keys(self): + """Get root account access keys. + + Note: This only works when authenticated as the root account. + If authenticated as a RAM user, this will return empty list as + RAM users cannot query root account access keys. + """ + logger.info("RAM - Getting Root Account Access Keys...") + root_access_keys = [] + + # Check if we're authenticated as root account + # Use the is_root flag from identity (set via STS GetCallerIdentity) + is_root = self.provider.identity.is_root + + if not is_root: + # If we're authenticated as a RAM user, we can't query root account access keys + logger.warning( + "RAM - Cannot query root account access keys: authenticated as RAM user, not root account" + ) + return root_access_keys + + try: + # Call ListAccessKeys without user_name to get root account access keys + # This only works when called with root account credentials + request = ram_models.ListAccessKeysRequest() + response = self.client.list_access_keys(request) + + if response and response.body and response.body.access_keys: + for key_data in response.body.access_keys.access_key: + root_access_keys.append( + AccessKey( + access_key_id=key_data.access_key_id, + status=key_data.status, + create_date=getattr(key_data, "create_date", None), + ) + ) + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + return root_access_keys + + +# Models for RAM service +class User(BaseModel): + """RAM User model.""" + + name: str + user_id: str + display_name: str = "" + create_date: Optional[datetime] = None + update_date: Optional[datetime] = None + has_console_access: bool = False + password_last_used: Optional[datetime] = None + mfa_bind_required: bool = False + mfa_devices: list = [] + access_keys: list = [] + attached_policies: list = [] + groups: list = [] + + +class AccessKey(BaseModel): + """Access Key model.""" + + access_key_id: str + status: str + create_date: Optional[datetime] = None + + +class MFADevice(BaseModel): + """MFA Device model.""" + + serial_number: str + user_name: str + enable_date: Optional[datetime] = None + + +class PasswordPolicy(BaseModel): + """Password Policy model.""" + + minimum_password_length: int = 8 + require_lowercase_characters: bool = False + require_uppercase_characters: bool = False + require_numbers: bool = False + require_symbols: bool = False + hard_expiry: bool = False + max_password_age: int = 0 + password_reuse_prevention: int = 0 + max_login_attempts: int = 0 + + +class AccountSummary(BaseModel): + """Account Summary model.""" + + users: int = 0 + groups: int = 0 + roles: int = 0 + policies: int = 0 + mfa_devices: int = 0 + mfa_devices_in_use: int = 0 + + +class Group(BaseModel): + """RAM Group model.""" + + name: str + group_id: str + create_date: Optional[datetime] = None + + +class Policy(BaseModel): + """RAM Policy model.""" + + name: str + policy_type: str + description: str = "" + create_date: Optional[datetime] = None + update_date: Optional[datetime] = None + attachment_count: int = 0 + document: Optional[dict] = None + + +class AttachedPolicy(BaseModel): + """Attached Policy model.""" + + policy_name: str + policy_type: str + attach_date: Optional[datetime] = None diff --git a/prowler/providers/alibabacloud/services/ram/ram_user_console_access_unused/__init__.py b/prowler/providers/alibabacloud/services/ram/ram_user_console_access_unused/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..ae17d5d472 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_user_console_access_unused/ram_user_console_access_unused.metadata.json @@ -0,0 +1,39 @@ +{ + "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" + ], + "ServiceName": "ram", + "SubServiceName": "", + "ResourceIdTemplate": "acs:ram::account-id:user/{user-name}", + "Severity": "medium", + "ResourceType": "AlibabaCloudRAMUser", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun ram DeleteLoginProfile --UserName ", + "NativeIaC": "", + "Other": "", + "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**", + "Url": "https://hub.prowler.com/check/ram_user_console_access_unused" + } + }, + "Categories": [ + "identity-access" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/ram/ram_user_console_access_unused/ram_user_console_access_unused.py b/prowler/providers/alibabacloud/services/ram/ram_user_console_access_unused/ram_user_console_access_unused.py new file mode 100644 index 0000000000..6624ee89d3 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_user_console_access_unused/ram_user_console_access_unused.py @@ -0,0 +1,56 @@ +import datetime + +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.ram.ram_client import ram_client + + +class ram_user_console_access_unused(Check): + """Check if RAM users with console access have logged in within the configured days.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + maximum_unused_days = ram_client.audit_config.get("max_console_access_days", 90) + findings = [] + for user in ram_client.users: + report = CheckReportAlibabaCloud(metadata=self.metadata(), resource=user) + report.region = ram_client.region + report.resource_id = user.name + report.resource_arn = ( + f"acs:ram::{ram_client.audited_account}:user/{user.name}" + ) + if user.has_console_access: + if user.password_last_used: + time_since_insertion = ( + datetime.datetime.now() + - datetime.datetime.strptime( + str(user.password_last_used), "%Y-%m-%d %H:%M:%S+00:00" + ) + ) + if time_since_insertion.days > maximum_unused_days: + report.status = "FAIL" + report.status_extended = ( + f"RAM user {user.name} has not logged in to the console " + f"in the past {maximum_unused_days} days " + f"({time_since_insertion.days} days)." + ) + else: + report.status = "PASS" + report.status_extended = ( + f"RAM user {user.name} has logged in to the console " + f"in the past {maximum_unused_days} days " + f"({time_since_insertion.days} days)." + ) + else: + # User has console access but has never logged in + report.status = "FAIL" + report.status_extended = ( + f"RAM user {user.name} has console access enabled " + "but has never logged in to the console." + ) + else: + report.status = "PASS" + report.status_extended = ( + f"RAM user {user.name} does not have console access enabled." + ) + + findings.append(report) + return findings diff --git a/prowler/providers/alibabacloud/services/ram/ram_user_mfa_enabled_console_access/__init__.py b/prowler/providers/alibabacloud/services/ram/ram_user_mfa_enabled_console_access/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..a838548ae4 --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_user_mfa_enabled_console_access/ram_user_mfa_enabled_console_access.metadata.json @@ -0,0 +1,39 @@ +{ + "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" + ], + "ServiceName": "ram", + "SubServiceName": "", + "ResourceIdTemplate": "acs:ram::account-id:user/{user-name}", + "Severity": "high", + "ResourceType": "AlibabaCloudRAMUser", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "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", + "Url": "https://hub.prowler.com/check/ram_user_mfa_enabled_console_access" + } + }, + "Categories": [ + "encryption" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/ram/ram_user_mfa_enabled_console_access/ram_user_mfa_enabled_console_access.py b/prowler/providers/alibabacloud/services/ram/ram_user_mfa_enabled_console_access/ram_user_mfa_enabled_console_access.py new file mode 100644 index 0000000000..454e2f283a --- /dev/null +++ b/prowler/providers/alibabacloud/services/ram/ram_user_mfa_enabled_console_access/ram_user_mfa_enabled_console_access.py @@ -0,0 +1,36 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.ram.ram_client import ram_client + + +class ram_user_mfa_enabled_console_access(Check): + """Check if all RAM users with console access have MFA enabled.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + for user in ram_client.users: + # Only check users with console access + if user.has_console_access: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=user + ) + report.region = ram_client.region + report.resource_id = user.name + report.resource_arn = ( + f"acs:ram::{ram_client.audited_account}:user/{user.name}" + ) + + # Check if MFA is required for console access + # mfa_bind_required indicates whether MFA is required in the login profile + if user.mfa_bind_required: + report.status = "PASS" + report.status_extended = ( + f"RAM user {user.name} has MFA enabled for console access." + ) + else: + report.status = "FAIL" + report.status_extended = f"RAM user {user.name} has console access but does not have MFA enabled." + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/rds/__init__.py b/prowler/providers/alibabacloud/services/rds/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/alibabacloud/services/rds/rds_client.py b/prowler/providers/alibabacloud/services/rds/rds_client.py new file mode 100644 index 0000000000..18fbc19711 --- /dev/null +++ b/prowler/providers/alibabacloud/services/rds/rds_client.py @@ -0,0 +1,4 @@ +from prowler.providers.alibabacloud.services.rds.rds_service import RDS +from prowler.providers.common.provider import Provider + +rds_client = RDS(Provider.get_global_provider()) diff --git a/prowler/providers/alibabacloud/services/rds/rds_instance_no_public_access_whitelist/__init__.py b/prowler/providers/alibabacloud/services/rds/rds_instance_no_public_access_whitelist/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..6db330920a --- /dev/null +++ b/prowler/providers/alibabacloud/services/rds/rds_instance_no_public_access_whitelist/rds_instance_no_public_access_whitelist.metadata.json @@ -0,0 +1,39 @@ +{ + "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" + ], + "ServiceName": "rds", + "SubServiceName": "", + "ResourceIdTemplate": "acs:rds:region:account-id:dbinstance/{dbinstance-id}", + "Severity": "critical", + "ResourceType": "AlibabaCloudRDSDBInstance", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun rds ModifySecurityIps --DBInstanceId --SecurityIps ", + "NativeIaC": "", + "Other": "", + "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", + "Url": "https://hub.prowler.com/check/rds_instance_no_public_access_whitelist" + } + }, + "Categories": [ + "internet-exposed" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/rds/rds_instance_no_public_access_whitelist/rds_instance_no_public_access_whitelist.py b/prowler/providers/alibabacloud/services/rds/rds_instance_no_public_access_whitelist/rds_instance_no_public_access_whitelist.py new file mode 100644 index 0000000000..37573c87dc --- /dev/null +++ b/prowler/providers/alibabacloud/services/rds/rds_instance_no_public_access_whitelist/rds_instance_no_public_access_whitelist.py @@ -0,0 +1,36 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.rds.rds_client import rds_client + + +class rds_instance_no_public_access_whitelist(Check): + """Check if RDS Instances are not open to the world.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + for instance in rds_client.instances: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=instance + ) + report.region = instance.region + report.resource_id = instance.id + report.resource_arn = f"acs:rds:{instance.region}:{rds_client.audited_account}:dbinstance/{instance.id}" + + is_public = False + for ip in instance.security_ips: + if ip == "0.0.0.0/0" or ip == "0.0.0.0": + is_public = True + break + + if not is_public: + report.status = "PASS" + report.status_extended = ( + f"RDS Instance {instance.name} is not open to the world." + ) + else: + report.status = "FAIL" + report.status_extended = f"RDS Instance {instance.name} is open to the world (0.0.0.0/0 allowed)." + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_connections_enabled/__init__.py b/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_connections_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..ef55b9162c --- /dev/null +++ b/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_connections_enabled/rds_instance_postgresql_log_connections_enabled.metadata.json @@ -0,0 +1,39 @@ +{ + "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" + ], + "ServiceName": "rds", + "SubServiceName": "", + "ResourceIdTemplate": "acs:rds:region:account-id:dbinstance/{dbinstance-id}", + "Severity": "medium", + "ResourceType": "AlibabaCloudRDSDBInstance", + "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**.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun rds ModifyParameter --DBInstanceId --Parameters \"{\\\"log_connections\\\":\\\"on\\\"}\"", + "NativeIaC": "", + "Other": "", + "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**", + "Url": "https://hub.prowler.com/check/rds_instance_postgresql_log_connections_enabled" + } + }, + "Categories": [ + "logging" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_connections_enabled/rds_instance_postgresql_log_connections_enabled.py b/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_connections_enabled/rds_instance_postgresql_log_connections_enabled.py new file mode 100644 index 0000000000..6b59681a68 --- /dev/null +++ b/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_connections_enabled/rds_instance_postgresql_log_connections_enabled.py @@ -0,0 +1,29 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.rds.rds_client import rds_client + + +class rds_instance_postgresql_log_connections_enabled(Check): + """Check if parameter 'log_connections' is set to 'ON' for PostgreSQL Database.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + for instance in rds_client.instances: + if "PostgreSQL" in instance.engine: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=instance + ) + report.region = instance.region + report.resource_id = instance.id + report.resource_arn = f"acs:rds:{instance.region}:{rds_client.audited_account}:dbinstance/{instance.id}" + + if instance.log_connections == "on": + report.status = "PASS" + report.status_extended = f"RDS PostgreSQL Instance {instance.name} has log_connections enabled." + else: + report.status = "FAIL" + report.status_extended = f"RDS PostgreSQL Instance {instance.name} has log_connections disabled." + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_disconnections_enabled/__init__.py b/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_disconnections_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..b85e3f330e --- /dev/null +++ b/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_disconnections_enabled/rds_instance_postgresql_log_disconnections_enabled.metadata.json @@ -0,0 +1,39 @@ +{ + "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" + ], + "ServiceName": "rds", + "SubServiceName": "", + "ResourceIdTemplate": "acs:rds:region:account-id:dbinstance/{dbinstance-id}", + "Severity": "medium", + "ResourceType": "AlibabaCloudRDSDBInstance", + "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**.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun rds ModifyParameter --DBInstanceId --Parameters \"{\\\"log_disconnections\\\":\\\"on\\\"}\"", + "NativeIaC": "", + "Other": "", + "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**", + "Url": "https://hub.prowler.com/check/rds_instance_postgresql_log_disconnections_enabled" + } + }, + "Categories": [ + "logging" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_disconnections_enabled/rds_instance_postgresql_log_disconnections_enabled.py b/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_disconnections_enabled/rds_instance_postgresql_log_disconnections_enabled.py new file mode 100644 index 0000000000..4c3095c12b --- /dev/null +++ b/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_disconnections_enabled/rds_instance_postgresql_log_disconnections_enabled.py @@ -0,0 +1,29 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.rds.rds_client import rds_client + + +class rds_instance_postgresql_log_disconnections_enabled(Check): + """Check if parameter 'log_disconnections' is set to 'ON' for PostgreSQL Database.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + for instance in rds_client.instances: + if "PostgreSQL" in instance.engine: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=instance + ) + report.region = instance.region + report.resource_id = instance.id + report.resource_arn = f"acs:rds:{instance.region}:{rds_client.audited_account}:dbinstance/{instance.id}" + + if instance.log_disconnections == "on": + report.status = "PASS" + report.status_extended = f"RDS PostgreSQL Instance {instance.name} has log_disconnections enabled." + else: + report.status = "FAIL" + report.status_extended = f"RDS PostgreSQL Instance {instance.name} has log_disconnections disabled." + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_duration_enabled/__init__.py b/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_duration_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..a50a479d8a --- /dev/null +++ b/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_duration_enabled/rds_instance_postgresql_log_duration_enabled.metadata.json @@ -0,0 +1,38 @@ +{ + "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" + ], + "ServiceName": "rds", + "SubServiceName": "", + "ResourceIdTemplate": "acs:rds:region:account-id:dbinstance/{dbinstance-id}", + "Severity": "medium", + "ResourceType": "AlibabaCloudRDSDBInstance", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun rds ModifyParameter --DBInstanceId --Parameters \"{\\\"log_duration\\\":\\\"on\\\"}\"", + "NativeIaC": "", + "Other": "", + "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**", + "Url": "https://hub.prowler.com/check/rds_instance_postgresql_log_duration_enabled" + } + }, + "Categories": [ + "logging" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_duration_enabled/rds_instance_postgresql_log_duration_enabled.py b/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_duration_enabled/rds_instance_postgresql_log_duration_enabled.py new file mode 100644 index 0000000000..886f908a72 --- /dev/null +++ b/prowler/providers/alibabacloud/services/rds/rds_instance_postgresql_log_duration_enabled/rds_instance_postgresql_log_duration_enabled.py @@ -0,0 +1,29 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.rds.rds_client import rds_client + + +class rds_instance_postgresql_log_duration_enabled(Check): + """Check if parameter 'log_duration' is set to 'ON' for PostgreSQL Database.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + for instance in rds_client.instances: + if "PostgreSQL" in instance.engine: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=instance + ) + report.region = instance.region + report.resource_id = instance.id + report.resource_arn = f"acs:rds:{instance.region}:{rds_client.audited_account}:dbinstance/{instance.id}" + + if instance.log_duration == "on": + report.status = "PASS" + report.status_extended = f"RDS PostgreSQL Instance {instance.name} has log_duration enabled." + else: + report.status = "FAIL" + report.status_extended = f"RDS PostgreSQL Instance {instance.name} has log_duration disabled." + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/rds/rds_instance_sql_audit_enabled/__init__.py b/prowler/providers/alibabacloud/services/rds/rds_instance_sql_audit_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..0a136093a4 --- /dev/null +++ b/prowler/providers/alibabacloud/services/rds/rds_instance_sql_audit_enabled/rds_instance_sql_audit_enabled.metadata.json @@ -0,0 +1,39 @@ +{ + "Provider": "alibabacloud", + "CheckID": "rds_instance_sql_audit_enabled", + "CheckTitle": "Auditing is set to On for applicable database instances", + "CheckType": [ + "Intrusion into applications" + ], + "ServiceName": "rds", + "SubServiceName": "", + "ResourceIdTemplate": "acs:rds:region:account-id:dbinstance/{dbinstance-id}", + "Severity": "medium", + "ResourceType": "AlibabaCloudRDSDBInstance", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun rds ModifySQLCollectorPolicy --DBInstanceId --SQLCollectorStatus Enable --StoragePeriod ", + "NativeIaC": "", + "Other": "", + "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**", + "Url": "https://hub.prowler.com/check/rds_instance_sql_audit_enabled" + } + }, + "Categories": [ + "logging", + "forensics-ready" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/rds/rds_instance_sql_audit_enabled/rds_instance_sql_audit_enabled.py b/prowler/providers/alibabacloud/services/rds/rds_instance_sql_audit_enabled/rds_instance_sql_audit_enabled.py new file mode 100644 index 0000000000..3015d16c38 --- /dev/null +++ b/prowler/providers/alibabacloud/services/rds/rds_instance_sql_audit_enabled/rds_instance_sql_audit_enabled.py @@ -0,0 +1,32 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.rds.rds_client import rds_client + + +class rds_instance_sql_audit_enabled(Check): + """Check if 'Auditing' is set to 'On' for applicable database instances.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + for instance in rds_client.instances: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=instance + ) + report.region = instance.region + report.resource_id = instance.id + report.resource_arn = f"acs:rds:{instance.region}:{rds_client.audited_account}:dbinstance/{instance.id}" + + if instance.audit_log_enabled: + report.status = "PASS" + report.status_extended = ( + f"RDS Instance {instance.name} has SQL audit enabled." + ) + else: + report.status = "FAIL" + report.status_extended = ( + f"RDS Instance {instance.name} does not have SQL audit enabled." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/rds/rds_instance_sql_audit_retention/__init__.py b/prowler/providers/alibabacloud/services/rds/rds_instance_sql_audit_retention/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..e4f301871b --- /dev/null +++ b/prowler/providers/alibabacloud/services/rds/rds_instance_sql_audit_retention/rds_instance_sql_audit_retention.metadata.json @@ -0,0 +1,39 @@ +{ + "Provider": "alibabacloud", + "CheckID": "rds_instance_sql_audit_retention", + "CheckTitle": "Auditing Retention is greater than the configured period", + "CheckType": [ + "Intrusion into applications" + ], + "ServiceName": "rds", + "SubServiceName": "", + "ResourceIdTemplate": "acs:rds:region:account-id:dbinstance/{dbinstance-id}", + "Severity": "medium", + "ResourceType": "AlibabaCloudRDSDBInstance", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun rds ModifySQLCollectorPolicy --DBInstanceId --SQLCollectorStatus Enable --StoragePeriod 180", + "NativeIaC": "", + "Other": "", + "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", + "Url": "https://hub.prowler.com/check/rds_instance_sql_audit_retention" + } + }, + "Categories": [ + "logging", + "forensics-ready" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/rds/rds_instance_sql_audit_retention/rds_instance_sql_audit_retention.py b/prowler/providers/alibabacloud/services/rds/rds_instance_sql_audit_retention/rds_instance_sql_audit_retention.py new file mode 100644 index 0000000000..5891daa78a --- /dev/null +++ b/prowler/providers/alibabacloud/services/rds/rds_instance_sql_audit_retention/rds_instance_sql_audit_retention.py @@ -0,0 +1,41 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.rds.rds_client import rds_client + + +class rds_instance_sql_audit_retention(Check): + """Check if 'Auditing' Retention is greater than the configured period.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + # Get configurable max days from audit config (default: 180 days - 6 months) + min_audit_retention_days = rds_client.audit_config.get( + "min_rds_audit_retention_days", 180 + ) + + for instance in rds_client.instances: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=instance + ) + report.region = instance.region + report.resource_id = instance.id + report.resource_arn = f"acs:rds:{instance.region}:{rds_client.audited_account}:dbinstance/{instance.id}" + + if ( + instance.audit_log_enabled + and instance.audit_log_retention >= min_audit_retention_days + ): + report.status = "PASS" + report.status_extended = f"RDS Instance {instance.name} has SQL audit enabled with retention of {instance.audit_log_retention} days (>= {min_audit_retention_days} days)." + elif instance.audit_log_enabled: + report.status = "FAIL" + report.status_extended = f"RDS Instance {instance.name} has SQL audit enabled but retention is {instance.audit_log_retention} days (< {min_audit_retention_days} days)." + else: + report.status = "FAIL" + report.status_extended = ( + f"RDS Instance {instance.name} does not have SQL audit enabled." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/rds/rds_instance_ssl_enabled/__init__.py b/prowler/providers/alibabacloud/services/rds/rds_instance_ssl_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..8cefe65dd9 --- /dev/null +++ b/prowler/providers/alibabacloud/services/rds/rds_instance_ssl_enabled/rds_instance_ssl_enabled.metadata.json @@ -0,0 +1,39 @@ +{ + "Provider": "alibabacloud", + "CheckID": "rds_instance_ssl_enabled", + "CheckTitle": "RDS instance requires all incoming connections to use SSL", + "CheckType": [ + "Sensitive file tampering", + "Intrusion into applications" + ], + "ServiceName": "rds", + "SubServiceName": "", + "ResourceIdTemplate": "acs:rds:region:account-id:dbinstance/{dbinstance-id}", + "Severity": "medium", + "ResourceType": "AlibabaCloudRDSDBInstance", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun rds ModifyDBInstanceSSL --DBInstanceId --SSLEnabled 1", + "NativeIaC": "", + "Other": "", + "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", + "Url": "https://hub.prowler.com/check/rds_instance_ssl_enabled" + } + }, + "Categories": [ + "encryption" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/rds/rds_instance_ssl_enabled/rds_instance_ssl_enabled.py b/prowler/providers/alibabacloud/services/rds/rds_instance_ssl_enabled/rds_instance_ssl_enabled.py new file mode 100644 index 0000000000..4c63a6e21e --- /dev/null +++ b/prowler/providers/alibabacloud/services/rds/rds_instance_ssl_enabled/rds_instance_ssl_enabled.py @@ -0,0 +1,30 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.rds.rds_client import rds_client + + +class rds_instance_ssl_enabled(Check): + """Check if RDS instance requires all incoming connections to use SSL.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + for instance in rds_client.instances: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=instance + ) + report.region = instance.region + report.resource_id = instance.id + report.resource_arn = f"acs:rds:{instance.region}:{rds_client.audited_account}:dbinstance/{instance.id}" + + if instance.ssl_enabled: + report.status = "PASS" + report.status_extended = ( + f"RDS Instance {instance.name} has SSL encryption enabled." + ) + else: + report.status = "FAIL" + report.status_extended = f"RDS Instance {instance.name} does not have SSL encryption enabled." + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/rds/rds_instance_tde_enabled/__init__.py b/prowler/providers/alibabacloud/services/rds/rds_instance_tde_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..2e35c8b079 --- /dev/null +++ b/prowler/providers/alibabacloud/services/rds/rds_instance_tde_enabled/rds_instance_tde_enabled.metadata.json @@ -0,0 +1,39 @@ +{ + "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" + ], + "ServiceName": "rds", + "SubServiceName": "", + "ResourceIdTemplate": "acs:rds:region:account-id:dbinstance/{dbinstance-id}", + "Severity": "high", + "ResourceType": "AlibabaCloudRDSDBInstance", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun rds ModifyDBInstanceTDE --DBInstanceId --TDEStatus Enabled", + "NativeIaC": "", + "Other": "", + "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**", + "Url": "https://hub.prowler.com/check/rds_instance_tde_enabled" + } + }, + "Categories": [ + "encryption" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/rds/rds_instance_tde_enabled/rds_instance_tde_enabled.py b/prowler/providers/alibabacloud/services/rds/rds_instance_tde_enabled/rds_instance_tde_enabled.py new file mode 100644 index 0000000000..87683c0322 --- /dev/null +++ b/prowler/providers/alibabacloud/services/rds/rds_instance_tde_enabled/rds_instance_tde_enabled.py @@ -0,0 +1,32 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.rds.rds_client import rds_client + + +class rds_instance_tde_enabled(Check): + """Check if TDE is set to Enabled for applicable database instance.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + for instance in rds_client.instances: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=instance + ) + report.region = instance.region + report.resource_id = instance.id + report.resource_arn = f"acs:rds:{instance.region}:{rds_client.audited_account}:dbinstance/{instance.id}" + + if instance.tde_status == "Enabled": + report.status = "PASS" + report.status_extended = ( + f"RDS Instance {instance.name} has TDE enabled." + ) + else: + report.status = "FAIL" + report.status_extended = ( + f"RDS Instance {instance.name} does not have TDE enabled." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/rds/rds_instance_tde_key_custom/__init__.py b/prowler/providers/alibabacloud/services/rds/rds_instance_tde_key_custom/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..1b4c8d4596 --- /dev/null +++ b/prowler/providers/alibabacloud/services/rds/rds_instance_tde_key_custom/rds_instance_tde_key_custom.metadata.json @@ -0,0 +1,39 @@ +{ + "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" + ], + "ServiceName": "rds", + "SubServiceName": "", + "ResourceIdTemplate": "acs:rds:region:account-id:dbinstance/{dbinstance-id}", + "Severity": "medium", + "ResourceType": "AlibabaCloudRDSDBInstance", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun rds ModifyDBInstanceTDE --DBInstanceId --TDEStatus Enabled --EncryptionKey ", + "NativeIaC": "", + "Other": "", + "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**", + "Url": "https://hub.prowler.com/check/rds_instance_tde_key_custom" + } + }, + "Categories": [ + "encryption" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/rds/rds_instance_tde_key_custom/rds_instance_tde_key_custom.py b/prowler/providers/alibabacloud/services/rds/rds_instance_tde_key_custom/rds_instance_tde_key_custom.py new file mode 100644 index 0000000000..087b36c670 --- /dev/null +++ b/prowler/providers/alibabacloud/services/rds/rds_instance_tde_key_custom/rds_instance_tde_key_custom.py @@ -0,0 +1,38 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.rds.rds_client import rds_client + + +class rds_instance_tde_key_custom(Check): + """Check if RDS instance TDE protector is encrypted with BYOK.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + for instance in rds_client.instances: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=instance + ) + report.region = instance.region + report.resource_id = instance.id + report.resource_arn = f"acs:rds:{instance.region}:{rds_client.audited_account}:dbinstance/{instance.id}" + + # TDE must be enabled AND key must be custom (not service managed) + # Note: The API response for TDEKeyId usually indicates if it's a custom KMS key + # If it's a UUID-like string, it's likely a KMS key. If it's "ServiceManaged" or similar, it's not. + # For Alibaba Cloud, typically if you supply a KeyId it's BYOK. + + if instance.tde_status == "Enabled" and instance.tde_key_id: + report.status = "PASS" + report.status_extended = f"RDS Instance {instance.name} has TDE enabled with custom key {instance.tde_key_id}." + elif instance.tde_status == "Enabled": + report.status = "FAIL" + report.status_extended = f"RDS Instance {instance.name} has TDE enabled but uses service-managed key." + else: + report.status = "FAIL" + report.status_extended = ( + f"RDS Instance {instance.name} does not have TDE enabled." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/rds/rds_service.py b/prowler/providers/alibabacloud/services/rds/rds_service.py new file mode 100644 index 0000000000..3d3f29d0da --- /dev/null +++ b/prowler/providers/alibabacloud/services/rds/rds_service.py @@ -0,0 +1,274 @@ +from alibabacloud_rds20140815 import models as rds_models +from pydantic.v1 import BaseModel + +from prowler.lib.logger import logger +from prowler.lib.scan_filters.scan_filters import is_resource_filtered +from prowler.providers.alibabacloud.lib.service.service import AlibabaCloudService + + +class RDS(AlibabaCloudService): + """ + RDS (Relational Database Service) class for Alibaba Cloud. + + This class provides methods to interact with Alibaba Cloud RDS service + to retrieve DB instances and their configurations. + """ + + def __init__(self, provider): + # Call AlibabaCloudService's __init__ + super().__init__(__class__.__name__, provider, global_service=False) + + # Fetch RDS resources + self.instances = [] + self.__threading_call__(self._describe_instances) + + def _describe_instances(self, regional_client): + """List all RDS instances and fetch their details in a specific region.""" + region = getattr(regional_client, "region", "unknown") + logger.info(f"RDS - Describing instances in {region}...") + + try: + # DescribeDBInstances returns instance list + request = rds_models.DescribeDBInstancesRequest() + response = regional_client.describe_dbinstances(request) + + if response and response.body and response.body.items: + for instance_data in response.body.items.dbinstance: + instance_id = getattr(instance_data, "dbinstance_id", "") + + if not self.audit_resources or is_resource_filtered( + instance_id, self.audit_resources + ): + + # Get additional information for specific checks + attribute_info = self._describe_db_instance_attribute( + regional_client, instance_id + ) + + # Check if SSL is enabled + ssl_status = self._describe_db_instance_ssl( + regional_client, instance_id + ) + + # Check TDE status + tde_status = self._describe_db_instance_tde( + regional_client, instance_id + ) + + # Check whitelist/security IPs + security_ips = self._describe_db_instance_ip_array( + regional_client, instance_id + ) + + # Check SQL audit status (SQL Explorer) + audit_status = self._describe_sql_collector_policy( + regional_client, instance_id + ) + + # Check parameters (log_connections, log_disconnections, log_duration) + parameters = self._describe_parameters( + regional_client, instance_id + ) + + self.instances.append( + DBInstance( + id=instance_id, + name=getattr( + instance_data, "dbinstance_description", instance_id + ), + region=region, + engine=getattr(instance_data, "engine", ""), + engine_version=getattr( + instance_data, "engine_version", "" + ), + status=getattr(instance_data, "dbinstance_status", ""), + type=getattr(instance_data, "dbinstance_type", ""), + net_type=getattr( + instance_data, "dbinstance_net_type", "" + ), + connection_mode=getattr( + instance_data, "connection_mode", "" + ), + public_connection_string=attribute_info.get( + "ConnectionString", "" + ), + ssl_enabled=ssl_status.get("SSLEnabled", False), + tde_status=tde_status.get("TDEStatus", "Disabled"), + tde_key_id=tde_status.get("TDEKeyId", ""), + security_ips=security_ips, + audit_log_enabled=audit_status.get("StoragePeriod") + is not None, + audit_log_retention=audit_status.get( + "StoragePeriod", 0 + ), + log_connections=parameters.get( + "log_connections", "off" + ), + log_disconnections=parameters.get( + "log_disconnections", "off" + ), + log_duration=parameters.get("log_duration", "off"), + ) + ) + + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def _describe_db_instance_attribute( + self, regional_client, instance_id: str + ) -> dict: + """Get DB instance attributes including connection string.""" + try: + request = rds_models.DescribeDBInstanceAttributeRequest() + request.dbinstance_id = instance_id + response = regional_client.describe_dbinstance_attribute(request) + + if ( + response + and response.body + and response.body.items + and response.body.items.dbinstance_attribute + ): + # The response is a list, usually with one item + attrs = response.body.items.dbinstance_attribute[0] + return {"ConnectionString": getattr(attrs, "connection_string", "")} + return {} + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return {} + + def _describe_db_instance_ssl(self, regional_client, instance_id: str) -> dict: + """Check if SSL is enabled.""" + try: + request = rds_models.DescribeDBInstanceSSLRequest() + request.dbinstance_id = instance_id + response = regional_client.describe_dbinstance_ssl(request) + + if response and response.body: + # response.body is a DescribeDBInstanceSSLResponseBody model object, use getattr + ssl_enabled = getattr(response.body, "sslenabled", "No") + force_encryption = getattr(response.body, "force_encryption", "0") + + # SSL is enabled if SSLEnabled is "Yes" or ForceEncryption is "1" + ssl_status = ssl_enabled == "Yes" or force_encryption == "1" + return {"SSLEnabled": ssl_status} + return {"SSLEnabled": False} + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + # Some instance types might not support SSL query + return {"SSLEnabled": False} + + def _describe_db_instance_tde(self, regional_client, instance_id: str) -> dict: + """Check TDE status.""" + try: + request = rds_models.DescribeDBInstanceTDERequest() + request.dbinstance_id = instance_id + response = regional_client.describe_dbinstance_tde(request) + + if response and response.body: + return { + "TDEStatus": getattr(response.body, "tdestatus", "Disabled"), + } + return {"TDEStatus": "Disabled"} + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return {"TDEStatus": "Disabled"} + + def _describe_db_instance_ip_array(self, regional_client, instance_id: str) -> list: + """Get whitelist IP arrays.""" + try: + request = rds_models.DescribeDBInstanceIPArrayListRequest() + request.dbinstance_id = instance_id + response = regional_client.describe_dbinstance_iparray_list(request) + + ips = [] + if response and response.body and response.body.items: + for item in response.body.items.dbinstance_iparray: + security_ips = getattr(item, "security_ips", "") + if security_ips: + ips.extend(security_ips.split(",")) + return ips + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return [] + + def _describe_sql_collector_policy(self, regional_client, instance_id: str) -> dict: + """Check SQL audit status.""" + try: + request = rds_models.DescribeSQLLogRecordsRequest() + request.dbinstance_id = instance_id + + policy_request = rds_models.DescribeSQLCollectorPolicyRequest() + policy_request.dbinstance_id = instance_id + response = regional_client.describe_sqlcollector_policy(policy_request) + + if response and response.body: + status = getattr(response.body, "sqlcollector_status", "") + # storage_period is in days + storage_period = getattr(response.body, "storage_period", 0) + + if status == "Enable": + return {"StoragePeriod": storage_period} + + return {} + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return {} + + def _describe_parameters(self, regional_client, instance_id: str) -> dict: + """Get instance parameters.""" + try: + request = rds_models.DescribeParametersRequest() + request.dbinstance_id = instance_id + response = regional_client.describe_parameters(request) + + params = {} + if response and response.body and response.body.running_parameters: + for param in response.body.running_parameters.dbinstance_parameter: + key = getattr(param, "parameter_name", "") + value = getattr(param, "parameter_value", "") + if key in ["log_connections", "log_disconnections", "log_duration"]: + params[key] = value.lower() + + return params + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return {} + + +class DBInstance(BaseModel): + """RDS DB Instance model.""" + + id: str + name: str + region: str + engine: str + engine_version: str + status: str + type: str + net_type: str + connection_mode: str + public_connection_string: str + ssl_enabled: bool + tde_status: str + tde_key_id: str + security_ips: list + audit_log_enabled: bool + audit_log_retention: int # in days + log_connections: str + log_disconnections: str + log_duration: str diff --git a/prowler/providers/alibabacloud/services/securitycenter/__init__.py b/prowler/providers/alibabacloud/services/securitycenter/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/alibabacloud/services/securitycenter/securitycenter_advanced_or_enterprise_edition/__init__.py b/prowler/providers/alibabacloud/services/securitycenter/securitycenter_advanced_or_enterprise_edition/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..ce17fcfb86 --- /dev/null +++ b/prowler/providers/alibabacloud/services/securitycenter/securitycenter_advanced_or_enterprise_edition/securitycenter_advanced_or_enterprise_edition.metadata.json @@ -0,0 +1,43 @@ +{ + "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" + ], + "ServiceName": "securitycenter", + "SubServiceName": "", + "ResourceIdTemplate": "acs:sas::account-id:security-center", + "Severity": "medium", + "ResourceType": "AlibabaCloudSecurityCenter", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "Logon to Security Center Console > Select Overview > Click Upgrade > Select Advanced or Enterprise Edition > Finish order placement", + "NativeIaC": "", + "Other": "", + "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", + "Url": "https://hub.prowler.com/check/securitycenter_advanced_or_enterprise_edition" + } + }, + "Categories": [ + "forensics-ready" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/securitycenter/securitycenter_advanced_or_enterprise_edition/securitycenter_advanced_or_enterprise_edition.py b/prowler/providers/alibabacloud/services/securitycenter/securitycenter_advanced_or_enterprise_edition/securitycenter_advanced_or_enterprise_edition.py new file mode 100644 index 0000000000..63a32e5210 --- /dev/null +++ b/prowler/providers/alibabacloud/services/securitycenter/securitycenter_advanced_or_enterprise_edition/securitycenter_advanced_or_enterprise_edition.py @@ -0,0 +1,48 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.securitycenter.securitycenter_client import ( + securitycenter_client, +) + + +class securitycenter_advanced_or_enterprise_edition(Check): + """Check if Security Center is Advanced or Enterprise Edition.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + report = CheckReportAlibabaCloud(metadata=self.metadata(), resource={}) + report.region = securitycenter_client.region + report.resource_id = securitycenter_client.audited_account + report.resource_arn = ( + f"acs:sas::{securitycenter_client.audited_account}:security-center" + ) + + version = securitycenter_client.version + edition = securitycenter_client.edition + + if version is None or edition == "Unknown": + report.status = "MANUAL" + report.status_extended = ( + "Security Center edition could not be determined. " + "Please check Security Center Console manually." + ) + else: + # Check if version is 3 (Enterprise) or 5 (Advanced) + # Version mapping: 1=Basic, 3=Enterprise, 5=Advanced, 6=Anti-virus, 7=Ultimate, 8=Multi-Version, 10=Value-added Plan + if version == 3 or version == 5: + report.status = "PASS" + report.status_extended = ( + f"Security Center is {edition} edition (Version {version}), which provides " + "threat detection for network and endpoints, malware detection, " + "webshell detection and anomaly detection." + ) + else: + report.status = "FAIL" + report.status_extended = ( + f"Security Center is {edition} edition (Version {version}). " + "It is recommended to use Advanced Edition (Version 5) or Enterprise Edition (Version 3) " + "for full protection to defend cloud threats." + ) + + findings.append(report) + return findings diff --git a/prowler/providers/alibabacloud/services/securitycenter/securitycenter_all_assets_agent_installed/__init__.py b/prowler/providers/alibabacloud/services/securitycenter/securitycenter_all_assets_agent_installed/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..cc2853bcff --- /dev/null +++ b/prowler/providers/alibabacloud/services/securitycenter/securitycenter_all_assets_agent_installed/securitycenter_all_assets_agent_installed.metadata.json @@ -0,0 +1,42 @@ +{ + "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" + ], + "ServiceName": "securitycenter", + "SubServiceName": "", + "ResourceIdTemplate": "acs:sas:region:account-id:machine/{machine-id}", + "Severity": "high", + "ResourceType": "AlibabaCloudSecurityCenterMachine", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun sas InstallUninstallAegis --InstanceIds ,", + "NativeIaC": "", + "Other": "", + "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", + "Url": "https://hub.prowler.com/check/securitycenter_all_assets_agent_installed" + } + }, + "Categories": [ + "forensics-ready" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/securitycenter/securitycenter_all_assets_agent_installed/securitycenter_all_assets_agent_installed.py b/prowler/providers/alibabacloud/services/securitycenter/securitycenter_all_assets_agent_installed/securitycenter_all_assets_agent_installed.py new file mode 100644 index 0000000000..77024838cd --- /dev/null +++ b/prowler/providers/alibabacloud/services/securitycenter/securitycenter_all_assets_agent_installed/securitycenter_all_assets_agent_installed.py @@ -0,0 +1,48 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.securitycenter.securitycenter_client import ( + securitycenter_client, +) + + +class securitycenter_all_assets_agent_installed(Check): + """Check if all assets are installed with security agent.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + uninstalled_machines = securitycenter_client.uninstalled_machines + + if not uninstalled_machines: + # All assets have the agent installed + report = CheckReportAlibabaCloud(metadata=self.metadata(), resource={}) + report.region = securitycenter_client.region + report.resource_id = securitycenter_client.audited_account + report.resource_arn = ( + f"acs:sas::{securitycenter_client.audited_account}:security-center" + ) + report.status = "PASS" + report.status_extended = "All assets have Security Center agent installed." + findings.append(report) + else: + # Report each uninstalled machine + for machine in uninstalled_machines: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=machine + ) + report.region = machine.region + report.resource_id = machine.instance_id + report.resource_arn = ( + f"acs:ecs:{machine.region}:{securitycenter_client.audited_account}:instance/{machine.instance_id}" + if machine.instance_id.startswith("i-") + or "ecs" in machine.instance_id.lower() + else f"acs:sas:{machine.region}:{securitycenter_client.audited_account}:machine/{machine.instance_id}" + ) + report.status = "FAIL" + report.status_extended = ( + f"Asset {machine.instance_name if machine.instance_name else machine.instance_id} " + f"({machine.instance_id}) does not have Security Center agent installed. " + f"Region: {machine.region}, OS: {machine.os if machine.os else 'Unknown'}." + ) + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/securitycenter/securitycenter_client.py b/prowler/providers/alibabacloud/services/securitycenter/securitycenter_client.py new file mode 100644 index 0000000000..e0e8993a6e --- /dev/null +++ b/prowler/providers/alibabacloud/services/securitycenter/securitycenter_client.py @@ -0,0 +1,6 @@ +from prowler.providers.alibabacloud.services.securitycenter.securitycenter_service import ( + SecurityCenter, +) +from prowler.providers.common.provider import Provider + +securitycenter_client = SecurityCenter(Provider.get_global_provider()) diff --git a/prowler/providers/alibabacloud/services/securitycenter/securitycenter_notification_enabled_high_risk/__init__.py b/prowler/providers/alibabacloud/services/securitycenter/securitycenter_notification_enabled_high_risk/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..6c1c245af5 --- /dev/null +++ b/prowler/providers/alibabacloud/services/securitycenter/securitycenter_notification_enabled_high_risk/securitycenter_notification_enabled_high_risk.metadata.json @@ -0,0 +1,42 @@ +{ + "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" + ], + "ServiceName": "securitycenter", + "SubServiceName": "", + "ResourceIdTemplate": "acs:sas::account-id:notice-config/{project}", + "Severity": "medium", + "ResourceType": "AlibabaCloudSecurityCenterNoticeConfig", + "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**.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun sas ModifyNoticeConfig --Project --Route ", + "NativeIaC": "", + "Other": "", + "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", + "Url": "https://hub.prowler.com/check/securitycenter_notification_enabled_high_risk" + } + }, + "Categories": [ + "forensics-ready" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/securitycenter/securitycenter_notification_enabled_high_risk/securitycenter_notification_enabled_high_risk.py b/prowler/providers/alibabacloud/services/securitycenter/securitycenter_notification_enabled_high_risk/securitycenter_notification_enabled_high_risk.py new file mode 100644 index 0000000000..78cd358e08 --- /dev/null +++ b/prowler/providers/alibabacloud/services/securitycenter/securitycenter_notification_enabled_high_risk/securitycenter_notification_enabled_high_risk.py @@ -0,0 +1,65 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.securitycenter.securitycenter_client import ( + securitycenter_client, +) + + +class securitycenter_notification_enabled_high_risk(Check): + """Check if notification is enabled on all high risk items.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + # High-risk categories based on CIS benchmark: + # - Vulnerability: sas_vulnerability, yundun_sas_vul_Emergency + # - Baseline Risks: sas_healthcheck + # - Alerts: sas_suspicious, suspicious, remotelogin, webshell, bruteforcesuccess + # - Accesskey Leak: yundun_sas_ak_leakage + high_risk_projects = [ + "sas_vulnerability", # Vulnerability + "yundun_sas_vul_Emergency", # Emergency vulnerabilities + "sas_healthcheck", # Baseline Risks + "sas_suspicious", # Alerts - Suspicious + "suspicious", # Alerts - Suspicious + "remotelogin", # Alerts - Remote login + "webshell", # Alerts - Webshell + "bruteforcesuccess", # Alerts - Brute force success + "yundun_sas_ak_leakage", # Accesskey Leak + ] + + notice_configs = securitycenter_client.notice_configs + + # Check each high-risk project + for project in high_risk_projects: + config = notice_configs.get(project) + + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=config if config else {} + ) + report.region = securitycenter_client.region + report.resource_id = project + report.resource_arn = f"acs:sas::{securitycenter_client.audited_account}:notice-config/{project}" + + if not config: + # Configuration not found - may not be available or not configured + report.status = "MANUAL" + report.status_extended = ( + f"Notification configuration for high-risk item '{project}' " + "could not be determined. Please check Security Center Console manually." + ) + elif config.notification_enabled: + # Route != 0 means notification is enabled + report.status = "PASS" + report.status_extended = ( + f"Notification is enabled for high-risk item '{project}'." + ) + else: + # Route == 0 means notification is disabled + report.status = "FAIL" + report.status_extended = ( + f"Notification is not enabled for high-risk item '{project}'." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/securitycenter/securitycenter_service.py b/prowler/providers/alibabacloud/services/securitycenter/securitycenter_service.py new file mode 100644 index 0000000000..6f114a3597 --- /dev/null +++ b/prowler/providers/alibabacloud/services/securitycenter/securitycenter_service.py @@ -0,0 +1,394 @@ +from alibabacloud_sas20181203 import models as sas_models +from pydantic.v1 import BaseModel + +from prowler.lib.logger import logger +from prowler.providers.alibabacloud.lib.service.service import AlibabaCloudService + + +class SecurityCenter(AlibabaCloudService): + """ + Security Center service class for Alibaba Cloud. + + This class provides methods to interact with Alibaba Cloud Security Center + to retrieve vulnerabilities, agent status, etc. + """ + + def __init__(self, provider): + # Call AlibabaCloudService's __init__ + super().__init__("sas", provider, global_service=True) + + self.instance_vulnerabilities = {} + self.instance_agents = {} + self.uninstalled_machines = [] + self.notice_configs = {} + self.vul_configs = {} + self.concern_necessity = [] + self.edition = None + self.version = None + self._describe_vulnerabilities() + self._describe_agents() + self._list_uninstalled_machines() + self._describe_notice_configs() + self._describe_vul_config() + self._describe_concern_necessity() + self._get_edition() + + def _describe_vulnerabilities(self): + """List vulnerabilities for ECS instances.""" + logger.info("Security Center - Describing Vulnerabilities...") + + try: + # Get all vulnerabilities + # Type: "cve" for CVE vulnerabilities, "app" for application vulnerabilities, "sys" for system vulnerabilities + # We'll check all types by making separate requests + vulnerability_types = ["cve", "app", "sys"] + + for vul_type in vulnerability_types: + request = sas_models.DescribeVulListRequest() + request.type = vul_type + request.current_page = 1 + request.page_size = 100 + + while True: + response = self.client.describe_vul_list(request) + + if response and response.body and response.body.vul_records: + vul_records = response.body.vul_records + if not vul_records: + break + + for vul_record in vul_records: + instance_id = getattr(vul_record, "instance_id", "") + if not instance_id: + continue + + # Get instance name and region from the vulnerability record + instance_name = getattr( + vul_record, "instance_name", instance_id + ) + region = getattr(vul_record, "region_id", "") + + instance_key = ( + f"{region}:{instance_id}" if region else instance_id + ) + + if instance_key not in self.instance_vulnerabilities: + self.instance_vulnerabilities[instance_key] = ( + InstanceVulnerability( + instance_id=instance_id, + instance_name=instance_name, + region=region, + has_vulnerabilities=True, + vulnerability_count=1, + ) + ) + else: + # Increment vulnerability count + self.instance_vulnerabilities[ + instance_key + ].vulnerability_count += 1 + + # Check if there are more pages + total_count = getattr(response.body, "total_count", 0) + if request.current_page * request.page_size >= total_count: + break + request.current_page += 1 + else: + break + + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def _describe_agents(self): + """List Security Center agent status for ECS instances.""" + logger.info("Security Center - Describing Agents...") + + try: + # Get all agents + request = sas_models.DescribeCloudCenterInstancesRequest() + request.current_page = 1 + request.page_size = 100 + + while True: + response = self.client.describe_cloud_center_instances(request) + + if response and response.body and response.body.instances: + instances = response.body.instances + if not instances: + break + + for instance_data in instances: + instance_id = getattr(instance_data, "instance_id", "") + if not instance_id: + continue + + instance_name = getattr( + instance_data, "instance_name", instance_id + ) + region = getattr(instance_data, "region_id", "") + agent_status = getattr(instance_data, "client_status", "") + + # Determine if agent is installed and online + agent_installed = agent_status in ["online", "offline"] + is_online = agent_status == "online" + + instance_key = ( + f"{region}:{instance_id}" if region else instance_id + ) + + self.instance_agents[instance_key] = InstanceAgent( + instance_id=instance_id, + instance_name=instance_name, + region=region, + agent_installed=agent_installed, + agent_status=( + agent_status + if agent_status + else ("online" if is_online else "not_installed") + ), + ) + + # Check if there are more pages + total_count = getattr(response.body, "total_count", 0) + if request.current_page * request.page_size >= total_count: + break + request.current_page += 1 + else: + break + + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def _list_uninstalled_machines(self): + """List machines without Security Center agent installed.""" + logger.info("Security Center - Listing Uninstalled Machines...") + + try: + # Get all machines without agent installed + request = sas_models.ListUninstallAegisMachinesRequest() + request.current_page = 1 + request.page_size = 100 + + while True: + response = self.client.list_uninstall_aegis_machines(request) + + if response and response.body and response.body.machine_list: + machines = response.body.machine_list + if not machines: + break + + for machine_data in machines: + instance_id = getattr(machine_data, "instance_id", "") + if not instance_id: + continue + + self.uninstalled_machines.append( + UninstalledMachine( + instance_id=instance_id, + instance_name=getattr( + machine_data, "instance_name", instance_id + ), + region=getattr(machine_data, "region_id", "") + or getattr(machine_data, "machine_region", ""), + uuid=getattr(machine_data, "uuid", ""), + os=getattr(machine_data, "os", ""), + internet_ip=getattr(machine_data, "internet_ip", ""), + intranet_ip=getattr(machine_data, "intranet_ip", ""), + ) + ) + + # Check if there are more pages + total_count = getattr(response.body, "total_count", 0) + if request.current_page * request.page_size >= total_count: + break + request.current_page += 1 + else: + break + + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def _describe_notice_configs(self): + """List notification configurations for Security Center.""" + logger.info("Security Center - Describing Notice Configs...") + + try: + # Get notification configurations + request = sas_models.DescribeNoticeConfigRequest() + response = self.client.describe_notice_config(request) + + if response and response.body and response.body.notice_config_list: + notice_configs = response.body.notice_config_list + + for config_data in notice_configs: + project = getattr(config_data, "project", "") + if not project: + continue + + route = getattr(config_data, "route", 0) + time_limit = getattr(config_data, "time_limit", 0) + + self.notice_configs[project] = NoticeConfig( + project=project, + route=route, + time_limit=time_limit, + notification_enabled=route != 0, + ) + + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def _describe_vul_config(self): + """List vulnerability scan configuration.""" + logger.info("Security Center - Describing Vulnerability Config...") + + try: + # Get vulnerability scan configuration + request = sas_models.DescribeVulConfigRequest() + response = self.client.describe_vul_config(request) + + if response and response.body and response.body.target_configs: + target_configs = response.body.target_configs + + for config_data in target_configs: + config_type = getattr(config_data, "type", "") + config_value = getattr(config_data, "config", "") + + if config_type: + self.vul_configs[config_type] = VulConfig( + type=config_type, + config=config_value, + enabled=config_value != "off", + ) + + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def _describe_concern_necessity(self): + """List vulnerability scan level priorities.""" + logger.info("Security Center - Describing Concern Necessity...") + + try: + # Get vulnerability scan level priorities + request = sas_models.DescribeConcernNecessityRequest() + response = self.client.describe_concern_necessity(request) + + if response and response.body: + concern_necessity = getattr(response.body, "concern_necessity", []) + if concern_necessity: + self.concern_necessity = concern_necessity + else: + self.concern_necessity = [] + + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + self.concern_necessity = [] + + def _get_edition(self): + """Get Security Center edition.""" + logger.info("Security Center - Getting Edition...") + + # Version mapping: 1=Basic, 3=Enterprise, 5=Advanced, 6=Anti-virus, 7=Ultimate, 8=Multi-Version, 10=Value-added Plan + version_to_edition = { + 1: "Basic", + 3: "Enterprise", + 5: "Advanced", + 6: "Anti-virus", + 7: "Ultimate", + 8: "Multi-Version", + 10: "Value-added Plan", + } + + try: + # Get Security Center edition + request = sas_models.DescribeVersionConfigRequest() + response = self.client.describe_version_config(request) + + if response and response.body: + # Get Version field from response + version = getattr(response.body, "version", None) + + if version is not None: + # Map version number to edition name + self.edition = version_to_edition.get( + version, f"Unknown (Version {version})" + ) + self.version = version + logger.info( + f"Security Center Version: {version}, Edition: {self.edition}" + ) + else: + self.edition = "Unknown" + self.version = None + else: + self.edition = "Unknown" + self.version = None + + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + self.edition = "Unknown" + self.version = None + + +# Models for Security Center service +class InstanceVulnerability(BaseModel): + """Security Center Instance Vulnerability model.""" + + instance_id: str + instance_name: str + region: str + has_vulnerabilities: bool + vulnerability_count: int = 0 + + +class InstanceAgent(BaseModel): + """Security Center Instance Agent model.""" + + instance_id: str + instance_name: str + region: str + agent_installed: bool + agent_status: str = "" # "online", "offline", "not_installed" + + +class UninstalledMachine(BaseModel): + """Security Center Uninstalled Machine model.""" + + instance_id: str + instance_name: str + region: str + uuid: str = "" + os: str = "" + internet_ip: str = "" + intranet_ip: str = "" + + +class NoticeConfig(BaseModel): + """Security Center Notice Config model.""" + + project: str + route: int # 0 = no notification, >0 = notification enabled + time_limit: int = 0 + notification_enabled: bool + + +class VulConfig(BaseModel): + """Security Center Vulnerability Config model.""" + + type: str # yum, cve, sys, cms, emg, etc. + config: str # "off", "on", or other values + enabled: bool # True if config != "off" diff --git a/prowler/providers/alibabacloud/services/securitycenter/securitycenter_vulnerability_scan_enabled/__init__.py b/prowler/providers/alibabacloud/services/securitycenter/securitycenter_vulnerability_scan_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..305e1c3265 --- /dev/null +++ b/prowler/providers/alibabacloud/services/securitycenter/securitycenter_vulnerability_scan_enabled/securitycenter_vulnerability_scan_enabled.metadata.json @@ -0,0 +1,39 @@ +{ + "Provider": "alibabacloud", + "CheckID": "securitycenter_vulnerability_scan_enabled", + "CheckTitle": "Scheduled vulnerability scan is enabled on all servers", + "CheckType": [ + "Malicious software", + "Web application threat detection" + ], + "ServiceName": "securitycenter", + "SubServiceName": "", + "ResourceIdTemplate": "acs:sas::account-id:vulnerability-scan-config", + "Severity": "high", + "ResourceType": "AlibabaCloudSecurityCenterVulConfig", + "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**.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun sas ModifyVulConfig --Type --Config on", + "NativeIaC": "", + "Other": "", + "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", + "Url": "https://hub.prowler.com/check/securitycenter_vulnerability_scan_enabled" + } + }, + "Categories": [ + "vulnerabilities" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/securitycenter/securitycenter_vulnerability_scan_enabled/securitycenter_vulnerability_scan_enabled.py b/prowler/providers/alibabacloud/services/securitycenter/securitycenter_vulnerability_scan_enabled/securitycenter_vulnerability_scan_enabled.py new file mode 100644 index 0000000000..65a59efdfd --- /dev/null +++ b/prowler/providers/alibabacloud/services/securitycenter/securitycenter_vulnerability_scan_enabled/securitycenter_vulnerability_scan_enabled.py @@ -0,0 +1,68 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.securitycenter.securitycenter_client import ( + securitycenter_client, +) + + +class securitycenter_vulnerability_scan_enabled(Check): + """Check if scheduled vulnerability scan is enabled on all servers.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + # Required vulnerability types that must be enabled + required_vul_types = ["yum", "cve", "sys", "cms", "emg"] + + # Required scan levels: "asap" (high) and "later" (medium) + required_scan_levels = ["asap", "later"] + + vul_configs = securitycenter_client.vul_configs + concern_necessity = securitycenter_client.concern_necessity + + # Check vulnerability types + disabled_types = [] + for vul_type in required_vul_types: + config = vul_configs.get(vul_type) + if not config or not config.enabled: + disabled_types.append(vul_type) + + # Check scan levels + missing_levels = [] + for level in required_scan_levels: + if level not in concern_necessity: + missing_levels.append(level) + + # Create report + report = CheckReportAlibabaCloud(metadata=self.metadata(), resource={}) + report.region = securitycenter_client.region + report.resource_id = securitycenter_client.audited_account + report.resource_arn = f"acs:sas::{securitycenter_client.audited_account}:vulnerability-scan-config" + + if not disabled_types and not missing_levels: + report.status = "PASS" + report.status_extended = ( + "Scheduled vulnerability scan is enabled for all vulnerability types " + "(yum, cve, sys, cms, emg) and all required scan levels (high/asap, medium/later) are enabled." + ) + else: + report.status = "FAIL" + issues = [] + if disabled_types: + issues.append( + f"Vulnerability types disabled: {', '.join(disabled_types)}" + ) + if missing_levels: + level_names = {"asap": "high", "later": "medium"} + missing_names = [ + level_names.get(level, level) for level in missing_levels + ] + issues.append( + f"Scan levels not enabled: {', '.join(missing_names)} ({', '.join(missing_levels)})" + ) + report.status_extended = ( + "Scheduled vulnerability scan is not properly configured. " + + "; ".join(issues) + ) + + findings.append(report) + return findings diff --git a/prowler/providers/alibabacloud/services/sls/__init__.py b/prowler/providers/alibabacloud/services/sls/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/alibabacloud/services/sls/sls_client.py b/prowler/providers/alibabacloud/services/sls/sls_client.py new file mode 100644 index 0000000000..d62fcb29f9 --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_client.py @@ -0,0 +1,4 @@ +from prowler.providers.alibabacloud.services.sls.sls_service import Sls +from prowler.providers.common.provider import Provider + +sls_client = Sls(Provider.get_global_provider()) diff --git a/prowler/providers/alibabacloud/services/sls/sls_cloud_firewall_changes_alert_enabled/__init__.py b/prowler/providers/alibabacloud/services/sls/sls_cloud_firewall_changes_alert_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..4d6f3d2ca0 --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_cloud_firewall_changes_alert_enabled/sls_cloud_firewall_changes_alert_enabled.metadata.json @@ -0,0 +1,39 @@ +{ + "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" + ], + "ServiceName": "sls", + "SubServiceName": "", + "ResourceIdTemplate": "acs:log:region:account-id:project/project-name/alert/alert-name", + "Severity": "medium", + "ResourceType": "AlibabaCloudSLSAlert", + "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**.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. Log on to the **SLS Console**\n2. Ensure **ActionTrail** is enabled\n3. Select **Alerts**\n4. Ensure alert rule has been enabled for Cloud Firewall changes", + "Url": "https://hub.prowler.com/check/sls_cloud_firewall_changes_alert_enabled" + } + }, + "Categories": [ + "logging" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/sls/sls_cloud_firewall_changes_alert_enabled/sls_cloud_firewall_changes_alert_enabled.py b/prowler/providers/alibabacloud/services/sls/sls_cloud_firewall_changes_alert_enabled/sls_cloud_firewall_changes_alert_enabled.py new file mode 100644 index 0000000000..aae2ddbd52 --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_cloud_firewall_changes_alert_enabled/sls_cloud_firewall_changes_alert_enabled.py @@ -0,0 +1,50 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.sls.sls_client import sls_client + + +class sls_cloud_firewall_changes_alert_enabled(Check): + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + found = False + + for alert in sls_client.alerts: + query_list = alert.configuration.get("queryList", []) + if not query_list: + continue + + for query_obj in query_list: + query = query_obj.get("query", "") + if "Cloudfw" in query and ( + "CreateVpcFirewallControlPolicy" in query + or "DeleteVpcFirewallControlPolicy" in query + or "ModifyVpcFirewallControlPolicy" in query + ): + found = True + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=alert + ) + report.status = "PASS" + report.status_extended = f"SLS Alert {alert.name} is configured for Cloud Firewall changes." + report.resource_id = alert.name + report.resource_arn = alert.arn + report.region = alert.region + findings.append(report) + break + + if found: + break + + if not found: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=sls_client.provider.identity + ) + report.status = "FAIL" + report.status_extended = ( + "No SLS Alert configured for Cloud Firewall changes." + ) + report.resource_id = sls_client.audited_account + report.resource_arn = sls_client.provider.identity.identity_arn + report.region = sls_client.region + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/sls/sls_customer_created_cmk_changes_alert_enabled/__init__.py b/prowler/providers/alibabacloud/services/sls/sls_customer_created_cmk_changes_alert_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..edd2b83c8d --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_customer_created_cmk_changes_alert_enabled/sls_customer_created_cmk_changes_alert_enabled.metadata.json @@ -0,0 +1,39 @@ +{ + "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" + ], + "ServiceName": "sls", + "SubServiceName": "", + "ResourceIdTemplate": "acs:log:region:account-id:project/project-name/alert/alert-name", + "Severity": "medium", + "ResourceType": "AlibabaCloudSLSAlert", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. Log on to the **SLS Console**\n2. Ensure **ActionTrail** is enabled\n3. Select **Alerts**\n4. Ensure alert rule has been enabled for disabling or deletion of customer-created CMKs", + "Url": "https://hub.prowler.com/check/sls_customer_created_cmk_changes_alert_enabled" + } + }, + "Categories": [ + "logging" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/sls/sls_customer_created_cmk_changes_alert_enabled/sls_customer_created_cmk_changes_alert_enabled.py b/prowler/providers/alibabacloud/services/sls/sls_customer_created_cmk_changes_alert_enabled/sls_customer_created_cmk_changes_alert_enabled.py new file mode 100644 index 0000000000..a41c97fed6 --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_customer_created_cmk_changes_alert_enabled/sls_customer_created_cmk_changes_alert_enabled.py @@ -0,0 +1,48 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.sls.sls_client import sls_client + + +class sls_customer_created_cmk_changes_alert_enabled(Check): + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + found = False + + for alert in sls_client.alerts: + query_list = alert.configuration.get("queryList", []) + if not query_list: + continue + + for query_obj in query_list: + query = query_obj.get("query", "") + if "Kms" in query and ( + "DisableKey" in query + or "ScheduleKeyDeletion" in query + or "DeleteKeyMaterial" in query + ): + found = True + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=alert + ) + report.status = "PASS" + report.status_extended = f"SLS Alert {alert.name} is configured for disabling or deletion of customer created CMKs." + report.resource_id = alert.name + report.resource_arn = alert.arn + report.region = alert.region + findings.append(report) + break + + if found: + break + + if not found: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=sls_client.provider.identity + ) + report.status = "FAIL" + report.status_extended = "No SLS Alert configured for disabling or deletion of customer created CMKs." + report.resource_id = sls_client.audited_account + report.resource_arn = sls_client.provider.identity.identity_arn + report.region = sls_client.region + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/sls/sls_logstore_retention_period/__init__.py b/prowler/providers/alibabacloud/services/sls/sls_logstore_retention_period/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..4fb8a80c5b --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_logstore_retention_period/sls_logstore_retention_period.metadata.json @@ -0,0 +1,38 @@ +{ + "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" + ], + "ServiceName": "sls", + "SubServiceName": "", + "ResourceIdTemplate": "acs:log:region:account-id:project/project-name/logstore/logstore-name", + "Severity": "medium", + "ResourceType": "AlibabaCloudSLSLogStore", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "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", + "Url": "https://hub.prowler.com/check/sls_logstore_retention_period" + } + }, + "Categories": [ + "logging" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/sls/sls_logstore_retention_period/sls_logstore_retention_period.py b/prowler/providers/alibabacloud/services/sls/sls_logstore_retention_period/sls_logstore_retention_period.py new file mode 100644 index 0000000000..25d262e9e6 --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_logstore_retention_period/sls_logstore_retention_period.py @@ -0,0 +1,32 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.sls.sls_client import sls_client + + +class sls_logstore_retention_period(Check): + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + # Get configurable max days from audit config (default: 365 days) + min_log_retention_days = sls_client.audit_config.get( + "min_log_retention_days", 365 + ) + + for log_store in sls_client.log_stores: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=log_store + ) + report.resource_id = log_store.name + report.resource_arn = log_store.arn + report.region = log_store.region + + # Check retention + if log_store.retention_days >= min_log_retention_days: + report.status = "PASS" + report.status_extended = f"SLS LogStore {log_store.name} in project {log_store.project} has retention set to {log_store.retention_days} days (>= {min_log_retention_days} days)." + else: + report.status = "FAIL" + report.status_extended = f"SLS LogStore {log_store.name} in project {log_store.project} has retention set to {log_store.retention_days} days (less than {min_log_retention_days} days)." + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/sls/sls_management_console_authentication_failures_alert_enabled/__init__.py b/prowler/providers/alibabacloud/services/sls/sls_management_console_authentication_failures_alert_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..554f16bb72 --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_management_console_authentication_failures_alert_enabled/sls_management_console_authentication_failures_alert_enabled.metadata.json @@ -0,0 +1,39 @@ +{ + "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" + ], + "ServiceName": "sls", + "SubServiceName": "", + "ResourceIdTemplate": "acs:log:region:account-id:project/project-name/alert/alert-name", + "Severity": "medium", + "ResourceType": "AlibabaCloudSLSAlert", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. Log on to the **SLS Console**\n2. Ensure **ActionTrail** is enabled\n3. Select **Alerts**\n4. Ensure alert rule has been enabled for Management Console authentication failures", + "Url": "https://hub.prowler.com/check/sls_management_console_authentication_failures_alert_enabled" + } + }, + "Categories": [ + "logging" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/sls/sls_management_console_authentication_failures_alert_enabled/sls_management_console_authentication_failures_alert_enabled.py b/prowler/providers/alibabacloud/services/sls/sls_management_console_authentication_failures_alert_enabled/sls_management_console_authentication_failures_alert_enabled.py new file mode 100644 index 0000000000..0a84357df2 --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_management_console_authentication_failures_alert_enabled/sls_management_console_authentication_failures_alert_enabled.py @@ -0,0 +1,44 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.sls.sls_client import sls_client + + +class sls_management_console_authentication_failures_alert_enabled(Check): + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + found = False + + for alert in sls_client.alerts: + query_list = alert.configuration.get("queryList", []) + if not query_list: + continue + + for query_obj in query_list: + query = query_obj.get("query", "") + if "ConsoleSignin" in query and "event.errorCode" in query: + found = True + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=alert + ) + report.status = "PASS" + report.status_extended = f"SLS Alert {alert.name} is configured for Management Console authentication failures." + report.resource_id = alert.name + report.resource_arn = alert.arn + report.region = alert.region + findings.append(report) + break + + if found: + break + + if not found: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=sls_client.provider.identity + ) + report.status = "FAIL" + report.status_extended = "No SLS Alert configured for Management Console authentication failures." + report.resource_id = sls_client.audited_account + report.resource_arn = sls_client.provider.identity.identity_arn + report.region = sls_client.region + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/sls/sls_management_console_signin_without_mfa_alert_enabled/__init__.py b/prowler/providers/alibabacloud/services/sls/sls_management_console_signin_without_mfa_alert_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..c615669d83 --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_management_console_signin_without_mfa_alert_enabled/sls_management_console_signin_without_mfa_alert_enabled.metadata.json @@ -0,0 +1,39 @@ +{ + "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" + ], + "ServiceName": "sls", + "SubServiceName": "", + "ResourceIdTemplate": "acs:log:region:account-id:project/project-name/alert/alert-name", + "Severity": "medium", + "ResourceType": "AlibabaCloudSLSAlert", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. Log on to the **SLS Console**\n2. Ensure **ActionTrail** is enabled\n3. Select **Alerts**\n4. Ensure alert rule has been enabled for Management Console sign-in without MFA", + "Url": "https://hub.prowler.com/check/sls_management_console_signin_without_mfa_alert_enabled" + } + }, + "Categories": [ + "logging" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/sls/sls_management_console_signin_without_mfa_alert_enabled/sls_management_console_signin_without_mfa_alert_enabled.py b/prowler/providers/alibabacloud/services/sls/sls_management_console_signin_without_mfa_alert_enabled/sls_management_console_signin_without_mfa_alert_enabled.py new file mode 100644 index 0000000000..dffb54ebe0 --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_management_console_signin_without_mfa_alert_enabled/sls_management_console_signin_without_mfa_alert_enabled.py @@ -0,0 +1,49 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.sls.sls_client import sls_client + + +class sls_management_console_signin_without_mfa_alert_enabled(Check): + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + found = False + + for alert in sls_client.alerts: + query_list = alert.configuration.get("queryList", []) + if not query_list: + continue + + for query_obj in query_list: + query = query_obj.get("query", "") + if ( + "ConsoleSignin" in query + and "addionalEventData.loginAccount" in query + ): + found = True + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=alert + ) + report.status = "PASS" + report.status_extended = f"SLS Alert {alert.name} is configured for Management Console sign-in without MFA." + report.resource_id = alert.name + report.resource_arn = alert.arn + report.region = alert.region + findings.append(report) + break + + if found: + break + + if not found: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=sls_client.provider.identity + ) + report.status = "FAIL" + report.status_extended = ( + "No SLS Alert configured for Management Console sign-in without MFA." + ) + report.resource_id = sls_client.audited_account + report.resource_arn = sls_client.provider.identity.identity_arn + report.region = sls_client.region + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/sls/sls_oss_bucket_policy_changes_alert_enabled/__init__.py b/prowler/providers/alibabacloud/services/sls/sls_oss_bucket_policy_changes_alert_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..1b7e6417fe --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_oss_bucket_policy_changes_alert_enabled/sls_oss_bucket_policy_changes_alert_enabled.metadata.json @@ -0,0 +1,39 @@ +{ + "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" + ], + "ServiceName": "sls", + "SubServiceName": "", + "ResourceIdTemplate": "acs:log:region:account-id:project/project-name/alert/alert-name", + "Severity": "medium", + "ResourceType": "AlibabaCloudSLSAlert", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. Log on to the **SLS Console**\n2. Ensure **ActionTrail** is enabled\n3. Select **Alerts**\n4. Ensure alert rule has been enabled for OSS bucket policy changes", + "Url": "https://hub.prowler.com/check/sls_oss_bucket_policy_changes_alert_enabled" + } + }, + "Categories": [ + "logging" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/sls/sls_oss_bucket_policy_changes_alert_enabled/sls_oss_bucket_policy_changes_alert_enabled.py b/prowler/providers/alibabacloud/services/sls/sls_oss_bucket_policy_changes_alert_enabled/sls_oss_bucket_policy_changes_alert_enabled.py new file mode 100644 index 0000000000..f1f2577ff6 --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_oss_bucket_policy_changes_alert_enabled/sls_oss_bucket_policy_changes_alert_enabled.py @@ -0,0 +1,57 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.sls.sls_client import sls_client + + +class sls_oss_bucket_policy_changes_alert_enabled(Check): + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + found = False + + for alert in sls_client.alerts: + query_list = alert.configuration.get("queryList", []) + if not query_list: + continue + + for query_obj in query_list: + query = query_obj.get("query", "") + if ( + "PutBucketLifecycle" in query + or "PutBucketPolicy" in query + or "PutBucketCors" in query + or "PutBucketEncryption" in query + or "PutBucketReplication" in query + or "DeleteBucketPolicy" in query + or "DeleteBucketCors" in query + or "DeleteBucketLifecycle" in query + or "DeleteBucketEncryption" in query + or "DeleteBucketReplication" in query + ): + found = True + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=alert + ) + report.status = "PASS" + report.status_extended = f"SLS Alert {alert.name} is configured for OSS bucket policy changes." + report.resource_id = alert.name + report.resource_arn = alert.arn + report.region = alert.region + findings.append(report) + break + + if found: + break + + if not found: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=sls_client.provider.identity + ) + report.status = "FAIL" + report.status_extended = ( + "No SLS Alert configured for OSS bucket policy changes." + ) + report.resource_id = sls_client.audited_account + report.resource_arn = sls_client.provider.identity.identity_arn + report.region = sls_client.region + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/sls/sls_oss_permission_changes_alert_enabled/__init__.py b/prowler/providers/alibabacloud/services/sls/sls_oss_permission_changes_alert_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..97af966902 --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_oss_permission_changes_alert_enabled/sls_oss_permission_changes_alert_enabled.metadata.json @@ -0,0 +1,39 @@ +{ + "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" + ], + "ServiceName": "sls", + "SubServiceName": "", + "ResourceIdTemplate": "acs:log:region:account-id:project/project-name/alert/alert-name", + "Severity": "medium", + "ResourceType": "AlibabaCloudSLSAlert", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. Log on to the **SLS Console**\n2. Ensure **OSS logging** is enabled\n3. Select **Alerts**\n4. Ensure alert rule has been enabled for OSS permission changes", + "Url": "https://hub.prowler.com/check/sls_oss_permission_changes_alert_enabled" + } + }, + "Categories": [ + "logging" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/sls/sls_oss_permission_changes_alert_enabled/sls_oss_permission_changes_alert_enabled.py b/prowler/providers/alibabacloud/services/sls/sls_oss_permission_changes_alert_enabled/sls_oss_permission_changes_alert_enabled.py new file mode 100644 index 0000000000..bc7c5a50be --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_oss_permission_changes_alert_enabled/sls_oss_permission_changes_alert_enabled.py @@ -0,0 +1,48 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.sls.sls_client import sls_client + + +class sls_oss_permission_changes_alert_enabled(Check): + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + found = False + + for alert in sls_client.alerts: + query_list = alert.configuration.get("queryList", []) + if not query_list: + continue + + for query_obj in query_list: + query = query_obj.get("query", "") + if ("PutBucket" in query and "acl" in query) or ( + "PutObjectAcl" in query + ): + found = True + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=alert + ) + report.status = "PASS" + report.status_extended = f"SLS Alert {alert.name} is configured for OSS permission changes." + report.resource_id = alert.name + report.resource_arn = alert.arn + report.region = alert.region + findings.append(report) + break + + if found: + break + + if not found: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=sls_client.provider.identity + ) + report.status = "FAIL" + report.status_extended = ( + "No SLS Alert configured for OSS permission changes." + ) + report.resource_id = sls_client.audited_account + report.resource_arn = sls_client.provider.identity.identity_arn + report.region = sls_client.region + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/sls/sls_ram_role_changes_alert_enabled/__init__.py b/prowler/providers/alibabacloud/services/sls/sls_ram_role_changes_alert_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..aeb9abce08 --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_ram_role_changes_alert_enabled/sls_ram_role_changes_alert_enabled.metadata.json @@ -0,0 +1,39 @@ +{ + "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" + ], + "ServiceName": "sls", + "SubServiceName": "", + "ResourceIdTemplate": "acs:log:region:account-id:project/project-name/alert/alert-name", + "Severity": "medium", + "ResourceType": "AlibabaCloudSLSAlert", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. Log on to the **SLS Console**\n2. Ensure **ActionTrail** is enabled\n3. Select **Alerts**\n4. Ensure alert rule has been enabled for RAM/ResourceManager policy changes", + "Url": "https://hub.prowler.com/check/sls_ram_role_changes_alert_enabled" + } + }, + "Categories": [ + "logging" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/sls/sls_ram_role_changes_alert_enabled/sls_ram_role_changes_alert_enabled.py b/prowler/providers/alibabacloud/services/sls/sls_ram_role_changes_alert_enabled/sls_ram_role_changes_alert_enabled.py new file mode 100644 index 0000000000..c7ae22cf31 --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_ram_role_changes_alert_enabled/sls_ram_role_changes_alert_enabled.py @@ -0,0 +1,54 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.sls.sls_client import sls_client + + +class sls_ram_role_changes_alert_enabled(Check): + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + found = False + + for alert in sls_client.alerts: + # Check configuration for query + # alert.configuration is a dict. configuration['queryList'] is a list of dicts with 'query' + query_list = alert.configuration.get("queryList", []) + if not query_list: + continue + + for query_obj in query_list: + query = query_obj.get("query", "") + # Check for key terms + # Query: ("event.serviceName": ResourceManager or "event.serviceName": Ram) ... + if ( + ("ResourceManager" in query or "Ram" in query) + and "CreatePolicy" in query + and "DeletePolicy" in query + ): + found = True + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=alert + ) + report.status = "PASS" + report.status_extended = ( + f"SLS Alert {alert.name} is configured for RAM Role changes." + ) + report.resource_id = alert.name + report.resource_arn = alert.arn + report.region = alert.region + findings.append(report) + break # Found one query in this alert + + if found: + break + + if not found: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=sls_client.provider.identity + ) + report.status = "FAIL" + report.status_extended = "No SLS Alert configured for RAM Role changes." + report.resource_id = sls_client.audited_account + report.resource_arn = sls_client.provider.identity.identity_arn + report.region = sls_client.region + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/sls/sls_rds_instance_configuration_changes_alert_enabled/__init__.py b/prowler/providers/alibabacloud/services/sls/sls_rds_instance_configuration_changes_alert_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..6d53ff95bd --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_rds_instance_configuration_changes_alert_enabled/sls_rds_instance_configuration_changes_alert_enabled.metadata.json @@ -0,0 +1,39 @@ +{ + "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" + ], + "ServiceName": "sls", + "SubServiceName": "", + "ResourceIdTemplate": "acs:log:region:account-id:project/project-name/alert/alert-name", + "Severity": "medium", + "ResourceType": "AlibabaCloudSLSAlert", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. Log on to the **SLS Console**\n2. Ensure **ActionTrail** is enabled\n3. Select **Alerts**\n4. Ensure alert rule has been enabled for RDS instance configuration changes", + "Url": "https://hub.prowler.com/check/sls_rds_instance_configuration_changes_alert_enabled" + } + }, + "Categories": [ + "logging" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/sls/sls_rds_instance_configuration_changes_alert_enabled/sls_rds_instance_configuration_changes_alert_enabled.py b/prowler/providers/alibabacloud/services/sls/sls_rds_instance_configuration_changes_alert_enabled/sls_rds_instance_configuration_changes_alert_enabled.py new file mode 100644 index 0000000000..12704884fd --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_rds_instance_configuration_changes_alert_enabled/sls_rds_instance_configuration_changes_alert_enabled.py @@ -0,0 +1,72 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.sls.sls_client import sls_client + + +class sls_rds_instance_configuration_changes_alert_enabled(Check): + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + found = False + + for alert in sls_client.alerts: + query_list = alert.configuration.get("queryList", []) + if not query_list: + continue + + for query_obj in query_list: + query = query_obj.get("query", "") + if "rds" in query and ( + "ModifyHASwitchConfig" in query + or "ModifyDBInstanceHAConfig" in query + or "SwitchDBInstanceHA" in query + or "ModifyDBInstanceSpec" in query + or "MigrateSecurityIPMode" in query + or "ModifySecurityIps" in query + or "ModifyDBInstanceSSL" in query + or "MigrateToOtherZone" in query + or "UpgradeDBInstanceKernelVersion" in query + or "UpgradeDBInstanceEngineVersion" in query + or "ModifyDBInstanceMaintainTime" in query + or "ModifyDBInstanceAutoUpgradeMinorVersion" in query + or "AllocateInstancePublicConnection" in query + or "ModifyDBInstanceConnectionString" in query + or "ModifyDBInstanceNetworkExpireTime" in query + or "ReleaseInstancePublicConnection" in query + or "SwitchDBInstanceNetType" in query + or "ModifyDBInstanceNetworkType" in query + or "ModifyDTCSecurityIpHostsForSQLServer" in query + or "ModifySecurityGroupConfiguration" in query + or "CreateBackup" in query + or "ModifyBackupPolicy" in query + or "DeleteBackup" in query + or "CreateDdrInstance" in query + or "ModifyInstanceCrossBackupPolicy" in query + ): + found = True + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=alert + ) + report.status = "PASS" + report.status_extended = f"SLS Alert {alert.name} is configured for RDS instance configuration changes." + report.resource_id = alert.name + report.resource_arn = alert.arn + report.region = alert.region + findings.append(report) + break + + if found: + break + + if not found: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=sls_client.provider.identity + ) + report.status = "FAIL" + report.status_extended = ( + "No SLS Alert configured for RDS instance configuration changes." + ) + report.resource_id = sls_client.audited_account + report.resource_arn = sls_client.provider.identity.identity_arn + report.region = sls_client.region + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/sls/sls_root_account_usage_alert_enabled/__init__.py b/prowler/providers/alibabacloud/services/sls/sls_root_account_usage_alert_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..ebed34d315 --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_root_account_usage_alert_enabled/sls_root_account_usage_alert_enabled.metadata.json @@ -0,0 +1,39 @@ +{ + "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" + ], + "ServiceName": "sls", + "SubServiceName": "", + "ResourceIdTemplate": "acs:log:region:account-id:project/project-name/alert/alert-name", + "Severity": "medium", + "ResourceType": "AlibabaCloudSLSAlert", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. Log on to the **SLS Console**\n2. Ensure **ActionTrail** is enabled\n3. Select **Alerts**\n4. Ensure alert rule has been enabled for root account usage", + "Url": "https://hub.prowler.com/check/sls_root_account_usage_alert_enabled" + } + }, + "Categories": [ + "logging" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/sls/sls_root_account_usage_alert_enabled/sls_root_account_usage_alert_enabled.py b/prowler/providers/alibabacloud/services/sls/sls_root_account_usage_alert_enabled/sls_root_account_usage_alert_enabled.py new file mode 100644 index 0000000000..739ea4f4e4 --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_root_account_usage_alert_enabled/sls_root_account_usage_alert_enabled.py @@ -0,0 +1,50 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.sls.sls_client import sls_client + + +class sls_root_account_usage_alert_enabled(Check): + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + found = False + + for alert in sls_client.alerts: + query_list = alert.configuration.get("queryList", []) + if not query_list: + continue + + for query_obj in query_list: + query = query_obj.get("query", "") + if ( + "ConsoleSignin" in query + and "event.userIdentity.type" in query + and "root-account" in query + ): + found = True + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=alert + ) + report.status = "PASS" + report.status_extended = ( + f"SLS Alert {alert.name} is configured for root account usage." + ) + report.resource_id = alert.name + report.resource_arn = alert.arn + report.region = alert.region + findings.append(report) + break + + if found: + break + + if not found: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=sls_client.provider.identity + ) + report.status = "FAIL" + report.status_extended = "No SLS Alert configured for root account usage." + report.resource_id = sls_client.audited_account + report.resource_arn = sls_client.provider.identity.identity_arn + report.region = sls_client.region + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/sls/sls_security_group_changes_alert_enabled/__init__.py b/prowler/providers/alibabacloud/services/sls/sls_security_group_changes_alert_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..8f2eef2dd0 --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_security_group_changes_alert_enabled/sls_security_group_changes_alert_enabled.metadata.json @@ -0,0 +1,39 @@ +{ + "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" + ], + "ServiceName": "sls", + "SubServiceName": "", + "ResourceIdTemplate": "acs:log:region:account-id:project/project-name/alert/alert-name", + "Severity": "medium", + "ResourceType": "AlibabaCloudSLSAlert", + "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**.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. Log on to the **SLS Console**\n2. Ensure **ActionTrail** is enabled\n3. Select **Alerts**\n4. Ensure alert rule has been enabled for security group changes", + "Url": "https://hub.prowler.com/check/sls_security_group_changes_alert_enabled" + } + }, + "Categories": [ + "logging" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/sls/sls_security_group_changes_alert_enabled/sls_security_group_changes_alert_enabled.py b/prowler/providers/alibabacloud/services/sls/sls_security_group_changes_alert_enabled/sls_security_group_changes_alert_enabled.py new file mode 100644 index 0000000000..1d460b7c1a --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_security_group_changes_alert_enabled/sls_security_group_changes_alert_enabled.py @@ -0,0 +1,56 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.sls.sls_client import sls_client + + +class sls_security_group_changes_alert_enabled(Check): + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + found = False + + for alert in sls_client.alerts: + query_list = alert.configuration.get("queryList", []) + if not query_list: + continue + + for query_obj in query_list: + query = query_obj.get("query", "") + if ( + "CreateSecurityGroup" in query + or "AuthorizeSecurityGroup" in query + or "AuthorizeSecurityGroupEgress" in query + or "RevokeSecurityGroup" in query + or "RevokeSecurityGroupEgress" in query + or "JoinSecurityGroup" in query + or "LeaveSecurityGroup" in query + or "DeleteSecurityGroup" in query + or "ModifySecurityGroupPolicy" in query + ): + found = True + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=alert + ) + report.status = "PASS" + report.status_extended = f"SLS Alert {alert.name} is configured for security group changes." + report.resource_id = alert.name + report.resource_arn = alert.arn + report.region = alert.region + findings.append(report) + break + + if found: + break + + if not found: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=sls_client.provider.identity + ) + report.status = "FAIL" + report.status_extended = ( + "No SLS Alert configured for security group changes." + ) + report.resource_id = sls_client.audited_account + report.resource_arn = sls_client.provider.identity.identity_arn + report.region = sls_client.region + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/sls/sls_service.py b/prowler/providers/alibabacloud/services/sls/sls_service.py new file mode 100644 index 0000000000..16df02dfbe --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_service.py @@ -0,0 +1,137 @@ +from alibabacloud_sls20201230 import models as sls_models +from pydantic.v1 import BaseModel + +from prowler.lib.logger import logger +from prowler.providers.alibabacloud.lib.service.service import AlibabaCloudService + + +class Alert(BaseModel): + name: str + display_name: str + state: str + schedule: dict + configuration: dict + project: str + region: str + arn: str = "" + + +class LogStore(BaseModel): + name: str + project: str + retention_forever: bool + retention_days: int + region: str + arn: str = "" + + +class Sls(AlibabaCloudService): + def __init__(self, provider): + super().__init__("sls", provider) + self.alerts = [] + self.log_stores = [] + self._get_alerts() + self._get_log_stores() + + def _get_alerts(self): + for region in self.regional_clients: + client = self.regional_clients[region] + try: + # List Projects + list_project_request = sls_models.ListProjectRequest(offset=0, size=500) + projects_resp = client.list_project(list_project_request) + + if projects_resp.body and projects_resp.body.projects: + for project in projects_resp.body.projects: + project_name = project.project_name + + # List Alerts for each project + list_alert_request = sls_models.ListAlertsRequest( + offset=0, size=500 + ) + try: + alerts_resp = client.list_alerts( + project_name, list_alert_request + ) + if alerts_resp.body and alerts_resp.body.results: + for alert in alerts_resp.body.results: + self.alerts.append( + Alert( + name=alert.name, + display_name=alert.display_name, + state=alert.state, + schedule=( + alert.schedule.to_map() + if alert.schedule + else {} + ), + configuration=( + alert.configuration.to_map() + if alert.configuration + else {} + ), + project=project_name, + region=region, + arn=f"acs:log:{region}:{self.audited_account}:project/{project_name}/alert/{alert.name}", + ) + ) + except Exception as e: + logger.error( + f"{region} -- {e.__class__.__name__}[{e.__traceback__.tb_lineno}]: {e}" + ) + except Exception as error: + logger.error( + f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def _get_log_stores(self): + for region in self.regional_clients: + client = self.regional_clients[region] + try: + # List Projects + list_project_request = sls_models.ListProjectRequest(offset=0, size=500) + projects_resp = client.list_project(list_project_request) + + if projects_resp.body and projects_resp.body.projects: + for project in projects_resp.body.projects: + project_name = project.project_name + + # List LogStores for each project + list_logstores_request = sls_models.ListLogStoresRequest( + offset=0, size=500 + ) + try: + logstores_resp = client.list_log_stores( + project_name, list_logstores_request + ) + if logstores_resp.body and logstores_resp.body.logstores: + for logstore_name in logstores_resp.body.logstores: + try: + logstore_resp = client.get_log_store( + project_name, logstore_name + ) + if logstore_resp.body: + self.log_stores.append( + LogStore( + name=logstore_name, + project=project_name, + retention_forever=False, + retention_days=logstore_resp.body.ttl, + region=region, + arn=f"acs:log:{region}:{self.audited_account}:project/{project_name}/logstore/{logstore_name}", + ) + ) + except Exception as e: + logger.error( + f"{region} -- {e.__class__.__name__}[{e.__traceback__.tb_lineno}]: {e}" + ) + + except Exception as e: + logger.error( + f"{region} -- {e.__class__.__name__}[{e.__traceback__.tb_lineno}]: {e}" + ) + + except Exception as error: + logger.error( + f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) diff --git a/prowler/providers/alibabacloud/services/sls/sls_unauthorized_api_calls_alert_enabled/__init__.py b/prowler/providers/alibabacloud/services/sls/sls_unauthorized_api_calls_alert_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..0948ab4d8e --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_unauthorized_api_calls_alert_enabled/sls_unauthorized_api_calls_alert_enabled.metadata.json @@ -0,0 +1,39 @@ +{ + "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" + ], + "ServiceName": "sls", + "SubServiceName": "", + "ResourceIdTemplate": "acs:log:region:account-id:project/project-name/alert/alert-name", + "Severity": "medium", + "ResourceType": "AlibabaCloudSLSAlert", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. Log on to the **SLS Console**\n2. Ensure **ActionTrail** is enabled\n3. Select **Alerts**\n4. Ensure alert rule has been enabled for unauthorized API calls", + "Url": "https://hub.prowler.com/check/sls_unauthorized_api_calls_alert_enabled" + } + }, + "Categories": [ + "logging" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/sls/sls_unauthorized_api_calls_alert_enabled/sls_unauthorized_api_calls_alert_enabled.py b/prowler/providers/alibabacloud/services/sls/sls_unauthorized_api_calls_alert_enabled/sls_unauthorized_api_calls_alert_enabled.py new file mode 100644 index 0000000000..41a79baa11 --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_unauthorized_api_calls_alert_enabled/sls_unauthorized_api_calls_alert_enabled.py @@ -0,0 +1,56 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.sls.sls_client import sls_client + + +class sls_unauthorized_api_calls_alert_enabled(Check): + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + found = False + + for alert in sls_client.alerts: + query_list = alert.configuration.get("queryList", []) + if not query_list: + continue + + for query_obj in query_list: + query = query_obj.get("query", "") + if "ApiCall" in query and ( + "NoPermission" in query + or "Forbidden" in query + or "Forbbiden" in query + or "InvalidAccessKeyId" in query + or "InvalidSecurityToken" in query + or "SignatureDoesNotMatch" in query + or "InvalidAuthorization" in query + or "AccessForbidden" in query + or "NotAuthorized" in query + ): + found = True + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=alert + ) + report.status = "PASS" + report.status_extended = f"SLS Alert {alert.name} is configured for unauthorized API calls." + report.resource_id = alert.name + report.resource_arn = alert.arn + report.region = alert.region + findings.append(report) + break + + if found: + break + + if not found: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=sls_client.provider.identity + ) + report.status = "FAIL" + report.status_extended = ( + "No SLS Alert configured for unauthorized API calls." + ) + report.resource_id = sls_client.audited_account + report.resource_arn = sls_client.provider.identity.identity_arn + report.region = sls_client.region + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/sls/sls_vpc_changes_alert_enabled/__init__.py b/prowler/providers/alibabacloud/services/sls/sls_vpc_changes_alert_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..700d730698 --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_vpc_changes_alert_enabled/sls_vpc_changes_alert_enabled.metadata.json @@ -0,0 +1,39 @@ +{ + "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" + ], + "ServiceName": "sls", + "SubServiceName": "", + "ResourceIdTemplate": "acs:log:region:account-id:project/project-name/alert/alert-name", + "Severity": "medium", + "ResourceType": "AlibabaCloudSLSAlert", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. Log on to the **SLS Console**\n2. Ensure **ActionTrail** is enabled\n3. Select **Alerts**\n4. Ensure alert rule has been enabled for VPC changes", + "Url": "https://hub.prowler.com/check/sls_vpc_changes_alert_enabled" + } + }, + "Categories": [ + "logging" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/sls/sls_vpc_changes_alert_enabled/sls_vpc_changes_alert_enabled.py b/prowler/providers/alibabacloud/services/sls/sls_vpc_changes_alert_enabled/sls_vpc_changes_alert_enabled.py new file mode 100644 index 0000000000..5071eae2e9 --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_vpc_changes_alert_enabled/sls_vpc_changes_alert_enabled.py @@ -0,0 +1,57 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.sls.sls_client import sls_client + + +class sls_vpc_changes_alert_enabled(Check): + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + found = False + + for alert in sls_client.alerts: + query_list = alert.configuration.get("queryList", []) + if not query_list: + continue + + for query_obj in query_list: + query = query_obj.get("query", "") + if ("Ecs" in query or "Vpc" in query) and ( + "CreateVpc" in query + or "DeleteVpc" in query + or "DisableVpcClassicLink" in query + or "EnableVpcClassicLink" in query + or "DeletionProtection" in query + or "AssociateVpcCidrBlock" in query + or "UnassociateVpcCidrBlock" in query + or "RevokeInstanceFromCen" in query + or "CreateVSwitch" in query + or "DeleteVSwitch" in query + ): + found = True + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=alert + ) + report.status = "PASS" + report.status_extended = ( + f"SLS Alert {alert.name} is configured for VPC changes." + ) + report.resource_id = alert.name + report.resource_arn = alert.arn + report.region = alert.region + findings.append(report) + break + + if found: + break + + if not found: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=sls_client.provider.identity + ) + report.status = "FAIL" + report.status_extended = "No SLS Alert configured for VPC changes." + report.resource_id = sls_client.audited_account + report.resource_arn = sls_client.provider.identity.identity_arn + report.region = sls_client.region + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/sls/sls_vpc_network_route_changes_alert_enabled/__init__.py b/prowler/providers/alibabacloud/services/sls/sls_vpc_network_route_changes_alert_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..f33b5c463e --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_vpc_network_route_changes_alert_enabled/sls_vpc_network_route_changes_alert_enabled.metadata.json @@ -0,0 +1,39 @@ +{ + "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" + ], + "ServiceName": "sls", + "SubServiceName": "", + "ResourceIdTemplate": "acs:log:region:account-id:project/project-name/alert/alert-name", + "Severity": "medium", + "ResourceType": "AlibabaCloudSLSAlert", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. Log on to the **SLS Console**\n2. Ensure **ActionTrail** is enabled\n3. Select **Alerts**\n4. Ensure alert rule has been enabled for VPC network route changes", + "Url": "https://hub.prowler.com/check/sls_vpc_network_route_changes_alert_enabled" + } + }, + "Categories": [ + "logging" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/sls/sls_vpc_network_route_changes_alert_enabled/sls_vpc_network_route_changes_alert_enabled.py b/prowler/providers/alibabacloud/services/sls/sls_vpc_network_route_changes_alert_enabled/sls_vpc_network_route_changes_alert_enabled.py new file mode 100644 index 0000000000..16cd0309fb --- /dev/null +++ b/prowler/providers/alibabacloud/services/sls/sls_vpc_network_route_changes_alert_enabled/sls_vpc_network_route_changes_alert_enabled.py @@ -0,0 +1,52 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.sls.sls_client import sls_client + + +class sls_vpc_network_route_changes_alert_enabled(Check): + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + found = False + + for alert in sls_client.alerts: + query_list = alert.configuration.get("queryList", []) + if not query_list: + continue + + for query_obj in query_list: + query = query_obj.get("query", "") + if ("Ecs" in query or "Vpc" in query) and ( + "CreateRouteEntry" in query + or "DeleteRouteEntry" in query + or "ModifyRouteEntry" in query + or "AssociateRouteTable" in query + or "UnassociateRouteTable" in query + ): + found = True + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=alert + ) + report.status = "PASS" + report.status_extended = f"SLS Alert {alert.name} is configured for VPC network route changes." + report.resource_id = alert.name + report.resource_arn = alert.arn + report.region = alert.region + findings.append(report) + break + + if found: + break + + if not found: + report = CheckReportAlibabaCloud( + metadata=self.metadata(), resource=sls_client.provider.identity + ) + report.status = "FAIL" + report.status_extended = ( + "No SLS Alert configured for VPC network route changes." + ) + report.resource_id = sls_client.audited_account + report.resource_arn = sls_client.provider.identity.identity_arn + report.region = sls_client.region + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/vpc/__init__.py b/prowler/providers/alibabacloud/services/vpc/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/alibabacloud/services/vpc/vpc_client.py b/prowler/providers/alibabacloud/services/vpc/vpc_client.py new file mode 100644 index 0000000000..a65a4d8f62 --- /dev/null +++ b/prowler/providers/alibabacloud/services/vpc/vpc_client.py @@ -0,0 +1,4 @@ +from prowler.providers.alibabacloud.services.vpc.vpc_service import VPC +from prowler.providers.common.provider import Provider + +vpc_client = VPC(Provider.get_global_provider()) diff --git a/prowler/providers/alibabacloud/services/vpc/vpc_flow_logs_enabled/__init__.py b/prowler/providers/alibabacloud/services/vpc/vpc_flow_logs_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..5904e47b1a --- /dev/null +++ b/prowler/providers/alibabacloud/services/vpc/vpc_flow_logs_enabled/vpc_flow_logs_enabled.metadata.json @@ -0,0 +1,39 @@ +{ + "Provider": "alibabacloud", + "CheckID": "vpc_flow_logs_enabled", + "CheckTitle": "VPC flow logging is enabled in all VPCs", + "CheckType": [ + "Suspicious network connection", + "Cloud threat detection" + ], + "ServiceName": "vpc", + "SubServiceName": "", + "ResourceIdTemplate": "acs:vpc:region:account-id:vpc/{vpc-id}", + "Severity": "medium", + "ResourceType": "AlibabaCloudVPC", + "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.", + "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" + ], + "Remediation": { + "Code": { + "CLI": "aliyun vpc CreateFlowLog --ResourceId --ResourceType VPC --FlowLogName --LogStoreName --ProjectName ", + "NativeIaC": "", + "Other": "", + "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", + "Url": "https://hub.prowler.com/check/vpc_flow_logs_enabled" + } + }, + "Categories": [ + "logging" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/alibabacloud/services/vpc/vpc_flow_logs_enabled/vpc_flow_logs_enabled.py b/prowler/providers/alibabacloud/services/vpc/vpc_flow_logs_enabled/vpc_flow_logs_enabled.py new file mode 100644 index 0000000000..e6d53eecef --- /dev/null +++ b/prowler/providers/alibabacloud/services/vpc/vpc_flow_logs_enabled/vpc_flow_logs_enabled.py @@ -0,0 +1,30 @@ +from prowler.lib.check.models import Check, CheckReportAlibabaCloud +from prowler.providers.alibabacloud.services.vpc.vpc_client import vpc_client + + +class vpc_flow_logs_enabled(Check): + """Check if VPC flow logging is enabled in all VPCs.""" + + def execute(self) -> list[CheckReportAlibabaCloud]: + findings = [] + + for vpc in vpc_client.vpcs.values(): + report = CheckReportAlibabaCloud(metadata=self.metadata(), resource=vpc) + report.region = vpc.region + report.resource_id = vpc.id + report.resource_arn = ( + f"acs:vpc:{vpc.region}:{vpc_client.audited_account}:vpc/{vpc.id}" + ) + + if vpc.flow_log_enabled: + report.status = "PASS" + report.status_extended = ( + f"VPC {vpc.name if vpc.name else vpc.id} has flow logs enabled." + ) + else: + report.status = "FAIL" + report.status_extended = f"VPC {vpc.name if vpc.name else vpc.id} does not have flow logs enabled." + + findings.append(report) + + return findings diff --git a/prowler/providers/alibabacloud/services/vpc/vpc_service.py b/prowler/providers/alibabacloud/services/vpc/vpc_service.py new file mode 100644 index 0000000000..9ed00fd9ea --- /dev/null +++ b/prowler/providers/alibabacloud/services/vpc/vpc_service.py @@ -0,0 +1,102 @@ +from datetime import datetime +from typing import Optional + +from alibabacloud_vpc20160428 import models as vpc_models +from pydantic.v1 import BaseModel + +from prowler.lib.logger import logger +from prowler.lib.scan_filters.scan_filters import is_resource_filtered +from prowler.providers.alibabacloud.lib.service.service import AlibabaCloudService + + +class VPC(AlibabaCloudService): + """ + VPC (Virtual Private Cloud) service class for Alibaba Cloud. + + This class provides methods to interact with Alibaba Cloud VPC service + to retrieve VPCs, flow logs, etc. + """ + + def __init__(self, provider): + # Call AlibabaCloudService's __init__ + super().__init__(__class__.__name__, provider, global_service=False) + + # Fetch VPC resources + self.vpcs = {} + self.__threading_call__(self._describe_vpcs) + self._describe_flow_logs() + + def _describe_vpcs(self, regional_client): + """List all VPCs in the region.""" + region = getattr(regional_client, "region", "unknown") + logger.info(f"VPC - Describing VPCs in {region}...") + + try: + request = vpc_models.DescribeVpcsRequest() + response = regional_client.describe_vpcs(request) + + if response and response.body and response.body.vpcs: + for vpc_data in response.body.vpcs.vpc: + if not self.audit_resources or is_resource_filtered( + vpc_data.vpc_id, self.audit_resources + ): + vpc_id = vpc_data.vpc_id + self.vpcs[vpc_id] = VPCs( + id=vpc_id, + name=getattr(vpc_data, "vpc_name", vpc_id), + region=region, + cidr_block=getattr(vpc_data, "cidr_block", ""), + description=getattr(vpc_data, "description", ""), + create_time=getattr(vpc_data, "creation_time", None), + is_default=getattr(vpc_data, "is_default", False), + flow_log_enabled=False, # Will be updated in _describe_flow_logs + ) + + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def _describe_flow_logs(self): + """Get flow logs for all VPCs.""" + logger.info("VPC - Describing Flow Logs...") + + for vpc_id, vpc in self.vpcs.items(): + try: + regional_client = self.regional_clients.get(vpc.region) + if not regional_client: + continue + + request = vpc_models.DescribeFlowLogsRequest() + request.resource_id = vpc_id + request.resource_type = "VPC" + response = regional_client.describe_flow_logs(request) + + if response and response.body and response.body.flow_logs: + flow_logs = response.body.flow_logs.flow_log + if flow_logs: + # Check if any flow log is active + for flow_log in flow_logs: + status = getattr(flow_log, "status", "") + if status == "Active": + vpc.flow_log_enabled = True + break + + except Exception as error: + logger.error( + f"{vpc.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + +# Models for VPC service +class VPCs(BaseModel): + """VPC model.""" + + id: str + name: str + region: str + cidr_block: str + description: str = "" + create_time: Optional[datetime] = None + is_default: bool = False + flow_log_enabled: bool = False diff --git a/prowler/providers/common/provider.py b/prowler/providers/common/provider.py index c19d0c468e..16758ed940 100644 --- a/prowler/providers/common/provider.py +++ b/prowler/providers/common/provider.py @@ -286,6 +286,18 @@ class Provider(ABC): fixer_config=fixer_config, use_instance_principal=arguments.use_instance_principal, ) + elif "alibabacloud" in provider_class_name.lower(): + provider_class( + role_arn=arguments.role_arn, + role_session_name=arguments.role_session_name, + ecs_ram_role=arguments.ecs_ram_role, + oidc_role_arn=arguments.oidc_role_arn, + credentials_uri=arguments.credentials_uri, + regions=arguments.regions, + config_path=arguments.config_file, + mutelist_path=arguments.mutelist_file, + fixer_config=fixer_config, + ) except TypeError as error: logger.critical( diff --git a/pyproject.toml b/pyproject.toml index 56aeb194e1..5171cfedac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,8 +68,20 @@ dependencies = [ "tabulate==0.9.0", "tzlocal==5.3.1", "py-iam-expand==0.1.0", - "h2 (==4.3.0)", - "oci==2.160.3" + "h2==4.3.0", + "oci==2.160.3", + "alibabacloud_credentials==1.0.3", + "alibabacloud_ram20150501==1.2.0", + "alibabacloud_tea_openapi==0.4.1", + "alibabacloud_sts20150401==1.1.6", + "alibabacloud_vpc20160428==6.13.0", + "alibabacloud_ecs20140526==7.2.5", + "alibabacloud_sas20181203==6.1.0", + "alibabacloud_oss20190517==1.0.6", + "alibabacloud_actiontrail20200706==2.4.1", + "alibabacloud_cs20151215==6.1.0", + "alibabacloud-rds20140815==12.0.0", + "alibabacloud-sls20201230==5.9.0" ] description = "Prowler is an Open Source security tool to perform AWS, GCP and Azure security best practices assessments, audits, incident response, continuous monitoring, hardening and forensics readiness. It contains hundreds of controls covering CIS, NIST 800, NIST CSF, CISA, RBI, FedRAMP, PCI-DSS, GDPR, HIPAA, FFIEC, SOC2, GXP, AWS Well-Architected Framework Security Pillar, AWS Foundational Technical Review (FTR), ENS (Spanish National Security Scheme) and your custom security frameworks." license = "Apache-2.0" diff --git a/tests/lib/cli/parser_test.py b/tests/lib/cli/parser_test.py index 0945d754c6..dee0f8caab 100644 --- a/tests/lib/cli/parser_test.py +++ b/tests/lib/cli/parser_test.py @@ -17,7 +17,7 @@ prowler_command = "prowler" # capsys # https://docs.pytest.org/en/7.1.x/how-to/capture-stdout-stderr.html -prowler_default_usage_error = "usage: prowler [-h] [--version] {aws,azure,gcp,kubernetes,m365,github,nhn,mongodbatlas,oraclecloud,dashboard,iac} ..." +prowler_default_usage_error = "usage: prowler [-h] [--version] {aws,azure,gcp,kubernetes,m365,github,nhn,mongodbatlas,oraclecloud,alibabacloud,dashboard,iac} ..." def mock_get_available_providers(): @@ -32,6 +32,7 @@ def mock_get_available_providers(): "nhn", "mongodbatlas", "oraclecloud", + "alibabacloud", ] diff --git a/tests/providers/alibabacloud/__init__.py b/tests/providers/alibabacloud/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/alibabacloud_fixtures.py b/tests/providers/alibabacloud/alibabacloud_fixtures.py new file mode 100644 index 0000000000..92f3445af2 --- /dev/null +++ b/tests/providers/alibabacloud/alibabacloud_fixtures.py @@ -0,0 +1,56 @@ +from unittest.mock import MagicMock + +from prowler.providers.alibabacloud.models import AlibabaCloudIdentityInfo +from prowler.providers.common.models import Audit_Metadata + + +def set_mocked_alibabacloud_provider( + account_id: str = "1234567890", + account_name: str = "test-account", + user_id: str = "123456", + user_name: str = "test-user", + region: str = "cn-hangzhou", +) -> MagicMock: + """Create a mocked Alibaba Cloud provider for service unit tests.""" + provider = MagicMock() + provider.type = "alibabacloud" + + provider.identity = AlibabaCloudIdentityInfo( + account_id=account_id, + account_name=account_name, + user_id=user_id, + user_name=user_name, + identity_arn=f"acs:ram::{account_id}:user/{user_name}", + profile="default", + profile_region=region, + audited_regions={region}, + is_root=False, + ) + + provider.audit_metadata = Audit_Metadata( + services_scanned=0, + expected_checks=[], + completed_checks=0, + audit_progress=0, + ) + provider.audit_resources = [] + provider.audit_config = {} + provider.fixer_config = {} + provider.mutelist = MagicMock() + provider.mutelist.is_muted = MagicMock(return_value=False) + + # Session/client mocks + provider.session = MagicMock() + provider.session.client = MagicMock(return_value=MagicMock(region=region)) + + # Region helpers + provider.get_default_region = MagicMock(return_value=region) + + def mock_generate_regional_clients(service_name): + return {region: MagicMock(region=region)} + + provider.generate_regional_clients = MagicMock( + side_effect=mock_generate_regional_clients + ) + + return provider diff --git a/tests/providers/alibabacloud/conftest.py b/tests/providers/alibabacloud/conftest.py new file mode 100644 index 0000000000..37fad33488 --- /dev/null +++ b/tests/providers/alibabacloud/conftest.py @@ -0,0 +1,41 @@ +""" +Pytest configuration for Alibaba Cloud provider tests. + +Mocks Alibaba Cloud SDK modules to avoid import issues when the real +dependencies are not installed in the test environment. +""" + +import sys +from unittest.mock import MagicMock + +# Mock Alibaba Cloud SDK modules so imports in provider/service code succeed +MOCKED_MODULES = [ + "alibabacloud_credentials", + "alibabacloud_credentials.client", + "alibabacloud_credentials.models", + "alibabacloud_sts20150401", + "alibabacloud_sts20150401.client", + "alibabacloud_tea_openapi", + "alibabacloud_tea_openapi.models", + "alibabacloud_ram20150501", + "alibabacloud_ram20150501.client", + "alibabacloud_vpc20160428", + "alibabacloud_vpc20160428.client", + "alibabacloud_sas20181203", + "alibabacloud_sas20181203.client", + "alibabacloud_ecs20140526", + "alibabacloud_ecs20140526.client", + "alibabacloud_oss20190517", + "alibabacloud_oss20190517.client", + "alibabacloud_actiontrail20200706", + "alibabacloud_actiontrail20200706.client", + "alibabacloud_cs20151215", + "alibabacloud_cs20151215.client", + "alibabacloud_rds20140815", + "alibabacloud_rds20140815.client", + "alibabacloud_sls20201230", + "alibabacloud_sls20201230.client", +] + +for module_name in MOCKED_MODULES: + sys.modules.setdefault(module_name, MagicMock()) diff --git a/tests/providers/alibabacloud/lib/__init__.py b/tests/providers/alibabacloud/lib/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/lib/mutelist/__init__.py b/tests/providers/alibabacloud/lib/mutelist/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/lib/mutelist/alibabacloud_mutelist_test.py b/tests/providers/alibabacloud/lib/mutelist/alibabacloud_mutelist_test.py new file mode 100644 index 0000000000..7bb5bc17ec --- /dev/null +++ b/tests/providers/alibabacloud/lib/mutelist/alibabacloud_mutelist_test.py @@ -0,0 +1,61 @@ +from unittest.mock import MagicMock + +import yaml + +from prowler.providers.alibabacloud.lib.mutelist.mutelist import AlibabaCloudMutelist + +MUTELIST_FIXTURE_PATH = ( + "tests/providers/alibabacloud/lib/mutelist/fixtures/alibabacloud_mutelist.yaml" +) + + +class TestAlibabaCloudMutelist: + def test_get_mutelist_file_from_local_file(self): + mutelist = AlibabaCloudMutelist( + mutelist_path=MUTELIST_FIXTURE_PATH, account_id="1234567890" + ) + + with open(MUTELIST_FIXTURE_PATH) as f: + mutelist_fixture = yaml.safe_load(f)["Mutelist"] + + assert mutelist.mutelist == mutelist_fixture + assert mutelist.mutelist_file_path == MUTELIST_FIXTURE_PATH + + def test_get_mutelist_file_from_local_file_non_existent(self): + mutelist_path = "tests/providers/alibabacloud/lib/mutelist/fixtures/not_present" + mutelist = AlibabaCloudMutelist( + mutelist_path=mutelist_path, account_id="1234567890" + ) + + assert mutelist.mutelist == {} + assert mutelist.mutelist_file_path == mutelist_path + + def test_is_finding_muted(self): + mutelist = AlibabaCloudMutelist( + mutelist_path=MUTELIST_FIXTURE_PATH, account_id="1234567890" + ) + + finding = MagicMock() + finding.check_metadata = MagicMock() + finding.check_metadata.CheckID = "test_check" + finding.status = "FAIL" + finding.resource_id = "test_resource" + finding.region = "cn-hangzhou" + finding.resource_tags = [{"Key": "Environment", "Value": "Prod"}] + + assert mutelist.is_finding_muted(finding, account_id="1234567890") + + def test_is_finding_not_muted_with_different_resource(self): + mutelist = AlibabaCloudMutelist( + mutelist_path=MUTELIST_FIXTURE_PATH, account_id="1234567890" + ) + + finding = MagicMock() + finding.check_metadata = MagicMock() + finding.check_metadata.CheckID = "test_check" + finding.status = "FAIL" + finding.resource_id = "another_resource" + finding.region = "cn-hangzhou" + finding.resource_tags = [{"Key": "Environment", "Value": "Prod"}] + + assert mutelist.is_finding_muted(finding, account_id="1234567890") is False diff --git a/tests/providers/alibabacloud/lib/mutelist/fixtures/alibabacloud_mutelist.yaml b/tests/providers/alibabacloud/lib/mutelist/fixtures/alibabacloud_mutelist.yaml new file mode 100644 index 0000000000..e323faa8d5 --- /dev/null +++ b/tests/providers/alibabacloud/lib/mutelist/fixtures/alibabacloud_mutelist.yaml @@ -0,0 +1,11 @@ +Mutelist: + Accounts: + "*": + Checks: + test_check: + Regions: + - "*" + Resources: + - "test_resource" + Tags: + - "Environment=Prod" diff --git a/tests/providers/alibabacloud/services/__init__.py b/tests/providers/alibabacloud/services/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/actiontrail/__init__.py b/tests/providers/alibabacloud/services/actiontrail/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/actiontrail/actiontrail_multi_region_enabled/__init__.py b/tests/providers/alibabacloud/services/actiontrail/actiontrail_multi_region_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/actiontrail/actiontrail_multi_region_enabled/actiontrail_multi_region_enabled_test.py b/tests/providers/alibabacloud/services/actiontrail/actiontrail_multi_region_enabled/actiontrail_multi_region_enabled_test.py new file mode 100644 index 0000000000..45fb74f48b --- /dev/null +++ b/tests/providers/alibabacloud/services/actiontrail/actiontrail_multi_region_enabled/actiontrail_multi_region_enabled_test.py @@ -0,0 +1,78 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestActionTrailMultiRegionEnabled: + def test_no_multi_region_trail_fails(self): + actiontrail_client = mock.MagicMock() + actiontrail_client.trails = {} + actiontrail_client.region = "cn-hangzhou" + actiontrail_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.actiontrail.actiontrail_multi_region_enabled.actiontrail_multi_region_enabled.actiontrail_client", + new=actiontrail_client, + ), + ): + from prowler.providers.alibabacloud.services.actiontrail.actiontrail_multi_region_enabled.actiontrail_multi_region_enabled import ( + actiontrail_multi_region_enabled, + ) + + check = actiontrail_multi_region_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert "not configured" in result[0].status_extended + + def test_enabled_multi_region_trail_passes(self): + actiontrail_client = mock.MagicMock() + actiontrail_client.region = "cn-hangzhou" + actiontrail_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.actiontrail.actiontrail_multi_region_enabled.actiontrail_multi_region_enabled.actiontrail_client", + new=actiontrail_client, + ), + ): + from prowler.providers.alibabacloud.services.actiontrail.actiontrail_multi_region_enabled.actiontrail_multi_region_enabled import ( + actiontrail_multi_region_enabled, + ) + from prowler.providers.alibabacloud.services.actiontrail.actiontrail_service import ( + Trail, + ) + + trail = Trail( + arn="acs:actiontrail::1234567890:trail/multi", + name="multi", + home_region="cn-hangzhou", + trail_region="All", + status="Enable", + oss_bucket_name="logs", + oss_bucket_location="cn-hangzhou", + sls_project_arn="", + event_rw="All", + ) + + actiontrail_client.trails = {trail.arn: trail} + + check = actiontrail_multi_region_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert "multi-region trail(s)" in result[0].status_extended + assert "multi" in result[0].status_extended diff --git a/tests/providers/alibabacloud/services/actiontrail/actiontrail_oss_bucket_not_publicly_accessible/__init__.py b/tests/providers/alibabacloud/services/actiontrail/actiontrail_oss_bucket_not_publicly_accessible/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/actiontrail/actiontrail_oss_bucket_not_publicly_accessible/actiontrail_oss_bucket_not_publicly_accessible_test.py b/tests/providers/alibabacloud/services/actiontrail/actiontrail_oss_bucket_not_publicly_accessible/actiontrail_oss_bucket_not_publicly_accessible_test.py new file mode 100644 index 0000000000..3b4e535518 --- /dev/null +++ b/tests/providers/alibabacloud/services/actiontrail/actiontrail_oss_bucket_not_publicly_accessible/actiontrail_oss_bucket_not_publicly_accessible_test.py @@ -0,0 +1,156 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestActionTrailOssBucketNotPubliclyAccessible: + def test_bucket_missing_marks_manual(self): + actiontrail_client = mock.MagicMock() + actiontrail_client.audited_account = "1234567890" + actiontrail_client.trails = {} + missing_trail = mock.MagicMock() + missing_trail.name = "trail-arn" + missing_trail.oss_bucket_name = "missing-bucket" + missing_trail.home_region = "cn-hangzhou" + actiontrail_client.trails["trail-arn"] = missing_trail + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.actiontrail.actiontrail_oss_bucket_not_publicly_accessible.actiontrail_oss_bucket_not_publicly_accessible.actiontrail_client", + new=actiontrail_client, + ), + mock.patch( + "prowler.providers.alibabacloud.services.actiontrail.actiontrail_oss_bucket_not_publicly_accessible.actiontrail_oss_bucket_not_publicly_accessible.oss_client", + new=mock.MagicMock(buckets={}), + ), + ): + from prowler.providers.alibabacloud.services.actiontrail.actiontrail_oss_bucket_not_publicly_accessible.actiontrail_oss_bucket_not_publicly_accessible import ( + actiontrail_oss_bucket_not_publicly_accessible, + ) + + check = actiontrail_oss_bucket_not_publicly_accessible() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "MANUAL" + assert "could not be found" in result[0].status_extended + + def test_public_bucket_fails(self): + actiontrail_client = mock.MagicMock() + actiontrail_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.actiontrail.actiontrail_oss_bucket_not_publicly_accessible.actiontrail_oss_bucket_not_publicly_accessible.actiontrail_client", + new=actiontrail_client, + ), + ): + from prowler.providers.alibabacloud.services.actiontrail.actiontrail_oss_bucket_not_publicly_accessible.actiontrail_oss_bucket_not_publicly_accessible import ( + actiontrail_oss_bucket_not_publicly_accessible, + ) + from prowler.providers.alibabacloud.services.actiontrail.actiontrail_service import ( + Trail, + ) + from prowler.providers.alibabacloud.services.oss.oss_service import Bucket + + trail = Trail( + arn="acs:actiontrail::1234567890:trail/trail1", + name="trail1", + home_region="cn-hangzhou", + trail_region="All", + status="Enable", + oss_bucket_name="public-bucket", + oss_bucket_location="cn-hangzhou", + sls_project_arn="", + event_rw="All", + ) + actiontrail_client.trails = {trail.arn: trail} + + bucket = Bucket( + arn="acs:oss::1234567890:public-bucket", + name="public-bucket", + region="cn-hangzhou", + acl="public-read", + policy={}, + ) + + oss_client = mock.MagicMock() + oss_client.buckets = {bucket.arn: bucket} + + with mock.patch( + "prowler.providers.alibabacloud.services.actiontrail.actiontrail_oss_bucket_not_publicly_accessible.actiontrail_oss_bucket_not_publicly_accessible.oss_client", + new=oss_client, + ): + check = actiontrail_oss_bucket_not_publicly_accessible() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert "publicly accessible" in result[0].status_extended + + def test_private_bucket_passes(self): + actiontrail_client = mock.MagicMock() + actiontrail_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.actiontrail.actiontrail_oss_bucket_not_publicly_accessible.actiontrail_oss_bucket_not_publicly_accessible.actiontrail_client", + new=actiontrail_client, + ), + ): + from prowler.providers.alibabacloud.services.actiontrail.actiontrail_oss_bucket_not_publicly_accessible.actiontrail_oss_bucket_not_publicly_accessible import ( + actiontrail_oss_bucket_not_publicly_accessible, + ) + from prowler.providers.alibabacloud.services.actiontrail.actiontrail_service import ( + Trail, + ) + from prowler.providers.alibabacloud.services.oss.oss_service import Bucket + + trail = Trail( + arn="acs:actiontrail::1234567890:trail/trail1", + name="trail1", + home_region="cn-hangzhou", + trail_region="All", + status="Enable", + oss_bucket_name="private-bucket", + oss_bucket_location="cn-hangzhou", + sls_project_arn="", + event_rw="All", + ) + actiontrail_client.trails = {trail.arn: trail} + + bucket = Bucket( + arn="acs:oss::1234567890:private-bucket", + name="private-bucket", + region="cn-hangzhou", + acl="private", + policy={}, + ) + + oss_client = mock.MagicMock() + oss_client.buckets = {bucket.arn: bucket} + + with mock.patch( + "prowler.providers.alibabacloud.services.actiontrail.actiontrail_oss_bucket_not_publicly_accessible.actiontrail_oss_bucket_not_publicly_accessible.oss_client", + new=oss_client, + ): + check = actiontrail_oss_bucket_not_publicly_accessible() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert "not publicly accessible" in result[0].status_extended diff --git a/tests/providers/alibabacloud/services/actiontrail/actiontrail_service_test.py b/tests/providers/alibabacloud/services/actiontrail/actiontrail_service_test.py new file mode 100644 index 0000000000..be652182a1 --- /dev/null +++ b/tests/providers/alibabacloud/services/actiontrail/actiontrail_service_test.py @@ -0,0 +1,26 @@ +from unittest.mock import patch + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestActionTrailService: + def test_service(self): + alibabacloud_provider = set_mocked_alibabacloud_provider() + + with patch( + "prowler.providers.alibabacloud.services.actiontrail.actiontrail_service.ActionTrail.__init__", + return_value=None, + ): + from prowler.providers.alibabacloud.services.actiontrail.actiontrail_service import ( + ActionTrail, + ) + + actiontrail_client = ActionTrail(alibabacloud_provider) + actiontrail_client.service = "actiontrail" + actiontrail_client.provider = alibabacloud_provider + actiontrail_client.regional_clients = {} + + assert actiontrail_client.service == "actiontrail" + assert actiontrail_client.provider == alibabacloud_provider diff --git a/tests/providers/alibabacloud/services/cs/__init__.py b/tests/providers/alibabacloud/services/cs/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/cs/cs_kubernetes_cloudmonitor_enabled/__init__.py b/tests/providers/alibabacloud/services/cs/cs_kubernetes_cloudmonitor_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/cs/cs_kubernetes_cloudmonitor_enabled/cs_kubernetes_cloudmonitor_enabled_test.py b/tests/providers/alibabacloud/services/cs/cs_kubernetes_cloudmonitor_enabled/cs_kubernetes_cloudmonitor_enabled_test.py new file mode 100644 index 0000000000..d89ea08628 --- /dev/null +++ b/tests/providers/alibabacloud/services/cs/cs_kubernetes_cloudmonitor_enabled/cs_kubernetes_cloudmonitor_enabled_test.py @@ -0,0 +1,81 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestCSKubernetesCloudmonitorEnabled: + def test_cloudmonitor_disabled_fails(self): + cs_client = mock.MagicMock() + cs_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.cs.cs_kubernetes_cloudmonitor_enabled.cs_kubernetes_cloudmonitor_enabled.cs_client", + new=cs_client, + ), + ): + from prowler.providers.alibabacloud.services.cs.cs_kubernetes_cloudmonitor_enabled.cs_kubernetes_cloudmonitor_enabled import ( + cs_kubernetes_cloudmonitor_enabled, + ) + from prowler.providers.alibabacloud.services.cs.cs_service import Cluster + + cluster = Cluster( + id="c1", + name="c1", + region="cn-hangzhou", + cluster_type="k8s", + state="running", + cloudmonitor_enabled=False, + ) + cs_client.clusters = [cluster] + + check = cs_kubernetes_cloudmonitor_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + "does not have CloudMonitor Agent enabled" in result[0].status_extended + ) + + def test_cloudmonitor_enabled_passes(self): + cs_client = mock.MagicMock() + cs_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.cs.cs_kubernetes_cloudmonitor_enabled.cs_kubernetes_cloudmonitor_enabled.cs_client", + new=cs_client, + ), + ): + from prowler.providers.alibabacloud.services.cs.cs_kubernetes_cloudmonitor_enabled.cs_kubernetes_cloudmonitor_enabled import ( + cs_kubernetes_cloudmonitor_enabled, + ) + from prowler.providers.alibabacloud.services.cs.cs_service import Cluster + + cluster = Cluster( + id="c2", + name="c2", + region="cn-hangzhou", + cluster_type="k8s", + state="running", + cloudmonitor_enabled=True, + ) + cs_client.clusters = [cluster] + + check = cs_kubernetes_cloudmonitor_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert "CloudMonitor Agent enabled" in result[0].status_extended diff --git a/tests/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_recent/__init__.py b/tests/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_recent/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_recent/cs_kubernetes_cluster_check_recent_test.py b/tests/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_recent/cs_kubernetes_cluster_check_recent_test.py new file mode 100644 index 0000000000..7c163a74fd --- /dev/null +++ b/tests/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_recent/cs_kubernetes_cluster_check_recent_test.py @@ -0,0 +1,80 @@ +from datetime import datetime, timedelta, timezone +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestCSKubernetesClusterCheckRecent: + def test_cluster_check_stale_fails(self): + cs_client = mock.MagicMock() + cs_client.audited_account = "1234567890" + cs_client.audit_config = {"max_cluster_check_days": 7} + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.cs.cs_kubernetes_cluster_check_recent.cs_kubernetes_cluster_check_recent.cs_client", + new=cs_client, + ), + ): + from prowler.providers.alibabacloud.services.cs.cs_kubernetes_cluster_check_recent.cs_kubernetes_cluster_check_recent import ( + cs_kubernetes_cluster_check_recent, + ) + from prowler.providers.alibabacloud.services.cs.cs_service import Cluster + + cluster = Cluster( + id="c1", + name="c1", + region="cn-hangzhou", + cluster_type="k8s", + state="running", + last_check_time=datetime.now(timezone.utc) - timedelta(days=10), + ) + cs_client.clusters = [cluster] + + check = cs_kubernetes_cluster_check_recent() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + + def test_cluster_check_recent_passes(self): + cs_client = mock.MagicMock() + cs_client.audited_account = "1234567890" + cs_client.audit_config = {"max_cluster_check_days": 7} + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.cs.cs_kubernetes_cluster_check_recent.cs_kubernetes_cluster_check_recent.cs_client", + new=cs_client, + ), + ): + from prowler.providers.alibabacloud.services.cs.cs_kubernetes_cluster_check_recent.cs_kubernetes_cluster_check_recent import ( + cs_kubernetes_cluster_check_recent, + ) + from prowler.providers.alibabacloud.services.cs.cs_service import Cluster + + cluster = Cluster( + id="c2", + name="c2", + region="cn-hangzhou", + cluster_type="k8s", + state="running", + last_check_time=datetime.now(timezone.utc) - timedelta(days=3), + ) + cs_client.clusters = [cluster] + + check = cs_kubernetes_cluster_check_recent() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" diff --git a/tests/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_weekly/__init__.py b/tests/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_weekly/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_weekly/cs_kubernetes_cluster_check_weekly_test.py b/tests/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_weekly/cs_kubernetes_cluster_check_weekly_test.py new file mode 100644 index 0000000000..b32281311b --- /dev/null +++ b/tests/providers/alibabacloud/services/cs/cs_kubernetes_cluster_check_weekly/cs_kubernetes_cluster_check_weekly_test.py @@ -0,0 +1,80 @@ +from datetime import datetime, timedelta, timezone +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestCSKubernetesClusterCheckWeekly: + def test_cluster_check_stale_fails(self): + cs_client = mock.MagicMock() + cs_client.audited_account = "1234567890" + cs_client.audit_config = {"max_cluster_check_days": 7} + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.cs.cs_kubernetes_cluster_check_weekly.cs_kubernetes_cluster_check_weekly.cs_client", + new=cs_client, + ), + ): + from prowler.providers.alibabacloud.services.cs.cs_kubernetes_cluster_check_weekly.cs_kubernetes_cluster_check_weekly import ( + cs_kubernetes_cluster_check_weekly, + ) + from prowler.providers.alibabacloud.services.cs.cs_service import Cluster + + cluster = Cluster( + id="c1", + name="c1", + region="cn-hangzhou", + cluster_type="k8s", + state="running", + last_check_time=datetime.now(timezone.utc) - timedelta(days=15), + ) + cs_client.clusters = [cluster] + + check = cs_kubernetes_cluster_check_weekly() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + + def test_cluster_check_recent_passes(self): + cs_client = mock.MagicMock() + cs_client.audited_account = "1234567890" + cs_client.audit_config = {"max_cluster_check_days": 7} + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.cs.cs_kubernetes_cluster_check_weekly.cs_kubernetes_cluster_check_weekly.cs_client", + new=cs_client, + ), + ): + from prowler.providers.alibabacloud.services.cs.cs_kubernetes_cluster_check_weekly.cs_kubernetes_cluster_check_weekly import ( + cs_kubernetes_cluster_check_weekly, + ) + from prowler.providers.alibabacloud.services.cs.cs_service import Cluster + + cluster = Cluster( + id="c2", + name="c2", + region="cn-hangzhou", + cluster_type="k8s", + state="running", + last_check_time=datetime.now(timezone.utc) - timedelta(days=2), + ) + cs_client.clusters = [cluster] + + check = cs_kubernetes_cluster_check_weekly() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" diff --git a/tests/providers/alibabacloud/services/cs/cs_kubernetes_dashboard_disabled/__init__.py b/tests/providers/alibabacloud/services/cs/cs_kubernetes_dashboard_disabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/cs/cs_kubernetes_dashboard_disabled/cs_kubernetes_dashboard_disabled_test.py b/tests/providers/alibabacloud/services/cs/cs_kubernetes_dashboard_disabled/cs_kubernetes_dashboard_disabled_test.py new file mode 100644 index 0000000000..f520ae3d30 --- /dev/null +++ b/tests/providers/alibabacloud/services/cs/cs_kubernetes_dashboard_disabled/cs_kubernetes_dashboard_disabled_test.py @@ -0,0 +1,82 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestCSKubernetesDashboardDisabled: + def test_dashboard_enabled_fails(self): + cs_client = mock.MagicMock() + cs_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.cs.cs_kubernetes_dashboard_disabled.cs_kubernetes_dashboard_disabled.cs_client", + new=cs_client, + ), + ): + from prowler.providers.alibabacloud.services.cs.cs_kubernetes_dashboard_disabled.cs_kubernetes_dashboard_disabled import ( + cs_kubernetes_dashboard_disabled, + ) + from prowler.providers.alibabacloud.services.cs.cs_service import Cluster + + cluster = Cluster( + id="c1", + name="c1", + region="cn-hangzhou", + cluster_type="k8s", + state="running", + dashboard_enabled=True, + ) + cs_client.clusters = [cluster] + + check = cs_kubernetes_dashboard_disabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert "Kubernetes Dashboard enabled" in result[0].status_extended + + def test_dashboard_disabled_passes(self): + cs_client = mock.MagicMock() + cs_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.cs.cs_kubernetes_dashboard_disabled.cs_kubernetes_dashboard_disabled.cs_client", + new=cs_client, + ), + ): + from prowler.providers.alibabacloud.services.cs.cs_kubernetes_dashboard_disabled.cs_kubernetes_dashboard_disabled import ( + cs_kubernetes_dashboard_disabled, + ) + from prowler.providers.alibabacloud.services.cs.cs_service import Cluster + + cluster = Cluster( + id="c2", + name="c2", + region="cn-hangzhou", + cluster_type="k8s", + state="running", + dashboard_enabled=False, + ) + cs_client.clusters = [cluster] + + check = cs_kubernetes_dashboard_disabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + "does not have the Kubernetes Dashboard enabled" + in result[0].status_extended + ) diff --git a/tests/providers/alibabacloud/services/cs/cs_kubernetes_eni_multiple_ip_enabled/__init__.py b/tests/providers/alibabacloud/services/cs/cs_kubernetes_eni_multiple_ip_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/cs/cs_kubernetes_eni_multiple_ip_enabled/cs_kubernetes_eni_multiple_ip_enabled_test.py b/tests/providers/alibabacloud/services/cs/cs_kubernetes_eni_multiple_ip_enabled/cs_kubernetes_eni_multiple_ip_enabled_test.py new file mode 100644 index 0000000000..f04d15c81c --- /dev/null +++ b/tests/providers/alibabacloud/services/cs/cs_kubernetes_eni_multiple_ip_enabled/cs_kubernetes_eni_multiple_ip_enabled_test.py @@ -0,0 +1,77 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestCSKubernetesEniMultipleIpEnabled: + def test_eni_multiple_ip_disabled_fails(self): + cs_client = mock.MagicMock() + cs_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.cs.cs_kubernetes_eni_multiple_ip_enabled.cs_kubernetes_eni_multiple_ip_enabled.cs_client", + new=cs_client, + ), + ): + from prowler.providers.alibabacloud.services.cs.cs_kubernetes_eni_multiple_ip_enabled.cs_kubernetes_eni_multiple_ip_enabled import ( + cs_kubernetes_eni_multiple_ip_enabled, + ) + from prowler.providers.alibabacloud.services.cs.cs_service import Cluster + + cluster = Cluster( + id="c1", + name="c1", + region="cn-hangzhou", + cluster_type="k8s", + state="running", + eni_multiple_ip_enabled=False, + ) + cs_client.clusters = [cluster] + + check = cs_kubernetes_eni_multiple_ip_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + + def test_eni_multiple_ip_enabled_passes(self): + cs_client = mock.MagicMock() + cs_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.cs.cs_kubernetes_eni_multiple_ip_enabled.cs_kubernetes_eni_multiple_ip_enabled.cs_client", + new=cs_client, + ), + ): + from prowler.providers.alibabacloud.services.cs.cs_kubernetes_eni_multiple_ip_enabled.cs_kubernetes_eni_multiple_ip_enabled import ( + cs_kubernetes_eni_multiple_ip_enabled, + ) + from prowler.providers.alibabacloud.services.cs.cs_service import Cluster + + cluster = Cluster( + id="c2", + name="c2", + region="cn-hangzhou", + cluster_type="k8s", + state="running", + eni_multiple_ip_enabled=True, + ) + cs_client.clusters = [cluster] + + check = cs_kubernetes_eni_multiple_ip_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" diff --git a/tests/providers/alibabacloud/services/cs/cs_kubernetes_log_service_enabled/__init__.py b/tests/providers/alibabacloud/services/cs/cs_kubernetes_log_service_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/cs/cs_kubernetes_log_service_enabled/cs_kubernetes_log_service_enabled_test.py b/tests/providers/alibabacloud/services/cs/cs_kubernetes_log_service_enabled/cs_kubernetes_log_service_enabled_test.py new file mode 100644 index 0000000000..c938dc1f4e --- /dev/null +++ b/tests/providers/alibabacloud/services/cs/cs_kubernetes_log_service_enabled/cs_kubernetes_log_service_enabled_test.py @@ -0,0 +1,77 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestCSKubernetesLogServiceEnabled: + def test_log_service_disabled_fails(self): + cs_client = mock.MagicMock() + cs_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.cs.cs_kubernetes_log_service_enabled.cs_kubernetes_log_service_enabled.cs_client", + new=cs_client, + ), + ): + from prowler.providers.alibabacloud.services.cs.cs_kubernetes_log_service_enabled.cs_kubernetes_log_service_enabled import ( + cs_kubernetes_log_service_enabled, + ) + from prowler.providers.alibabacloud.services.cs.cs_service import Cluster + + cluster = Cluster( + id="c1", + name="c1", + region="cn-hangzhou", + cluster_type="k8s", + state="running", + log_service_enabled=False, + ) + cs_client.clusters = [cluster] + + check = cs_kubernetes_log_service_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + + def test_log_service_enabled_passes(self): + cs_client = mock.MagicMock() + cs_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.cs.cs_kubernetes_log_service_enabled.cs_kubernetes_log_service_enabled.cs_client", + new=cs_client, + ), + ): + from prowler.providers.alibabacloud.services.cs.cs_kubernetes_log_service_enabled.cs_kubernetes_log_service_enabled import ( + cs_kubernetes_log_service_enabled, + ) + from prowler.providers.alibabacloud.services.cs.cs_service import Cluster + + cluster = Cluster( + id="c2", + name="c2", + region="cn-hangzhou", + cluster_type="k8s", + state="running", + log_service_enabled=True, + ) + cs_client.clusters = [cluster] + + check = cs_kubernetes_log_service_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" diff --git a/tests/providers/alibabacloud/services/cs/cs_kubernetes_network_policy_enabled/__init__.py b/tests/providers/alibabacloud/services/cs/cs_kubernetes_network_policy_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/cs/cs_kubernetes_network_policy_enabled/cs_kubernetes_network_policy_enabled_test.py b/tests/providers/alibabacloud/services/cs/cs_kubernetes_network_policy_enabled/cs_kubernetes_network_policy_enabled_test.py new file mode 100644 index 0000000000..8ae167c402 --- /dev/null +++ b/tests/providers/alibabacloud/services/cs/cs_kubernetes_network_policy_enabled/cs_kubernetes_network_policy_enabled_test.py @@ -0,0 +1,77 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestCSKubernetesNetworkPolicyEnabled: + def test_network_policy_disabled_fails(self): + cs_client = mock.MagicMock() + cs_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.cs.cs_kubernetes_network_policy_enabled.cs_kubernetes_network_policy_enabled.cs_client", + new=cs_client, + ), + ): + from prowler.providers.alibabacloud.services.cs.cs_kubernetes_network_policy_enabled.cs_kubernetes_network_policy_enabled import ( + cs_kubernetes_network_policy_enabled, + ) + from prowler.providers.alibabacloud.services.cs.cs_service import Cluster + + cluster = Cluster( + id="c1", + name="c1", + region="cn-hangzhou", + cluster_type="k8s", + state="running", + network_policy_enabled=False, + ) + cs_client.clusters = [cluster] + + check = cs_kubernetes_network_policy_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + + def test_network_policy_enabled_passes(self): + cs_client = mock.MagicMock() + cs_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.cs.cs_kubernetes_network_policy_enabled.cs_kubernetes_network_policy_enabled.cs_client", + new=cs_client, + ), + ): + from prowler.providers.alibabacloud.services.cs.cs_kubernetes_network_policy_enabled.cs_kubernetes_network_policy_enabled import ( + cs_kubernetes_network_policy_enabled, + ) + from prowler.providers.alibabacloud.services.cs.cs_service import Cluster + + cluster = Cluster( + id="c2", + name="c2", + region="cn-hangzhou", + cluster_type="k8s", + state="running", + network_policy_enabled=True, + ) + cs_client.clusters = [cluster] + + check = cs_kubernetes_network_policy_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" diff --git a/tests/providers/alibabacloud/services/cs/cs_kubernetes_private_cluster_enabled/__init__.py b/tests/providers/alibabacloud/services/cs/cs_kubernetes_private_cluster_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/cs/cs_kubernetes_private_cluster_enabled/cs_kubernetes_private_cluster_enabled_test.py b/tests/providers/alibabacloud/services/cs/cs_kubernetes_private_cluster_enabled/cs_kubernetes_private_cluster_enabled_test.py new file mode 100644 index 0000000000..310cdf85dc --- /dev/null +++ b/tests/providers/alibabacloud/services/cs/cs_kubernetes_private_cluster_enabled/cs_kubernetes_private_cluster_enabled_test.py @@ -0,0 +1,81 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestCSKubernetesPrivateClusterEnabled: + def test_public_cluster_fails(self): + cs_client = mock.MagicMock() + cs_client.audited_account = "1234567890" + cs_client.region = "cn-hangzhou" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.cs.cs_kubernetes_private_cluster_enabled.cs_kubernetes_private_cluster_enabled.cs_client", + new=cs_client, + ), + ): + from prowler.providers.alibabacloud.services.cs.cs_kubernetes_private_cluster_enabled.cs_kubernetes_private_cluster_enabled import ( + cs_kubernetes_private_cluster_enabled, + ) + from prowler.providers.alibabacloud.services.cs.cs_service import Cluster + + cluster = Cluster( + id="c1", + name="public", + region="cn-hangzhou", + cluster_type="k8s", + state="running", + private_cluster_enabled=False, + ) + cs_client.clusters = [cluster] + + check = cs_kubernetes_private_cluster_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert "public API endpoint" in result[0].status_extended + + def test_private_cluster_passes(self): + cs_client = mock.MagicMock() + cs_client.audited_account = "1234567890" + cs_client.region = "cn-hangzhou" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.cs.cs_kubernetes_private_cluster_enabled.cs_kubernetes_private_cluster_enabled.cs_client", + new=cs_client, + ), + ): + from prowler.providers.alibabacloud.services.cs.cs_kubernetes_private_cluster_enabled.cs_kubernetes_private_cluster_enabled import ( + cs_kubernetes_private_cluster_enabled, + ) + from prowler.providers.alibabacloud.services.cs.cs_service import Cluster + + cluster = Cluster( + id="c2", + name="private", + region="cn-hangzhou", + cluster_type="k8s", + state="running", + private_cluster_enabled=True, + ) + cs_client.clusters = [cluster] + + check = cs_kubernetes_private_cluster_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert "private cluster" in result[0].status_extended diff --git a/tests/providers/alibabacloud/services/cs/cs_kubernetes_rbac_enabled/__init__.py b/tests/providers/alibabacloud/services/cs/cs_kubernetes_rbac_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/cs/cs_kubernetes_rbac_enabled/cs_kubernetes_rbac_enabled_test.py b/tests/providers/alibabacloud/services/cs/cs_kubernetes_rbac_enabled/cs_kubernetes_rbac_enabled_test.py new file mode 100644 index 0000000000..adb456806f --- /dev/null +++ b/tests/providers/alibabacloud/services/cs/cs_kubernetes_rbac_enabled/cs_kubernetes_rbac_enabled_test.py @@ -0,0 +1,77 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestCSKubernetesRBACEnabled: + def test_rbac_disabled_fails(self): + cs_client = mock.MagicMock() + cs_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.cs.cs_kubernetes_rbac_enabled.cs_kubernetes_rbac_enabled.cs_client", + new=cs_client, + ), + ): + from prowler.providers.alibabacloud.services.cs.cs_kubernetes_rbac_enabled.cs_kubernetes_rbac_enabled import ( + cs_kubernetes_rbac_enabled, + ) + from prowler.providers.alibabacloud.services.cs.cs_service import Cluster + + cluster = Cluster( + id="c1", + name="c1", + region="cn-hangzhou", + cluster_type="k8s", + state="running", + rbac_enabled=False, + ) + cs_client.clusters = [cluster] + + check = cs_kubernetes_rbac_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + + def test_rbac_enabled_passes(self): + cs_client = mock.MagicMock() + cs_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.cs.cs_kubernetes_rbac_enabled.cs_kubernetes_rbac_enabled.cs_client", + new=cs_client, + ), + ): + from prowler.providers.alibabacloud.services.cs.cs_kubernetes_rbac_enabled.cs_kubernetes_rbac_enabled import ( + cs_kubernetes_rbac_enabled, + ) + from prowler.providers.alibabacloud.services.cs.cs_service import Cluster + + cluster = Cluster( + id="c2", + name="c2", + region="cn-hangzhou", + cluster_type="k8s", + state="running", + rbac_enabled=True, + ) + cs_client.clusters = [cluster] + + check = cs_kubernetes_rbac_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" diff --git a/tests/providers/alibabacloud/services/cs/cs_service_test.py b/tests/providers/alibabacloud/services/cs/cs_service_test.py new file mode 100644 index 0000000000..cf94e3e537 --- /dev/null +++ b/tests/providers/alibabacloud/services/cs/cs_service_test.py @@ -0,0 +1,24 @@ +from unittest.mock import patch + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestCSService: + def test_service(self): + alibabacloud_provider = set_mocked_alibabacloud_provider() + + with patch( + "prowler.providers.alibabacloud.services.cs.cs_service.CS.__init__", + return_value=None, + ): + from prowler.providers.alibabacloud.services.cs.cs_service import CS + + cs_client = CS(alibabacloud_provider) + cs_client.service = "cs" + cs_client.provider = alibabacloud_provider + cs_client.regional_clients = {} + + assert cs_client.service == "cs" + assert cs_client.provider == alibabacloud_provider diff --git a/tests/providers/alibabacloud/services/ecs/__init__.py b/tests/providers/alibabacloud/services/ecs/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/ecs/ecs_attached_disk_encrypted/__init__.py b/tests/providers/alibabacloud/services/ecs/ecs_attached_disk_encrypted/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/ecs/ecs_attached_disk_encrypted/ecs_attached_disk_encrypted_test.py b/tests/providers/alibabacloud/services/ecs/ecs_attached_disk_encrypted/ecs_attached_disk_encrypted_test.py new file mode 100644 index 0000000000..67e4be00d5 --- /dev/null +++ b/tests/providers/alibabacloud/services/ecs/ecs_attached_disk_encrypted/ecs_attached_disk_encrypted_test.py @@ -0,0 +1,85 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestEcsAttachedDiskEncrypted: + def test_attached_disk_not_encrypted_fails(self): + ecs_client = mock.MagicMock() + ecs_client.audited_account = "1234567890" + ecs_client.disks = [] + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ecs.ecs_attached_disk_encrypted.ecs_attached_disk_encrypted.ecs_client", + new=ecs_client, + ), + ): + from prowler.providers.alibabacloud.services.ecs.ecs_attached_disk_encrypted.ecs_attached_disk_encrypted import ( + ecs_attached_disk_encrypted, + ) + from prowler.providers.alibabacloud.services.ecs.ecs_service import Disk + + disk = Disk( + id="d1", + name="d1", + region="cn-hangzhou", + status="In-use", + disk_category="cloud", + size=20, + is_attached=True, + attached_instance_id="i-1", + is_encrypted=False, + ) + ecs_client.disks = [disk] + + check = ecs_attached_disk_encrypted() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + + def test_attached_disk_encrypted_passes(self): + ecs_client = mock.MagicMock() + ecs_client.audited_account = "1234567890" + ecs_client.disks = [] + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ecs.ecs_attached_disk_encrypted.ecs_attached_disk_encrypted.ecs_client", + new=ecs_client, + ), + ): + from prowler.providers.alibabacloud.services.ecs.ecs_attached_disk_encrypted.ecs_attached_disk_encrypted import ( + ecs_attached_disk_encrypted, + ) + from prowler.providers.alibabacloud.services.ecs.ecs_service import Disk + + disk = Disk( + id="d2", + name="d2", + region="cn-hangzhou", + status="In-use", + disk_category="cloud", + size=20, + is_attached=True, + attached_instance_id="i-2", + is_encrypted=True, + ) + ecs_client.disks = [disk] + + check = ecs_attached_disk_encrypted() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" diff --git a/tests/providers/alibabacloud/services/ecs/ecs_instance_endpoint_protection_installed/__init__.py b/tests/providers/alibabacloud/services/ecs/ecs_instance_endpoint_protection_installed/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/ecs/ecs_instance_endpoint_protection_installed/ecs_instance_endpoint_protection_installed_test.py b/tests/providers/alibabacloud/services/ecs/ecs_instance_endpoint_protection_installed/ecs_instance_endpoint_protection_installed_test.py new file mode 100644 index 0000000000..86d305a49f --- /dev/null +++ b/tests/providers/alibabacloud/services/ecs/ecs_instance_endpoint_protection_installed/ecs_instance_endpoint_protection_installed_test.py @@ -0,0 +1,109 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class DummyAgent: + def __init__(self, installed: bool, status: str): + self.agent_installed = installed + self.agent_status = status + + +class TestEcsInstanceEndpointProtectionInstalled: + def test_agent_missing_or_offline_fails(self): + ecs_client = mock.MagicMock() + ecs_client.audited_account = "1234567890" + ecs_client.instances = [] + + securitycenter_client = mock.MagicMock() + securitycenter_client.instance_agents = {} + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ecs.ecs_instance_endpoint_protection_installed.ecs_instance_endpoint_protection_installed.ecs_client", + new=ecs_client, + ), + mock.patch( + "prowler.providers.alibabacloud.services.ecs.ecs_instance_endpoint_protection_installed.ecs_instance_endpoint_protection_installed.securitycenter_client", + new=securitycenter_client, + ), + ): + from prowler.providers.alibabacloud.services.ecs.ecs_instance_endpoint_protection_installed.ecs_instance_endpoint_protection_installed import ( + ecs_instance_endpoint_protection_installed, + ) + from prowler.providers.alibabacloud.services.ecs.ecs_service import Instance + + instance = Instance( + id="i-1", + name="i-1", + region="cn-hangzhou", + status="Running", + instance_type="ecs.g6.large", + network_type="vpc", + public_ip="", + private_ip="10.0.0.1", + ) + ecs_client.instances = [instance] + securitycenter_client.instance_agents = { + "cn-hangzhou:i-1": DummyAgent(installed=False, status="offline") + } + + check = ecs_instance_endpoint_protection_installed() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + + def test_agent_installed_online_passes(self): + ecs_client = mock.MagicMock() + ecs_client.audited_account = "1234567890" + ecs_client.instances = [] + + securitycenter_client = mock.MagicMock() + securitycenter_client.instance_agents = {} + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ecs.ecs_instance_endpoint_protection_installed.ecs_instance_endpoint_protection_installed.ecs_client", + new=ecs_client, + ), + mock.patch( + "prowler.providers.alibabacloud.services.ecs.ecs_instance_endpoint_protection_installed.ecs_instance_endpoint_protection_installed.securitycenter_client", + new=securitycenter_client, + ), + ): + from prowler.providers.alibabacloud.services.ecs.ecs_instance_endpoint_protection_installed.ecs_instance_endpoint_protection_installed import ( + ecs_instance_endpoint_protection_installed, + ) + from prowler.providers.alibabacloud.services.ecs.ecs_service import Instance + + instance = Instance( + id="i-2", + name="i-2", + region="cn-hangzhou", + status="Running", + instance_type="ecs.g6.large", + network_type="vpc", + public_ip="", + private_ip="10.0.0.2", + ) + ecs_client.instances = [instance] + securitycenter_client.instance_agents = { + "cn-hangzhou:i-2": DummyAgent(installed=True, status="online") + } + + check = ecs_instance_endpoint_protection_installed() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" diff --git a/tests/providers/alibabacloud/services/ecs/ecs_instance_latest_os_patches_applied/__init__.py b/tests/providers/alibabacloud/services/ecs/ecs_instance_latest_os_patches_applied/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/ecs/ecs_instance_latest_os_patches_applied/ecs_instance_latest_os_patches_applied_test.py b/tests/providers/alibabacloud/services/ecs/ecs_instance_latest_os_patches_applied/ecs_instance_latest_os_patches_applied_test.py new file mode 100644 index 0000000000..7ef4e241cf --- /dev/null +++ b/tests/providers/alibabacloud/services/ecs/ecs_instance_latest_os_patches_applied/ecs_instance_latest_os_patches_applied_test.py @@ -0,0 +1,109 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class DummyVulnerability: + def __init__(self, has_vulnerabilities: bool, count: int): + self.has_vulnerabilities = has_vulnerabilities + self.vulnerability_count = count + + +class TestEcsInstanceLatestOSPatchesApplied: + def test_instance_with_vulnerabilities_fails(self): + ecs_client = mock.MagicMock() + ecs_client.audited_account = "1234567890" + ecs_client.instances = [] + + securitycenter_client = mock.MagicMock() + securitycenter_client.instance_vulnerabilities = {} + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ecs.ecs_instance_latest_os_patches_applied.ecs_instance_latest_os_patches_applied.ecs_client", + new=ecs_client, + ), + mock.patch( + "prowler.providers.alibabacloud.services.ecs.ecs_instance_latest_os_patches_applied.ecs_instance_latest_os_patches_applied.securitycenter_client", + new=securitycenter_client, + ), + ): + from prowler.providers.alibabacloud.services.ecs.ecs_instance_latest_os_patches_applied.ecs_instance_latest_os_patches_applied import ( + ecs_instance_latest_os_patches_applied, + ) + from prowler.providers.alibabacloud.services.ecs.ecs_service import Instance + + instance = Instance( + id="i-1", + name="i-1", + region="cn-hangzhou", + status="Running", + instance_type="ecs.g6.large", + network_type="vpc", + public_ip="", + private_ip="10.0.0.1", + ) + ecs_client.instances = [instance] + securitycenter_client.instance_vulnerabilities = { + "cn-hangzhou:i-1": DummyVulnerability(True, 5) + } + + check = ecs_instance_latest_os_patches_applied() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + + def test_instance_no_vulnerabilities_passes(self): + ecs_client = mock.MagicMock() + ecs_client.audited_account = "1234567890" + ecs_client.instances = [] + + securitycenter_client = mock.MagicMock() + securitycenter_client.instance_vulnerabilities = {} + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ecs.ecs_instance_latest_os_patches_applied.ecs_instance_latest_os_patches_applied.ecs_client", + new=ecs_client, + ), + mock.patch( + "prowler.providers.alibabacloud.services.ecs.ecs_instance_latest_os_patches_applied.ecs_instance_latest_os_patches_applied.securitycenter_client", + new=securitycenter_client, + ), + ): + from prowler.providers.alibabacloud.services.ecs.ecs_instance_latest_os_patches_applied.ecs_instance_latest_os_patches_applied import ( + ecs_instance_latest_os_patches_applied, + ) + from prowler.providers.alibabacloud.services.ecs.ecs_service import Instance + + instance = Instance( + id="i-2", + name="i-2", + region="cn-hangzhou", + status="Running", + instance_type="ecs.g6.large", + network_type="vpc", + public_ip="", + private_ip="10.0.0.2", + ) + ecs_client.instances = [instance] + securitycenter_client.instance_vulnerabilities = { + "cn-hangzhou:i-2": DummyVulnerability(False, 0) + } + + check = ecs_instance_latest_os_patches_applied() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" diff --git a/tests/providers/alibabacloud/services/ecs/ecs_instance_no_legacy_network/__init__.py b/tests/providers/alibabacloud/services/ecs/ecs_instance_no_legacy_network/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/ecs/ecs_instance_no_legacy_network/ecs_instance_no_legacy_network_test.py b/tests/providers/alibabacloud/services/ecs/ecs_instance_no_legacy_network/ecs_instance_no_legacy_network_test.py new file mode 100644 index 0000000000..9ddf5e9ca8 --- /dev/null +++ b/tests/providers/alibabacloud/services/ecs/ecs_instance_no_legacy_network/ecs_instance_no_legacy_network_test.py @@ -0,0 +1,81 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestEcsInstanceNoLegacyNetwork: + def test_classic_network_fails(self): + ecs_client = mock.MagicMock() + ecs_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ecs.ecs_instance_no_legacy_network.ecs_instance_no_legacy_network.ecs_client", + new=ecs_client, + ), + ): + from prowler.providers.alibabacloud.services.ecs.ecs_instance_no_legacy_network.ecs_instance_no_legacy_network import ( + ecs_instance_no_legacy_network, + ) + from prowler.providers.alibabacloud.services.ecs.ecs_service import Instance + + instance = Instance( + id="i-1", + name="i-1", + region="cn-hangzhou", + status="Running", + instance_type="ecs.g6.large", + network_type="classic", + public_ip="", + private_ip="10.0.0.1", + ) + ecs_client.instances = [instance] + + check = ecs_instance_no_legacy_network() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + + def test_vpc_network_passes(self): + ecs_client = mock.MagicMock() + ecs_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ecs.ecs_instance_no_legacy_network.ecs_instance_no_legacy_network.ecs_client", + new=ecs_client, + ), + ): + from prowler.providers.alibabacloud.services.ecs.ecs_instance_no_legacy_network.ecs_instance_no_legacy_network import ( + ecs_instance_no_legacy_network, + ) + from prowler.providers.alibabacloud.services.ecs.ecs_service import Instance + + instance = Instance( + id="i-2", + name="i-2", + region="cn-hangzhou", + status="Running", + instance_type="ecs.g6.large", + network_type="vpc", + public_ip="", + private_ip="10.0.0.2", + ) + ecs_client.instances = [instance] + + check = ecs_instance_no_legacy_network() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" diff --git a/tests/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_rdp_internet/__init__.py b/tests/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_rdp_internet/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_rdp_internet/ecs_securitygroup_restrict_rdp_internet_test.py b/tests/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_rdp_internet/ecs_securitygroup_restrict_rdp_internet_test.py new file mode 100644 index 0000000000..17709a94a7 --- /dev/null +++ b/tests/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_rdp_internet/ecs_securitygroup_restrict_rdp_internet_test.py @@ -0,0 +1,93 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestEcsSecurityGroupRestrictRdpInternet: + def test_rdp_open_fails(self): + ecs_client = mock.MagicMock() + ecs_client.security_groups = {} + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ecs.ecs_securitygroup_restrict_rdp_internet.ecs_securitygroup_restrict_rdp_internet.ecs_client", + new=ecs_client, + ), + ): + from prowler.providers.alibabacloud.services.ecs.ecs_securitygroup_restrict_rdp_internet.ecs_securitygroup_restrict_rdp_internet import ( + ecs_securitygroup_restrict_rdp_internet, + ) + from prowler.providers.alibabacloud.services.ecs.ecs_service import ( + SecurityGroup, + ) + + sg = SecurityGroup( + id="sg-1", + name="sg-rdp", + region="cn-hangzhou", + arn="arn:sg/sg-1", + ingress_rules=[ + { + "ip_protocol": "tcp", + "source_cidr_ip": "0.0.0.0/0", + "port_range": "3389/3389", + "policy": "accept", + } + ], + ) + ecs_client.security_groups = {sg.arn: sg} + + check = ecs_securitygroup_restrict_rdp_internet() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + + def test_rdp_restricted_passes(self): + ecs_client = mock.MagicMock() + ecs_client.security_groups = {} + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ecs.ecs_securitygroup_restrict_rdp_internet.ecs_securitygroup_restrict_rdp_internet.ecs_client", + new=ecs_client, + ), + ): + from prowler.providers.alibabacloud.services.ecs.ecs_securitygroup_restrict_rdp_internet.ecs_securitygroup_restrict_rdp_internet import ( + ecs_securitygroup_restrict_rdp_internet, + ) + from prowler.providers.alibabacloud.services.ecs.ecs_service import ( + SecurityGroup, + ) + + sg = SecurityGroup( + id="sg-2", + name="sg-restricted", + region="cn-hangzhou", + arn="arn:sg/sg-2", + ingress_rules=[ + { + "ip_protocol": "tcp", + "source_cidr_ip": "10.0.0.0/24", + "port_range": "3389/3389", + "policy": "accept", + } + ], + ) + ecs_client.security_groups = {sg.arn: sg} + + check = ecs_securitygroup_restrict_rdp_internet() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" diff --git a/tests/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_ssh_internet/__init__.py b/tests/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_ssh_internet/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_ssh_internet/ecs_securitygroup_restrict_ssh_internet_test.py b/tests/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_ssh_internet/ecs_securitygroup_restrict_ssh_internet_test.py new file mode 100644 index 0000000000..3278ce9a80 --- /dev/null +++ b/tests/providers/alibabacloud/services/ecs/ecs_securitygroup_restrict_ssh_internet/ecs_securitygroup_restrict_ssh_internet_test.py @@ -0,0 +1,95 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestEcsSecurityGroupRestrictSSHInternet: + def test_security_group_open_to_internet_fails(self): + ecs_client = mock.MagicMock() + ecs_client.security_groups = {} + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ecs.ecs_securitygroup_restrict_ssh_internet.ecs_securitygroup_restrict_ssh_internet.ecs_client", + new=ecs_client, + ), + ): + from prowler.providers.alibabacloud.services.ecs.ecs_securitygroup_restrict_ssh_internet.ecs_securitygroup_restrict_ssh_internet import ( + ecs_securitygroup_restrict_ssh_internet, + ) + from prowler.providers.alibabacloud.services.ecs.ecs_service import ( + SecurityGroup, + ) + + sg = SecurityGroup( + id="sg-1", + name="sg-open", + region="cn-hangzhou", + arn="arn:sg/sg-1", + ingress_rules=[ + { + "ip_protocol": "tcp", + "source_cidr_ip": "0.0.0.0/0", + "port_range": "22/22", + "policy": "accept", + } + ], + ) + ecs_client.security_groups = {sg.arn: sg} + + check = ecs_securitygroup_restrict_ssh_internet() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert "SSH port 22 open to the internet" in result[0].status_extended + + def test_security_group_restricted_passes(self): + ecs_client = mock.MagicMock() + ecs_client.security_groups = {} + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ecs.ecs_securitygroup_restrict_ssh_internet.ecs_securitygroup_restrict_ssh_internet.ecs_client", + new=ecs_client, + ), + ): + from prowler.providers.alibabacloud.services.ecs.ecs_securitygroup_restrict_ssh_internet.ecs_securitygroup_restrict_ssh_internet import ( + ecs_securitygroup_restrict_ssh_internet, + ) + from prowler.providers.alibabacloud.services.ecs.ecs_service import ( + SecurityGroup, + ) + + sg = SecurityGroup( + id="sg-2", + name="sg-restricted", + region="cn-hangzhou", + arn="arn:sg/sg-2", + ingress_rules=[ + { + "ip_protocol": "tcp", + "source_cidr_ip": "10.0.0.0/24", + "port_range": "22/22", + "policy": "accept", + } + ], + ) + ecs_client.security_groups = {sg.arn: sg} + + check = ecs_securitygroup_restrict_ssh_internet() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert "does not have SSH port 22 open" in result[0].status_extended diff --git a/tests/providers/alibabacloud/services/ecs/ecs_service_test.py b/tests/providers/alibabacloud/services/ecs/ecs_service_test.py new file mode 100644 index 0000000000..1b486a3743 --- /dev/null +++ b/tests/providers/alibabacloud/services/ecs/ecs_service_test.py @@ -0,0 +1,24 @@ +from unittest.mock import patch + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestECSService: + def test_service(self): + alibabacloud_provider = set_mocked_alibabacloud_provider() + + with patch( + "prowler.providers.alibabacloud.services.ecs.ecs_service.ECS.__init__", + return_value=None, + ): + from prowler.providers.alibabacloud.services.ecs.ecs_service import ECS + + ecs_client = ECS(alibabacloud_provider) + ecs_client.service = "ecs" + ecs_client.provider = alibabacloud_provider + ecs_client.regional_clients = {} + + assert ecs_client.service == "ecs" + assert ecs_client.provider == alibabacloud_provider diff --git a/tests/providers/alibabacloud/services/ecs/ecs_unattached_disk_encrypted/__init__.py b/tests/providers/alibabacloud/services/ecs/ecs_unattached_disk_encrypted/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/ecs/ecs_unattached_disk_encrypted/ecs_unattached_disk_encrypted_test.py b/tests/providers/alibabacloud/services/ecs/ecs_unattached_disk_encrypted/ecs_unattached_disk_encrypted_test.py new file mode 100644 index 0000000000..0f4568e434 --- /dev/null +++ b/tests/providers/alibabacloud/services/ecs/ecs_unattached_disk_encrypted/ecs_unattached_disk_encrypted_test.py @@ -0,0 +1,83 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestEcsUnattachedDiskEncrypted: + def test_unattached_disk_not_encrypted_fails(self): + ecs_client = mock.MagicMock() + ecs_client.audited_account = "1234567890" + ecs_client.disks = [] + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ecs.ecs_unattached_disk_encrypted.ecs_unattached_disk_encrypted.ecs_client", + new=ecs_client, + ), + ): + from prowler.providers.alibabacloud.services.ecs.ecs_service import Disk + from prowler.providers.alibabacloud.services.ecs.ecs_unattached_disk_encrypted.ecs_unattached_disk_encrypted import ( + ecs_unattached_disk_encrypted, + ) + + disk = Disk( + id="d1", + name="d1", + region="cn-hangzhou", + status="Available", + disk_category="cloud", + size=20, + is_attached=False, + is_encrypted=False, + ) + ecs_client.disks = [disk] + + check = ecs_unattached_disk_encrypted() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + + def test_unattached_disk_encrypted_passes(self): + ecs_client = mock.MagicMock() + ecs_client.audited_account = "1234567890" + ecs_client.disks = [] + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ecs.ecs_unattached_disk_encrypted.ecs_unattached_disk_encrypted.ecs_client", + new=ecs_client, + ), + ): + from prowler.providers.alibabacloud.services.ecs.ecs_service import Disk + from prowler.providers.alibabacloud.services.ecs.ecs_unattached_disk_encrypted.ecs_unattached_disk_encrypted import ( + ecs_unattached_disk_encrypted, + ) + + disk = Disk( + id="d2", + name="d2", + region="cn-hangzhou", + status="Available", + disk_category="cloud", + size=20, + is_attached=False, + is_encrypted=True, + ) + ecs_client.disks = [disk] + + check = ecs_unattached_disk_encrypted() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" diff --git a/tests/providers/alibabacloud/services/oss/__init__.py b/tests/providers/alibabacloud/services/oss/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/oss/oss_bucket_logging_enabled/__init__.py b/tests/providers/alibabacloud/services/oss/oss_bucket_logging_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/oss/oss_bucket_logging_enabled/oss_bucket_logging_enabled_test.py b/tests/providers/alibabacloud/services/oss/oss_bucket_logging_enabled/oss_bucket_logging_enabled_test.py new file mode 100644 index 0000000000..872ba232cf --- /dev/null +++ b/tests/providers/alibabacloud/services/oss/oss_bucket_logging_enabled/oss_bucket_logging_enabled_test.py @@ -0,0 +1,79 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestOssBucketLoggingEnabled: + def test_bucket_with_logging_enabled(self): + oss_client = mock.MagicMock() + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.oss.oss_bucket_logging_enabled.oss_bucket_logging_enabled.oss_client", + new=oss_client, + ), + ): + from prowler.providers.alibabacloud.services.oss.oss_bucket_logging_enabled.oss_bucket_logging_enabled import ( + oss_bucket_logging_enabled, + ) + from prowler.providers.alibabacloud.services.oss.oss_service import Bucket + + bucket = Bucket( + arn="acs:oss::1234567890:with-logging", + name="with-logging", + region="cn-hangzhou", + logging_enabled=True, + logging_target_bucket="log-bucket", + logging_target_prefix="logs/", + ) + oss_client.buckets = {bucket.arn: bucket} + + check = oss_bucket_logging_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert "logging enabled" in result[0].status_extended + assert result[0].resource_id == "with-logging" + assert result[0].resource_arn == bucket.arn + + def test_bucket_without_logging(self): + oss_client = mock.MagicMock() + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.oss.oss_bucket_logging_enabled.oss_bucket_logging_enabled.oss_client", + new=oss_client, + ), + ): + from prowler.providers.alibabacloud.services.oss.oss_bucket_logging_enabled.oss_bucket_logging_enabled import ( + oss_bucket_logging_enabled, + ) + from prowler.providers.alibabacloud.services.oss.oss_service import Bucket + + bucket = Bucket( + arn="acs:oss::1234567890:no-logging", + name="no-logging", + region="cn-hangzhou", + logging_enabled=False, + ) + oss_client.buckets = {bucket.arn: bucket} + + check = oss_bucket_logging_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert "does not have logging enabled" in result[0].status_extended + assert result[0].resource_id == "no-logging" + assert result[0].resource_arn == bucket.arn diff --git a/tests/providers/alibabacloud/services/oss/oss_bucket_not_publicly_accessible/__init__.py b/tests/providers/alibabacloud/services/oss/oss_bucket_not_publicly_accessible/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/oss/oss_bucket_not_publicly_accessible/oss_bucket_not_publicly_accessible_test.py b/tests/providers/alibabacloud/services/oss/oss_bucket_not_publicly_accessible/oss_bucket_not_publicly_accessible_test.py new file mode 100644 index 0000000000..c6d68da0cc --- /dev/null +++ b/tests/providers/alibabacloud/services/oss/oss_bucket_not_publicly_accessible/oss_bucket_not_publicly_accessible_test.py @@ -0,0 +1,76 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestOssBucketNotPubliclyAccessible: + def test_public_acl_fails(self): + oss_client = mock.MagicMock() + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.oss.oss_bucket_not_publicly_accessible.oss_bucket_not_publicly_accessible.oss_client", + new=oss_client, + ), + ): + from prowler.providers.alibabacloud.services.oss.oss_bucket_not_publicly_accessible.oss_bucket_not_publicly_accessible import ( + oss_bucket_not_publicly_accessible, + ) + from prowler.providers.alibabacloud.services.oss.oss_service import Bucket + + bucket = Bucket( + arn="acs:oss::1234567890:public-acl", + name="public-acl", + region="cn-hangzhou", + acl="public-read", + policy={}, + ) + oss_client.buckets = {bucket.arn: bucket} + + check = oss_bucket_not_publicly_accessible() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert "publicly accessible" in result[0].status_extended + assert "public-read" in result[0].status_extended + + def test_private_bucket_passes(self): + oss_client = mock.MagicMock() + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.oss.oss_bucket_not_publicly_accessible.oss_bucket_not_publicly_accessible.oss_client", + new=oss_client, + ), + ): + from prowler.providers.alibabacloud.services.oss.oss_bucket_not_publicly_accessible.oss_bucket_not_publicly_accessible import ( + oss_bucket_not_publicly_accessible, + ) + from prowler.providers.alibabacloud.services.oss.oss_service import Bucket + + bucket = Bucket( + arn="acs:oss::1234567890:private", + name="private", + region="cn-hangzhou", + acl="private", + policy={}, + ) + oss_client.buckets = {bucket.arn: bucket} + + check = oss_bucket_not_publicly_accessible() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert "not publicly accessible" in result[0].status_extended diff --git a/tests/providers/alibabacloud/services/oss/oss_bucket_secure_transport_enabled/__init__.py b/tests/providers/alibabacloud/services/oss/oss_bucket_secure_transport_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/oss/oss_bucket_secure_transport_enabled/oss_bucket_secure_transport_enabled_test.py b/tests/providers/alibabacloud/services/oss/oss_bucket_secure_transport_enabled/oss_bucket_secure_transport_enabled_test.py new file mode 100644 index 0000000000..e968c853ca --- /dev/null +++ b/tests/providers/alibabacloud/services/oss/oss_bucket_secure_transport_enabled/oss_bucket_secure_transport_enabled_test.py @@ -0,0 +1,85 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestOssBucketSecureTransportEnabled: + def test_bucket_without_secure_transport_policy(self): + oss_client = mock.MagicMock() + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.oss.oss_bucket_secure_transport_enabled.oss_bucket_secure_transport_enabled.oss_client", + new=oss_client, + ), + ): + from prowler.providers.alibabacloud.services.oss.oss_bucket_secure_transport_enabled.oss_bucket_secure_transport_enabled import ( + oss_bucket_secure_transport_enabled, + ) + from prowler.providers.alibabacloud.services.oss.oss_service import Bucket + + bucket = Bucket( + arn="acs:oss::1234567890:insecure", + name="insecure", + region="cn-hangzhou", + policy={}, + ) + oss_client.buckets = {bucket.arn: bucket} + + check = oss_bucket_secure_transport_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + "does not have secure transfer required enabled" + in result[0].status_extended + ) + assert result[0].resource_id == "insecure" + + def test_bucket_with_secure_transport_policy(self): + oss_client = mock.MagicMock() + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.oss.oss_bucket_secure_transport_enabled.oss_bucket_secure_transport_enabled.oss_client", + new=oss_client, + ), + ): + from prowler.providers.alibabacloud.services.oss.oss_bucket_secure_transport_enabled.oss_bucket_secure_transport_enabled import ( + oss_bucket_secure_transport_enabled, + ) + from prowler.providers.alibabacloud.services.oss.oss_service import Bucket + + bucket = Bucket( + arn="acs:oss::1234567890:secure", + name="secure", + region="cn-hangzhou", + policy={ + "Statement": [ + { + "Effect": "Deny", + "Condition": {"Bool": {"acs:SecureTransport": ["false"]}}, + } + ] + }, + ) + oss_client.buckets = {bucket.arn: bucket} + + check = oss_bucket_secure_transport_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert "secure transfer required enabled" in result[0].status_extended + assert result[0].resource_id == "secure" diff --git a/tests/providers/alibabacloud/services/oss/oss_service_test.py b/tests/providers/alibabacloud/services/oss/oss_service_test.py new file mode 100644 index 0000000000..ea906f587e --- /dev/null +++ b/tests/providers/alibabacloud/services/oss/oss_service_test.py @@ -0,0 +1,76 @@ +from datetime import timezone +from threading import Lock +from unittest.mock import MagicMock, patch + +from prowler.providers.alibabacloud.services.oss.oss_service import OSS + + +class _DummyCreds: + def __init__(self): + self.access_key_id = "AKID" + self.access_key_secret = "SECRET" + self.security_token = None + + +def _build_oss_service(audit_resources=None): + """Create an OSS service instance without running __init__.""" + service = OSS.__new__(OSS) + service.audit_resources = audit_resources or [] + service.region = "cn-hangzhou" + service.audited_account = "1234567890" + service.buckets = {} + service._buckets_lock = Lock() + client = MagicMock() + client.region = "ap-southeast-1" + service.regional_clients = {"ap-southeast-1": client} + service.client = client + service.session = MagicMock() + service.session.get_credentials.return_value = _DummyCreds() + # Avoid real thread pool in tests + service.__threading_call__ = lambda call, iterator=None: [ + call(item) for item in ((iterator or service.regional_clients.values())) + ] + return service + + +def _fake_oss_list_response(bucket_name="prowler-test", location="oss-ap-southeast-1"): + return f""" + + + + {bucket_name} + 2025-11-26T10:26:46.000Z + {location} + + + + """.strip() + + +def test_list_buckets_parses_and_normalizes_location(): + oss = _build_oss_service() + + with patch("requests.get") as get_mock: + get_mock.return_value = MagicMock( + status_code=200, text=_fake_oss_list_response() + ) + oss._list_buckets() + + arn = "acs:oss::1234567890:prowler-test" + assert arn in oss.buckets + stored_bucket = oss.buckets[arn] + assert stored_bucket.region == "ap-southeast-1" + assert stored_bucket.creation_date.tzinfo == timezone.utc + + +def test_list_buckets_respects_audit_filters(): + oss = _build_oss_service(audit_resources=["acs:oss::1234567890:allowed-bucket"]) + + with patch("requests.get") as get_mock: + get_mock.return_value = MagicMock( + status_code=200, + text=_fake_oss_list_response(bucket_name="denied-bucket"), + ) + oss._list_buckets() + + assert list(oss.buckets.keys()) == [] diff --git a/tests/providers/alibabacloud/services/ram/__init__.py b/tests/providers/alibabacloud/services/ram/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/ram/ram_no_root_access_key/__init__.py b/tests/providers/alibabacloud/services/ram/ram_no_root_access_key/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/ram/ram_no_root_access_key/ram_no_root_access_key_test.py b/tests/providers/alibabacloud/services/ram/ram_no_root_access_key/ram_no_root_access_key_test.py new file mode 100644 index 0000000000..d5f3404dea --- /dev/null +++ b/tests/providers/alibabacloud/services/ram/ram_no_root_access_key/ram_no_root_access_key_test.py @@ -0,0 +1,65 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestRamNoRootAccessKey: + def test_root_has_keys_fails(self): + provider = set_mocked_alibabacloud_provider() + provider.identity.is_root = True + ram_client = mock.MagicMock() + ram_client.provider = provider + ram_client.region = "cn-hangzhou" + ram_client.audited_account = "1234567890" + ram_client.root_access_keys = ["AKIA"] + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=provider, + ), + mock.patch( + "prowler.providers.alibabacloud.services.ram.ram_no_root_access_key.ram_no_root_access_key.ram_client", + new=ram_client, + ), + ): + from prowler.providers.alibabacloud.services.ram.ram_no_root_access_key.ram_no_root_access_key import ( + ram_no_root_access_key, + ) + + check = ram_no_root_access_key() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + + def test_root_without_keys_passes(self): + provider = set_mocked_alibabacloud_provider() + provider.identity.is_root = True + ram_client = mock.MagicMock() + ram_client.provider = provider + ram_client.region = "cn-hangzhou" + ram_client.audited_account = "1234567890" + ram_client.root_access_keys = [] + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=provider, + ), + mock.patch( + "prowler.providers.alibabacloud.services.ram.ram_no_root_access_key.ram_no_root_access_key.ram_client", + new=ram_client, + ), + ): + from prowler.providers.alibabacloud.services.ram.ram_no_root_access_key.ram_no_root_access_key import ( + ram_no_root_access_key, + ) + + check = ram_no_root_access_key() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" diff --git a/tests/providers/alibabacloud/services/ram/ram_password_policy_lowercase/__init__.py b/tests/providers/alibabacloud/services/ram/ram_password_policy_lowercase/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/ram/ram_password_policy_lowercase/ram_password_policy_lowercase_test.py b/tests/providers/alibabacloud/services/ram/ram_password_policy_lowercase/ram_password_policy_lowercase_test.py new file mode 100644 index 0000000000..cad3ef5ecb --- /dev/null +++ b/tests/providers/alibabacloud/services/ram/ram_password_policy_lowercase/ram_password_policy_lowercase_test.py @@ -0,0 +1,71 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestRamPasswordPolicyLowercase: + def test_lowercase_not_required_fails(self): + ram_client = mock.MagicMock() + ram_client.audited_account = "1234567890" + ram_client.region = "cn-hangzhou" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ram.ram_password_policy_lowercase.ram_password_policy_lowercase.ram_client", + new=ram_client, + ), + ): + from prowler.providers.alibabacloud.services.ram.ram_password_policy_lowercase.ram_password_policy_lowercase import ( + ram_password_policy_lowercase, + ) + from prowler.providers.alibabacloud.services.ram.ram_service import ( + PasswordPolicy, + ) + + ram_client.password_policy = PasswordPolicy( + require_lowercase_characters=False + ) + + check = ram_password_policy_lowercase() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + + def test_lowercase_required_passes(self): + ram_client = mock.MagicMock() + ram_client.audited_account = "1234567890" + ram_client.region = "cn-hangzhou" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ram.ram_password_policy_lowercase.ram_password_policy_lowercase.ram_client", + new=ram_client, + ), + ): + from prowler.providers.alibabacloud.services.ram.ram_password_policy_lowercase.ram_password_policy_lowercase import ( + ram_password_policy_lowercase, + ) + from prowler.providers.alibabacloud.services.ram.ram_service import ( + PasswordPolicy, + ) + + ram_client.password_policy = PasswordPolicy( + require_lowercase_characters=True + ) + + check = ram_password_policy_lowercase() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" diff --git a/tests/providers/alibabacloud/services/ram/ram_password_policy_max_login_attempts/__init__.py b/tests/providers/alibabacloud/services/ram/ram_password_policy_max_login_attempts/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/ram/ram_password_policy_max_login_attempts/ram_password_policy_max_login_attempts_test.py b/tests/providers/alibabacloud/services/ram/ram_password_policy_max_login_attempts/ram_password_policy_max_login_attempts_test.py new file mode 100644 index 0000000000..a93cf4713e --- /dev/null +++ b/tests/providers/alibabacloud/services/ram/ram_password_policy_max_login_attempts/ram_password_policy_max_login_attempts_test.py @@ -0,0 +1,67 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestRamPasswordPolicyMaxLoginAttempts: + def test_max_login_attempts_zero_fails(self): + ram_client = mock.MagicMock() + ram_client.audited_account = "1234567890" + ram_client.region = "cn-hangzhou" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ram.ram_password_policy_max_login_attempts.ram_password_policy_max_login_attempts.ram_client", + new=ram_client, + ), + ): + from prowler.providers.alibabacloud.services.ram.ram_password_policy_max_login_attempts.ram_password_policy_max_login_attempts import ( + ram_password_policy_max_login_attempts, + ) + from prowler.providers.alibabacloud.services.ram.ram_service import ( + PasswordPolicy, + ) + + ram_client.password_policy = PasswordPolicy(max_login_attempts=0) + + check = ram_password_policy_max_login_attempts() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + + def test_max_login_attempts_within_or_above_limit_passes(self): + ram_client = mock.MagicMock() + ram_client.audited_account = "1234567890" + ram_client.region = "cn-hangzhou" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ram.ram_password_policy_max_login_attempts.ram_password_policy_max_login_attempts.ram_client", + new=ram_client, + ), + ): + from prowler.providers.alibabacloud.services.ram.ram_password_policy_max_login_attempts.ram_password_policy_max_login_attempts import ( + ram_password_policy_max_login_attempts, + ) + from prowler.providers.alibabacloud.services.ram.ram_service import ( + PasswordPolicy, + ) + + ram_client.password_policy = PasswordPolicy(max_login_attempts=5) + + check = ram_password_policy_max_login_attempts() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" diff --git a/tests/providers/alibabacloud/services/ram/ram_password_policy_max_password_age/__init__.py b/tests/providers/alibabacloud/services/ram/ram_password_policy_max_password_age/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/ram/ram_password_policy_max_password_age/ram_password_policy_max_password_age_test.py b/tests/providers/alibabacloud/services/ram/ram_password_policy_max_password_age/ram_password_policy_max_password_age_test.py new file mode 100644 index 0000000000..31593fb888 --- /dev/null +++ b/tests/providers/alibabacloud/services/ram/ram_password_policy_max_password_age/ram_password_policy_max_password_age_test.py @@ -0,0 +1,67 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestRamPasswordPolicyMaxPasswordAge: + def test_password_age_too_low_fails(self): + ram_client = mock.MagicMock() + ram_client.audited_account = "1234567890" + ram_client.region = "cn-hangzhou" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ram.ram_password_policy_max_password_age.ram_password_policy_max_password_age.ram_client", + new=ram_client, + ), + ): + from prowler.providers.alibabacloud.services.ram.ram_password_policy_max_password_age.ram_password_policy_max_password_age import ( + ram_password_policy_max_password_age, + ) + from prowler.providers.alibabacloud.services.ram.ram_service import ( + PasswordPolicy, + ) + + ram_client.password_policy = PasswordPolicy(max_password_age=90) + + check = ram_password_policy_max_password_age() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + + def test_password_age_disabled_or_high_passes(self): + ram_client = mock.MagicMock() + ram_client.audited_account = "1234567890" + ram_client.region = "cn-hangzhou" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ram.ram_password_policy_max_password_age.ram_password_policy_max_password_age.ram_client", + new=ram_client, + ), + ): + from prowler.providers.alibabacloud.services.ram.ram_password_policy_max_password_age.ram_password_policy_max_password_age import ( + ram_password_policy_max_password_age, + ) + from prowler.providers.alibabacloud.services.ram.ram_service import ( + PasswordPolicy, + ) + + ram_client.password_policy = PasswordPolicy(max_password_age=0) + + check = ram_password_policy_max_password_age() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" diff --git a/tests/providers/alibabacloud/services/ram/ram_password_policy_minimum_length/__init__.py b/tests/providers/alibabacloud/services/ram/ram_password_policy_minimum_length/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/ram/ram_password_policy_minimum_length/ram_password_policy_minimum_length_test.py b/tests/providers/alibabacloud/services/ram/ram_password_policy_minimum_length/ram_password_policy_minimum_length_test.py new file mode 100644 index 0000000000..1cb884db65 --- /dev/null +++ b/tests/providers/alibabacloud/services/ram/ram_password_policy_minimum_length/ram_password_policy_minimum_length_test.py @@ -0,0 +1,71 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestRamPasswordPolicyMinimumLength: + def test_policy_too_short_fails(self): + ram_client = mock.MagicMock() + ram_client.audited_account = "1234567890" + ram_client.region = "cn-hangzhou" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ram.ram_password_policy_minimum_length.ram_password_policy_minimum_length.ram_client", + new=ram_client, + ), + ): + from prowler.providers.alibabacloud.services.ram.ram_password_policy_minimum_length.ram_password_policy_minimum_length import ( + ram_password_policy_minimum_length, + ) + from prowler.providers.alibabacloud.services.ram.ram_service import ( + PasswordPolicy, + ) + + ram_client.password_policy = PasswordPolicy(minimum_password_length=8) + + check = ram_password_policy_minimum_length() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + "less than the recommended 14 characters" in result[0].status_extended + ) + + def test_policy_long_enough_passes(self): + ram_client = mock.MagicMock() + ram_client.audited_account = "1234567890" + ram_client.region = "cn-hangzhou" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ram.ram_password_policy_minimum_length.ram_password_policy_minimum_length.ram_client", + new=ram_client, + ), + ): + from prowler.providers.alibabacloud.services.ram.ram_password_policy_minimum_length.ram_password_policy_minimum_length import ( + ram_password_policy_minimum_length, + ) + from prowler.providers.alibabacloud.services.ram.ram_service import ( + PasswordPolicy, + ) + + ram_client.password_policy = PasswordPolicy(minimum_password_length=14) + + check = ram_password_policy_minimum_length() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert "minimum length of 14 characters" in result[0].status_extended diff --git a/tests/providers/alibabacloud/services/ram/ram_password_policy_password_reuse_prevention/__init__.py b/tests/providers/alibabacloud/services/ram/ram_password_policy_password_reuse_prevention/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/ram/ram_password_policy_password_reuse_prevention/ram_password_policy_password_reuse_prevention_test.py b/tests/providers/alibabacloud/services/ram/ram_password_policy_password_reuse_prevention/ram_password_policy_password_reuse_prevention_test.py new file mode 100644 index 0000000000..6acdb9b9c4 --- /dev/null +++ b/tests/providers/alibabacloud/services/ram/ram_password_policy_password_reuse_prevention/ram_password_policy_password_reuse_prevention_test.py @@ -0,0 +1,67 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestRamPasswordPolicyPasswordReusePrevention: + def test_reuse_prevention_too_low_fails(self): + ram_client = mock.MagicMock() + ram_client.audited_account = "1234567890" + ram_client.region = "cn-hangzhou" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ram.ram_password_policy_password_reuse_prevention.ram_password_policy_password_reuse_prevention.ram_client", + new=ram_client, + ), + ): + from prowler.providers.alibabacloud.services.ram.ram_password_policy_password_reuse_prevention.ram_password_policy_password_reuse_prevention import ( + ram_password_policy_password_reuse_prevention, + ) + from prowler.providers.alibabacloud.services.ram.ram_service import ( + PasswordPolicy, + ) + + ram_client.password_policy = PasswordPolicy(password_reuse_prevention=0) + + check = ram_password_policy_password_reuse_prevention() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + + def test_reuse_prevention_set_passes(self): + ram_client = mock.MagicMock() + ram_client.audited_account = "1234567890" + ram_client.region = "cn-hangzhou" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ram.ram_password_policy_password_reuse_prevention.ram_password_policy_password_reuse_prevention.ram_client", + new=ram_client, + ), + ): + from prowler.providers.alibabacloud.services.ram.ram_password_policy_password_reuse_prevention.ram_password_policy_password_reuse_prevention import ( + ram_password_policy_password_reuse_prevention, + ) + from prowler.providers.alibabacloud.services.ram.ram_service import ( + PasswordPolicy, + ) + + ram_client.password_policy = PasswordPolicy(password_reuse_prevention=5) + + check = ram_password_policy_password_reuse_prevention() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" diff --git a/tests/providers/alibabacloud/services/ram/ram_password_policy_symbol/__init__.py b/tests/providers/alibabacloud/services/ram/ram_password_policy_symbol/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/ram/ram_password_policy_symbol/ram_password_policy_symbol_test.py b/tests/providers/alibabacloud/services/ram/ram_password_policy_symbol/ram_password_policy_symbol_test.py new file mode 100644 index 0000000000..6721d5e441 --- /dev/null +++ b/tests/providers/alibabacloud/services/ram/ram_password_policy_symbol/ram_password_policy_symbol_test.py @@ -0,0 +1,67 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestRamPasswordPolicySymbol: + def test_symbols_not_required_fails(self): + ram_client = mock.MagicMock() + ram_client.audited_account = "1234567890" + ram_client.region = "cn-hangzhou" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ram.ram_password_policy_symbol.ram_password_policy_symbol.ram_client", + new=ram_client, + ), + ): + from prowler.providers.alibabacloud.services.ram.ram_password_policy_symbol.ram_password_policy_symbol import ( + ram_password_policy_symbol, + ) + from prowler.providers.alibabacloud.services.ram.ram_service import ( + PasswordPolicy, + ) + + ram_client.password_policy = PasswordPolicy(require_symbols=False) + + check = ram_password_policy_symbol() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + + def test_symbols_required_passes(self): + ram_client = mock.MagicMock() + ram_client.audited_account = "1234567890" + ram_client.region = "cn-hangzhou" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ram.ram_password_policy_symbol.ram_password_policy_symbol.ram_client", + new=ram_client, + ), + ): + from prowler.providers.alibabacloud.services.ram.ram_password_policy_symbol.ram_password_policy_symbol import ( + ram_password_policy_symbol, + ) + from prowler.providers.alibabacloud.services.ram.ram_service import ( + PasswordPolicy, + ) + + ram_client.password_policy = PasswordPolicy(require_symbols=True) + + check = ram_password_policy_symbol() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" diff --git a/tests/providers/alibabacloud/services/ram/ram_password_policy_uppercase/__init__.py b/tests/providers/alibabacloud/services/ram/ram_password_policy_uppercase/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/ram/ram_password_policy_uppercase/ram_password_policy_uppercase_test.py b/tests/providers/alibabacloud/services/ram/ram_password_policy_uppercase/ram_password_policy_uppercase_test.py new file mode 100644 index 0000000000..7a089020e7 --- /dev/null +++ b/tests/providers/alibabacloud/services/ram/ram_password_policy_uppercase/ram_password_policy_uppercase_test.py @@ -0,0 +1,71 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestRamPasswordPolicyUppercase: + def test_uppercase_not_required_fails(self): + ram_client = mock.MagicMock() + ram_client.audited_account = "1234567890" + ram_client.region = "cn-hangzhou" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ram.ram_password_policy_uppercase.ram_password_policy_uppercase.ram_client", + new=ram_client, + ), + ): + from prowler.providers.alibabacloud.services.ram.ram_password_policy_uppercase.ram_password_policy_uppercase import ( + ram_password_policy_uppercase, + ) + from prowler.providers.alibabacloud.services.ram.ram_service import ( + PasswordPolicy, + ) + + ram_client.password_policy = PasswordPolicy( + require_uppercase_characters=False + ) + + check = ram_password_policy_uppercase() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + + def test_uppercase_required_passes(self): + ram_client = mock.MagicMock() + ram_client.audited_account = "1234567890" + ram_client.region = "cn-hangzhou" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ram.ram_password_policy_uppercase.ram_password_policy_uppercase.ram_client", + new=ram_client, + ), + ): + from prowler.providers.alibabacloud.services.ram.ram_password_policy_uppercase.ram_password_policy_uppercase import ( + ram_password_policy_uppercase, + ) + from prowler.providers.alibabacloud.services.ram.ram_service import ( + PasswordPolicy, + ) + + ram_client.password_policy = PasswordPolicy( + require_uppercase_characters=True + ) + + check = ram_password_policy_uppercase() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" diff --git a/tests/providers/alibabacloud/services/ram/ram_rotate_access_key_90_days/__init__.py b/tests/providers/alibabacloud/services/ram/ram_rotate_access_key_90_days/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/ram/ram_rotate_access_key_90_days/ram_rotate_access_key_90_days_test.py b/tests/providers/alibabacloud/services/ram/ram_rotate_access_key_90_days/ram_rotate_access_key_90_days_test.py new file mode 100644 index 0000000000..82ddeb42b2 --- /dev/null +++ b/tests/providers/alibabacloud/services/ram/ram_rotate_access_key_90_days/ram_rotate_access_key_90_days_test.py @@ -0,0 +1,90 @@ +from datetime import datetime, timedelta, timezone +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestRamRotateAccessKey90Days: + def test_old_access_key_fails(self): + ram_client = mock.MagicMock() + ram_client.audited_account = "1234567890" + ram_client.region = "cn-hangzhou" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ram.ram_rotate_access_key_90_days.ram_rotate_access_key_90_days.ram_client", + new=ram_client, + ), + ): + from prowler.providers.alibabacloud.services.ram.ram_rotate_access_key_90_days.ram_rotate_access_key_90_days import ( + ram_rotate_access_key_90_days, + ) + from prowler.providers.alibabacloud.services.ram.ram_service import ( + AccessKey, + User, + ) + + access_key = AccessKey( + access_key_id="AK1", + status="Active", + create_date=datetime.now(timezone.utc) - timedelta(days=120), + ) + user = User( + name="user1", + user_id="u1", + access_keys=[access_key], + ) + ram_client.users = [user] + + check = ram_rotate_access_key_90_days() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + + def test_recent_access_key_passes(self): + ram_client = mock.MagicMock() + ram_client.audited_account = "1234567890" + ram_client.region = "cn-hangzhou" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.ram.ram_rotate_access_key_90_days.ram_rotate_access_key_90_days.ram_client", + new=ram_client, + ), + ): + from prowler.providers.alibabacloud.services.ram.ram_rotate_access_key_90_days.ram_rotate_access_key_90_days import ( + ram_rotate_access_key_90_days, + ) + from prowler.providers.alibabacloud.services.ram.ram_service import ( + AccessKey, + User, + ) + + access_key = AccessKey( + access_key_id="AK2", + status="Active", + create_date=datetime.now(timezone.utc) - timedelta(days=10), + ) + user = User( + name="user2", + user_id="u2", + access_keys=[access_key], + ) + ram_client.users = [user] + + check = ram_rotate_access_key_90_days() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" diff --git a/tests/providers/alibabacloud/services/ram/ram_service_test.py b/tests/providers/alibabacloud/services/ram/ram_service_test.py new file mode 100644 index 0000000000..f75a3ad210 --- /dev/null +++ b/tests/providers/alibabacloud/services/ram/ram_service_test.py @@ -0,0 +1,24 @@ +from unittest.mock import patch + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestRAMService: + def test_service(self): + alibabacloud_provider = set_mocked_alibabacloud_provider() + + with patch( + "prowler.providers.alibabacloud.services.ram.ram_service.RAM.__init__", + return_value=None, + ): + from prowler.providers.alibabacloud.services.ram.ram_service import RAM + + ram_client = RAM(alibabacloud_provider) + ram_client.service = "ram" + ram_client.provider = alibabacloud_provider + ram_client.regional_clients = {} + + assert ram_client.service == "ram" + assert ram_client.provider == alibabacloud_provider diff --git a/tests/providers/alibabacloud/services/rds/__init__.py b/tests/providers/alibabacloud/services/rds/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/rds/rds_instance_postgresql_log_connections_enabled/__init__.py b/tests/providers/alibabacloud/services/rds/rds_instance_postgresql_log_connections_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/rds/rds_instance_postgresql_log_connections_enabled/rds_instance_postgresql_log_connections_enabled_test.py b/tests/providers/alibabacloud/services/rds/rds_instance_postgresql_log_connections_enabled/rds_instance_postgresql_log_connections_enabled_test.py new file mode 100644 index 0000000000..2c310c32d5 --- /dev/null +++ b/tests/providers/alibabacloud/services/rds/rds_instance_postgresql_log_connections_enabled/rds_instance_postgresql_log_connections_enabled_test.py @@ -0,0 +1,107 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestRdsInstancePostgresqlLogConnectionsEnabled: + def test_log_connections_off_fails(self): + rds_client = mock.MagicMock() + rds_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.rds.rds_instance_postgresql_log_connections_enabled.rds_instance_postgresql_log_connections_enabled.rds_client", + new=rds_client, + ), + ): + from prowler.providers.alibabacloud.services.rds.rds_instance_postgresql_log_connections_enabled.rds_instance_postgresql_log_connections_enabled import ( + rds_instance_postgresql_log_connections_enabled, + ) + from prowler.providers.alibabacloud.services.rds.rds_service import ( + DBInstance, + ) + + instance = DBInstance( + id="db-1", + name="db-1", + region="cn-hangzhou", + engine="PostgreSQL", + engine_version="14", + status="Running", + type="Primary", + net_type="VPC", + connection_mode="Standard", + public_connection_string="", + ssl_enabled=True, + tde_status="Enabled", + tde_key_id="", + security_ips=[], + audit_log_enabled=True, + audit_log_retention=365, + log_connections="off", + log_disconnections="", + log_duration="", + ) + rds_client.instances = [instance] + + check = rds_instance_postgresql_log_connections_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + + def test_log_connections_on_passes(self): + rds_client = mock.MagicMock() + rds_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.rds.rds_instance_postgresql_log_connections_enabled.rds_instance_postgresql_log_connections_enabled.rds_client", + new=rds_client, + ), + ): + from prowler.providers.alibabacloud.services.rds.rds_instance_postgresql_log_connections_enabled.rds_instance_postgresql_log_connections_enabled import ( + rds_instance_postgresql_log_connections_enabled, + ) + from prowler.providers.alibabacloud.services.rds.rds_service import ( + DBInstance, + ) + + instance = DBInstance( + id="db-2", + name="db-2", + region="cn-hangzhou", + engine="PostgreSQL", + engine_version="14", + status="Running", + type="Primary", + net_type="VPC", + connection_mode="Standard", + public_connection_string="", + ssl_enabled=True, + tde_status="Enabled", + tde_key_id="", + security_ips=[], + audit_log_enabled=True, + audit_log_retention=365, + log_connections="on", + log_disconnections="", + log_duration="", + ) + rds_client.instances = [instance] + + check = rds_instance_postgresql_log_connections_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" diff --git a/tests/providers/alibabacloud/services/rds/rds_instance_sql_audit_enabled/__init__.py b/tests/providers/alibabacloud/services/rds/rds_instance_sql_audit_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/rds/rds_instance_sql_audit_enabled/rds_instance_sql_audit_enabled_test.py b/tests/providers/alibabacloud/services/rds/rds_instance_sql_audit_enabled/rds_instance_sql_audit_enabled_test.py new file mode 100644 index 0000000000..f866c2b47a --- /dev/null +++ b/tests/providers/alibabacloud/services/rds/rds_instance_sql_audit_enabled/rds_instance_sql_audit_enabled_test.py @@ -0,0 +1,107 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestRdsInstanceSqlAuditEnabled: + def test_sql_audit_disabled_fails(self): + rds_client = mock.MagicMock() + rds_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.rds.rds_instance_sql_audit_enabled.rds_instance_sql_audit_enabled.rds_client", + new=rds_client, + ), + ): + from prowler.providers.alibabacloud.services.rds.rds_instance_sql_audit_enabled.rds_instance_sql_audit_enabled import ( + rds_instance_sql_audit_enabled, + ) + from prowler.providers.alibabacloud.services.rds.rds_service import ( + DBInstance, + ) + + instance = DBInstance( + id="db-1", + name="db-1", + region="cn-hangzhou", + engine="MySQL", + engine_version="8.0", + status="Running", + type="Primary", + net_type="VPC", + connection_mode="Standard", + public_connection_string="", + ssl_enabled=True, + tde_status="Enabled", + tde_key_id="", + security_ips=[], + audit_log_enabled=False, + audit_log_retention=0, + log_connections="", + log_disconnections="", + log_duration="", + ) + rds_client.instances = [instance] + + check = rds_instance_sql_audit_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + + def test_sql_audit_enabled_passes(self): + rds_client = mock.MagicMock() + rds_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.rds.rds_instance_sql_audit_enabled.rds_instance_sql_audit_enabled.rds_client", + new=rds_client, + ), + ): + from prowler.providers.alibabacloud.services.rds.rds_instance_sql_audit_enabled.rds_instance_sql_audit_enabled import ( + rds_instance_sql_audit_enabled, + ) + from prowler.providers.alibabacloud.services.rds.rds_service import ( + DBInstance, + ) + + instance = DBInstance( + id="db-2", + name="db-2", + region="cn-hangzhou", + engine="MySQL", + engine_version="8.0", + status="Running", + type="Primary", + net_type="VPC", + connection_mode="Standard", + public_connection_string="", + ssl_enabled=True, + tde_status="Enabled", + tde_key_id="", + security_ips=[], + audit_log_enabled=True, + audit_log_retention=7, + log_connections="", + log_disconnections="", + log_duration="", + ) + rds_client.instances = [instance] + + check = rds_instance_sql_audit_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" diff --git a/tests/providers/alibabacloud/services/rds/rds_instance_sql_audit_retention/__init__.py b/tests/providers/alibabacloud/services/rds/rds_instance_sql_audit_retention/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/rds/rds_instance_sql_audit_retention/rds_instance_sql_audit_retention_test.py b/tests/providers/alibabacloud/services/rds/rds_instance_sql_audit_retention/rds_instance_sql_audit_retention_test.py new file mode 100644 index 0000000000..d0cb205651 --- /dev/null +++ b/tests/providers/alibabacloud/services/rds/rds_instance_sql_audit_retention/rds_instance_sql_audit_retention_test.py @@ -0,0 +1,109 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestRdsInstanceSqlAuditRetention: + def test_enabled_short_retention_fails(self): + rds_client = mock.MagicMock() + rds_client.audited_account = "1234567890" + rds_client.audit_config = {"min_rds_audit_retention_days": 180} + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.rds.rds_instance_sql_audit_retention.rds_instance_sql_audit_retention.rds_client", + new=rds_client, + ), + ): + from prowler.providers.alibabacloud.services.rds.rds_instance_sql_audit_retention.rds_instance_sql_audit_retention import ( + rds_instance_sql_audit_retention, + ) + from prowler.providers.alibabacloud.services.rds.rds_service import ( + DBInstance, + ) + + instance = DBInstance( + id="db-1", + name="db-1", + region="cn-hangzhou", + engine="MySQL", + engine_version="8.0", + status="Running", + type="Primary", + net_type="VPC", + connection_mode="Standard", + public_connection_string="", + ssl_enabled=True, + tde_status="Enabled", + tde_key_id="", + security_ips=[], + audit_log_enabled=True, + audit_log_retention=30, + log_connections="", + log_disconnections="", + log_duration="", + ) + rds_client.instances = [instance] + + check = rds_instance_sql_audit_retention() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + + def test_enabled_long_retention_passes(self): + rds_client = mock.MagicMock() + rds_client.audited_account = "1234567890" + rds_client.audit_config = {"min_rds_audit_retention_days": 180} + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.rds.rds_instance_sql_audit_retention.rds_instance_sql_audit_retention.rds_client", + new=rds_client, + ), + ): + from prowler.providers.alibabacloud.services.rds.rds_instance_sql_audit_retention.rds_instance_sql_audit_retention import ( + rds_instance_sql_audit_retention, + ) + from prowler.providers.alibabacloud.services.rds.rds_service import ( + DBInstance, + ) + + instance = DBInstance( + id="db-2", + name="db-2", + region="cn-hangzhou", + engine="MySQL", + engine_version="8.0", + status="Running", + type="Primary", + net_type="VPC", + connection_mode="Standard", + public_connection_string="", + ssl_enabled=True, + tde_status="Enabled", + tde_key_id="", + security_ips=[], + audit_log_enabled=True, + audit_log_retention=365, + log_connections="", + log_disconnections="", + log_duration="", + ) + rds_client.instances = [instance] + + check = rds_instance_sql_audit_retention() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" diff --git a/tests/providers/alibabacloud/services/rds/rds_instance_ssl_enabled/__init__.py b/tests/providers/alibabacloud/services/rds/rds_instance_ssl_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/rds/rds_instance_ssl_enabled/rds_instance_ssl_enabled_test.py b/tests/providers/alibabacloud/services/rds/rds_instance_ssl_enabled/rds_instance_ssl_enabled_test.py new file mode 100644 index 0000000000..c34814a1ca --- /dev/null +++ b/tests/providers/alibabacloud/services/rds/rds_instance_ssl_enabled/rds_instance_ssl_enabled_test.py @@ -0,0 +1,109 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestRdsInstanceSslEnabled: + def test_instance_without_ssl_fails(self): + rds_client = mock.MagicMock() + rds_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.rds.rds_instance_ssl_enabled.rds_instance_ssl_enabled.rds_client", + new=rds_client, + ), + ): + from prowler.providers.alibabacloud.services.rds.rds_instance_ssl_enabled.rds_instance_ssl_enabled import ( + rds_instance_ssl_enabled, + ) + from prowler.providers.alibabacloud.services.rds.rds_service import ( + DBInstance, + ) + + instance = DBInstance( + id="db-1", + name="db-1", + region="cn-hangzhou", + engine="MySQL", + engine_version="8.0", + status="Running", + type="Primary", + net_type="VPC", + connection_mode="Standard", + public_connection_string="", + ssl_enabled=False, + tde_status="Disabled", + tde_key_id="", + security_ips=[], + audit_log_enabled=False, + audit_log_retention=0, + log_connections="", + log_disconnections="", + log_duration="", + ) + rds_client.instances = [instance] + + check = rds_instance_ssl_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert "does not have SSL encryption enabled" in result[0].status_extended + + def test_instance_with_ssl_passes(self): + rds_client = mock.MagicMock() + rds_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.rds.rds_instance_ssl_enabled.rds_instance_ssl_enabled.rds_client", + new=rds_client, + ), + ): + from prowler.providers.alibabacloud.services.rds.rds_instance_ssl_enabled.rds_instance_ssl_enabled import ( + rds_instance_ssl_enabled, + ) + from prowler.providers.alibabacloud.services.rds.rds_service import ( + DBInstance, + ) + + instance = DBInstance( + id="db-2", + name="db-2", + region="cn-hangzhou", + engine="MySQL", + engine_version="8.0", + status="Running", + type="Primary", + net_type="VPC", + connection_mode="Standard", + public_connection_string="", + ssl_enabled=True, + tde_status="Enabled", + tde_key_id="", + security_ips=[], + audit_log_enabled=False, + audit_log_retention=0, + log_connections="", + log_disconnections="", + log_duration="", + ) + rds_client.instances = [instance] + + check = rds_instance_ssl_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert "has SSL encryption enabled" in result[0].status_extended diff --git a/tests/providers/alibabacloud/services/rds/rds_instance_tde_enabled/__init__.py b/tests/providers/alibabacloud/services/rds/rds_instance_tde_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/rds/rds_instance_tde_enabled/rds_instance_tde_enabled_test.py b/tests/providers/alibabacloud/services/rds/rds_instance_tde_enabled/rds_instance_tde_enabled_test.py new file mode 100644 index 0000000000..48b65c8da6 --- /dev/null +++ b/tests/providers/alibabacloud/services/rds/rds_instance_tde_enabled/rds_instance_tde_enabled_test.py @@ -0,0 +1,107 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestRdsInstanceTdeEnabled: + def test_tde_disabled_fails(self): + rds_client = mock.MagicMock() + rds_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.rds.rds_instance_tde_enabled.rds_instance_tde_enabled.rds_client", + new=rds_client, + ), + ): + from prowler.providers.alibabacloud.services.rds.rds_instance_tde_enabled.rds_instance_tde_enabled import ( + rds_instance_tde_enabled, + ) + from prowler.providers.alibabacloud.services.rds.rds_service import ( + DBInstance, + ) + + instance = DBInstance( + id="db-1", + name="db-1", + region="cn-hangzhou", + engine="MySQL", + engine_version="8.0", + status="Running", + type="Primary", + net_type="VPC", + connection_mode="Standard", + public_connection_string="", + ssl_enabled=True, + tde_status="Disabled", + tde_key_id="", + security_ips=[], + audit_log_enabled=False, + audit_log_retention=0, + log_connections="", + log_disconnections="", + log_duration="", + ) + rds_client.instances = [instance] + + check = rds_instance_tde_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + + def test_tde_enabled_passes(self): + rds_client = mock.MagicMock() + rds_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.rds.rds_instance_tde_enabled.rds_instance_tde_enabled.rds_client", + new=rds_client, + ), + ): + from prowler.providers.alibabacloud.services.rds.rds_instance_tde_enabled.rds_instance_tde_enabled import ( + rds_instance_tde_enabled, + ) + from prowler.providers.alibabacloud.services.rds.rds_service import ( + DBInstance, + ) + + instance = DBInstance( + id="db-2", + name="db-2", + region="cn-hangzhou", + engine="MySQL", + engine_version="8.0", + status="Running", + type="Primary", + net_type="VPC", + connection_mode="Standard", + public_connection_string="", + ssl_enabled=True, + tde_status="Enabled", + tde_key_id="", + security_ips=[], + audit_log_enabled=False, + audit_log_retention=0, + log_connections="", + log_disconnections="", + log_duration="", + ) + rds_client.instances = [instance] + + check = rds_instance_tde_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" diff --git a/tests/providers/alibabacloud/services/rds/rds_instance_tde_key_custom/__init__.py b/tests/providers/alibabacloud/services/rds/rds_instance_tde_key_custom/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/rds/rds_instance_tde_key_custom/rds_instance_tde_key_custom_test.py b/tests/providers/alibabacloud/services/rds/rds_instance_tde_key_custom/rds_instance_tde_key_custom_test.py new file mode 100644 index 0000000000..df0c6a07b0 --- /dev/null +++ b/tests/providers/alibabacloud/services/rds/rds_instance_tde_key_custom/rds_instance_tde_key_custom_test.py @@ -0,0 +1,107 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestRdsInstanceTdeKeyCustom: + def test_tde_enabled_service_key_fails(self): + rds_client = mock.MagicMock() + rds_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.rds.rds_instance_tde_key_custom.rds_instance_tde_key_custom.rds_client", + new=rds_client, + ), + ): + from prowler.providers.alibabacloud.services.rds.rds_instance_tde_key_custom.rds_instance_tde_key_custom import ( + rds_instance_tde_key_custom, + ) + from prowler.providers.alibabacloud.services.rds.rds_service import ( + DBInstance, + ) + + instance = DBInstance( + id="db-1", + name="db-1", + region="cn-hangzhou", + engine="MySQL", + engine_version="8.0", + status="Running", + type="Primary", + net_type="VPC", + connection_mode="Standard", + public_connection_string="", + ssl_enabled=True, + tde_status="Enabled", + tde_key_id="", + security_ips=[], + audit_log_enabled=False, + audit_log_retention=0, + log_connections="", + log_disconnections="", + log_duration="", + ) + rds_client.instances = [instance] + + check = rds_instance_tde_key_custom() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + + def test_tde_enabled_custom_key_passes(self): + rds_client = mock.MagicMock() + rds_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.rds.rds_instance_tde_key_custom.rds_instance_tde_key_custom.rds_client", + new=rds_client, + ), + ): + from prowler.providers.alibabacloud.services.rds.rds_instance_tde_key_custom.rds_instance_tde_key_custom import ( + rds_instance_tde_key_custom, + ) + from prowler.providers.alibabacloud.services.rds.rds_service import ( + DBInstance, + ) + + instance = DBInstance( + id="db-2", + name="db-2", + region="cn-hangzhou", + engine="MySQL", + engine_version="8.0", + status="Running", + type="Primary", + net_type="VPC", + connection_mode="Standard", + public_connection_string="", + ssl_enabled=True, + tde_status="Enabled", + tde_key_id="kms-key-id", + security_ips=[], + audit_log_enabled=False, + audit_log_retention=0, + log_connections="", + log_disconnections="", + log_duration="", + ) + rds_client.instances = [instance] + + check = rds_instance_tde_key_custom() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" diff --git a/tests/providers/alibabacloud/services/rds/rds_service_test.py b/tests/providers/alibabacloud/services/rds/rds_service_test.py new file mode 100644 index 0000000000..e6eb54801c --- /dev/null +++ b/tests/providers/alibabacloud/services/rds/rds_service_test.py @@ -0,0 +1,24 @@ +from unittest.mock import patch + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestRDSService: + def test_service(self): + alibabacloud_provider = set_mocked_alibabacloud_provider() + + with patch( + "prowler.providers.alibabacloud.services.rds.rds_service.RDS.__init__", + return_value=None, + ): + from prowler.providers.alibabacloud.services.rds.rds_service import RDS + + rds_client = RDS(alibabacloud_provider) + rds_client.service = "rds" + rds_client.provider = alibabacloud_provider + rds_client.regional_clients = {} + + assert rds_client.service == "rds" + assert rds_client.provider == alibabacloud_provider diff --git a/tests/providers/alibabacloud/services/securitycenter/__init__.py b/tests/providers/alibabacloud/services/securitycenter/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/securitycenter/securitycenter_all_assets_agent_installed/__init__.py b/tests/providers/alibabacloud/services/securitycenter/securitycenter_all_assets_agent_installed/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/securitycenter/securitycenter_all_assets_agent_installed/securitycenter_all_assets_agent_installed_test.py b/tests/providers/alibabacloud/services/securitycenter/securitycenter_all_assets_agent_installed/securitycenter_all_assets_agent_installed_test.py new file mode 100644 index 0000000000..65bb5593af --- /dev/null +++ b/tests/providers/alibabacloud/services/securitycenter/securitycenter_all_assets_agent_installed/securitycenter_all_assets_agent_installed_test.py @@ -0,0 +1,65 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestSecurityCenterAllAssetsAgentInstalled: + def test_uninstalled_assets_fail(self): + securitycenter_client = mock.MagicMock() + securitycenter_client.audited_account = "1234567890" + securitycenter_client.region = "cn-hangzhou" + + uninstalled = mock.MagicMock() + uninstalled.instance_id = "i-1" + uninstalled.instance_name = "asset1" + uninstalled.region = "cn-hangzhou" + uninstalled.os = "Linux" + securitycenter_client.uninstalled_machines = [uninstalled] + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.securitycenter.securitycenter_all_assets_agent_installed.securitycenter_all_assets_agent_installed.securitycenter_client", + new=securitycenter_client, + ), + ): + from prowler.providers.alibabacloud.services.securitycenter.securitycenter_all_assets_agent_installed.securitycenter_all_assets_agent_installed import ( + securitycenter_all_assets_agent_installed, + ) + + check = securitycenter_all_assets_agent_installed() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + + def test_all_assets_installed_passes(self): + securitycenter_client = mock.MagicMock() + securitycenter_client.audited_account = "1234567890" + securitycenter_client.region = "cn-hangzhou" + securitycenter_client.uninstalled_machines = [] + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.securitycenter.securitycenter_all_assets_agent_installed.securitycenter_all_assets_agent_installed.securitycenter_client", + new=securitycenter_client, + ), + ): + from prowler.providers.alibabacloud.services.securitycenter.securitycenter_all_assets_agent_installed.securitycenter_all_assets_agent_installed import ( + securitycenter_all_assets_agent_installed, + ) + + check = securitycenter_all_assets_agent_installed() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" diff --git a/tests/providers/alibabacloud/services/securitycenter/securitycenter_service_test.py b/tests/providers/alibabacloud/services/securitycenter/securitycenter_service_test.py new file mode 100644 index 0000000000..2943b775a9 --- /dev/null +++ b/tests/providers/alibabacloud/services/securitycenter/securitycenter_service_test.py @@ -0,0 +1,26 @@ +from unittest.mock import patch + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestSecurityCenterService: + def test_service(self): + alibabacloud_provider = set_mocked_alibabacloud_provider() + + with patch( + "prowler.providers.alibabacloud.services.securitycenter.securitycenter_service.SecurityCenter.__init__", + return_value=None, + ): + from prowler.providers.alibabacloud.services.securitycenter.securitycenter_service import ( + SecurityCenter, + ) + + securitycenter_client = SecurityCenter(alibabacloud_provider) + securitycenter_client.service = "securitycenter" + securitycenter_client.provider = alibabacloud_provider + securitycenter_client.regional_clients = {} + + assert securitycenter_client.service == "securitycenter" + assert securitycenter_client.provider == alibabacloud_provider diff --git a/tests/providers/alibabacloud/services/securitycenter/securitycenter_vulnerability_scan_enabled/__init__.py b/tests/providers/alibabacloud/services/securitycenter/securitycenter_vulnerability_scan_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/securitycenter/securitycenter_vulnerability_scan_enabled/securitycenter_vulnerability_scan_enabled_test.py b/tests/providers/alibabacloud/services/securitycenter/securitycenter_vulnerability_scan_enabled/securitycenter_vulnerability_scan_enabled_test.py new file mode 100644 index 0000000000..cea2469c41 --- /dev/null +++ b/tests/providers/alibabacloud/services/securitycenter/securitycenter_vulnerability_scan_enabled/securitycenter_vulnerability_scan_enabled_test.py @@ -0,0 +1,72 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class DummyConfig: + def __init__(self, enabled: bool): + self.enabled = enabled + + +class TestSecurityCenterVulnerabilityScanEnabled: + def test_missing_types_or_levels_fail(self): + securitycenter_client = mock.MagicMock() + securitycenter_client.audited_account = "1234567890" + securitycenter_client.region = "cn-hangzhou" + securitycenter_client.vul_configs = { + "yum": DummyConfig(enabled=True), + "cve": DummyConfig(enabled=False), + } + securitycenter_client.concern_necessity = ["asap"] # missing "later" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.securitycenter.securitycenter_vulnerability_scan_enabled.securitycenter_vulnerability_scan_enabled.securitycenter_client", + new=securitycenter_client, + ), + ): + from prowler.providers.alibabacloud.services.securitycenter.securitycenter_vulnerability_scan_enabled.securitycenter_vulnerability_scan_enabled import ( + securitycenter_vulnerability_scan_enabled, + ) + + check = securitycenter_vulnerability_scan_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + + def test_all_types_and_levels_pass(self): + securitycenter_client = mock.MagicMock() + securitycenter_client.audited_account = "1234567890" + securitycenter_client.region = "cn-hangzhou" + securitycenter_client.vul_configs = { + key: DummyConfig(enabled=True) + for key in ["yum", "cve", "sys", "cms", "emg"] + } + securitycenter_client.concern_necessity = ["asap", "later"] + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.securitycenter.securitycenter_vulnerability_scan_enabled.securitycenter_vulnerability_scan_enabled.securitycenter_client", + new=securitycenter_client, + ), + ): + from prowler.providers.alibabacloud.services.securitycenter.securitycenter_vulnerability_scan_enabled.securitycenter_vulnerability_scan_enabled import ( + securitycenter_vulnerability_scan_enabled, + ) + + check = securitycenter_vulnerability_scan_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" diff --git a/tests/providers/alibabacloud/services/sls/__init__.py b/tests/providers/alibabacloud/services/sls/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/sls/sls_logstore_retention_period/__init__.py b/tests/providers/alibabacloud/services/sls/sls_logstore_retention_period/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/sls/sls_logstore_retention_period/sls_logstore_retention_period_test.py b/tests/providers/alibabacloud/services/sls/sls_logstore_retention_period/sls_logstore_retention_period_test.py new file mode 100644 index 0000000000..b9bcf0c1c6 --- /dev/null +++ b/tests/providers/alibabacloud/services/sls/sls_logstore_retention_period/sls_logstore_retention_period_test.py @@ -0,0 +1,81 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestSlsLogstoreRetentionPeriod: + def test_short_retention_fails(self): + sls_client = mock.MagicMock() + sls_client.audit_config = {"min_log_retention_days": 365} + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.sls.sls_logstore_retention_period.sls_logstore_retention_period.sls_client", + new=sls_client, + ), + ): + from prowler.providers.alibabacloud.services.sls.sls_logstore_retention_period.sls_logstore_retention_period import ( + sls_logstore_retention_period, + ) + from prowler.providers.alibabacloud.services.sls.sls_service import LogStore + + logstore = LogStore( + name="short", + project="proj", + retention_forever=False, + retention_days=90, + region="cn-hangzhou", + arn="arn:log:short", + ) + sls_client.log_stores = [logstore] + sls_client.provider = set_mocked_alibabacloud_provider() + + check = sls_logstore_retention_period() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert "less than" in result[0].status_extended + + def test_long_retention_passes(self): + sls_client = mock.MagicMock() + sls_client.audit_config = {"min_log_retention_days": 365} + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.sls.sls_logstore_retention_period.sls_logstore_retention_period.sls_client", + new=sls_client, + ), + ): + from prowler.providers.alibabacloud.services.sls.sls_logstore_retention_period.sls_logstore_retention_period import ( + sls_logstore_retention_period, + ) + from prowler.providers.alibabacloud.services.sls.sls_service import LogStore + + logstore = LogStore( + name="long", + project="proj", + retention_forever=False, + retention_days=400, + region="cn-hangzhou", + arn="arn:log:long", + ) + sls_client.log_stores = [logstore] + sls_client.provider = set_mocked_alibabacloud_provider() + + check = sls_logstore_retention_period() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert "retention set to 400 days" in result[0].status_extended diff --git a/tests/providers/alibabacloud/services/sls/sls_management_console_authentication_failures_alert_enabled/__init__.py b/tests/providers/alibabacloud/services/sls/sls_management_console_authentication_failures_alert_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/sls/sls_management_console_authentication_failures_alert_enabled/sls_management_console_authentication_failures_alert_enabled_test.py b/tests/providers/alibabacloud/services/sls/sls_management_console_authentication_failures_alert_enabled/sls_management_console_authentication_failures_alert_enabled_test.py new file mode 100644 index 0000000000..37346b331d --- /dev/null +++ b/tests/providers/alibabacloud/services/sls/sls_management_console_authentication_failures_alert_enabled/sls_management_console_authentication_failures_alert_enabled_test.py @@ -0,0 +1,68 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestSlsManagementConsoleAuthenticationFailuresAlertEnabled: + def test_alert_present_passes(self): + sls_client = mock.MagicMock() + sls_client.provider = set_mocked_alibabacloud_provider() + sls_client.audited_account = "1234567890" + alert = mock.MagicMock() + alert.name = "auth-failures" + alert.arn = "arn:log:alert/auth-failures" + alert.region = "cn-hangzhou" + alert.configuration = { + "queryList": [{"query": "ConsoleSignin | event.errorCode"}] + } + sls_client.alerts = [alert] + sls_client.region = "cn-hangzhou" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=sls_client.provider, + ), + mock.patch( + "prowler.providers.alibabacloud.services.sls.sls_management_console_authentication_failures_alert_enabled.sls_management_console_authentication_failures_alert_enabled.sls_client", + new=sls_client, + ), + ): + from prowler.providers.alibabacloud.services.sls.sls_management_console_authentication_failures_alert_enabled.sls_management_console_authentication_failures_alert_enabled import ( + sls_management_console_authentication_failures_alert_enabled, + ) + + check = sls_management_console_authentication_failures_alert_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + + def test_no_alert_fails(self): + sls_client = mock.MagicMock() + sls_client.provider = set_mocked_alibabacloud_provider() + sls_client.audited_account = "1234567890" + sls_client.alerts = [] + sls_client.region = "cn-hangzhou" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=sls_client.provider, + ), + mock.patch( + "prowler.providers.alibabacloud.services.sls.sls_management_console_authentication_failures_alert_enabled.sls_management_console_authentication_failures_alert_enabled.sls_client", + new=sls_client, + ), + ): + from prowler.providers.alibabacloud.services.sls.sls_management_console_authentication_failures_alert_enabled.sls_management_console_authentication_failures_alert_enabled import ( + sls_management_console_authentication_failures_alert_enabled, + ) + + check = sls_management_console_authentication_failures_alert_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" diff --git a/tests/providers/alibabacloud/services/sls/sls_management_console_signin_without_mfa_alert_enabled/__init__.py b/tests/providers/alibabacloud/services/sls/sls_management_console_signin_without_mfa_alert_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/sls/sls_management_console_signin_without_mfa_alert_enabled/sls_management_console_signin_without_mfa_alert_enabled_test.py b/tests/providers/alibabacloud/services/sls/sls_management_console_signin_without_mfa_alert_enabled/sls_management_console_signin_without_mfa_alert_enabled_test.py new file mode 100644 index 0000000000..3bd8cf927a --- /dev/null +++ b/tests/providers/alibabacloud/services/sls/sls_management_console_signin_without_mfa_alert_enabled/sls_management_console_signin_without_mfa_alert_enabled_test.py @@ -0,0 +1,68 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestSlsManagementConsoleSigninWithoutMfaAlertEnabled: + def test_alert_present_passes(self): + sls_client = mock.MagicMock() + sls_client.provider = set_mocked_alibabacloud_provider() + sls_client.audited_account = "1234567890" + alert = mock.MagicMock() + alert.name = "signin-without-mfa" + alert.arn = "arn:log:alert/signin-without-mfa" + alert.region = "cn-hangzhou" + alert.configuration = { + "queryList": [{"query": "ConsoleSignin | addionalEventData.loginAccount"}] + } + sls_client.alerts = [alert] + sls_client.region = "cn-hangzhou" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=sls_client.provider, + ), + mock.patch( + "prowler.providers.alibabacloud.services.sls.sls_management_console_signin_without_mfa_alert_enabled.sls_management_console_signin_without_mfa_alert_enabled.sls_client", + new=sls_client, + ), + ): + from prowler.providers.alibabacloud.services.sls.sls_management_console_signin_without_mfa_alert_enabled.sls_management_console_signin_without_mfa_alert_enabled import ( + sls_management_console_signin_without_mfa_alert_enabled, + ) + + check = sls_management_console_signin_without_mfa_alert_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + + def test_no_alert_fails(self): + sls_client = mock.MagicMock() + sls_client.provider = set_mocked_alibabacloud_provider() + sls_client.audited_account = "1234567890" + sls_client.alerts = [] + sls_client.region = "cn-hangzhou" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=sls_client.provider, + ), + mock.patch( + "prowler.providers.alibabacloud.services.sls.sls_management_console_signin_without_mfa_alert_enabled.sls_management_console_signin_without_mfa_alert_enabled.sls_client", + new=sls_client, + ), + ): + from prowler.providers.alibabacloud.services.sls.sls_management_console_signin_without_mfa_alert_enabled.sls_management_console_signin_without_mfa_alert_enabled import ( + sls_management_console_signin_without_mfa_alert_enabled, + ) + + check = sls_management_console_signin_without_mfa_alert_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" diff --git a/tests/providers/alibabacloud/services/sls/sls_root_account_usage_alert_enabled/__init__.py b/tests/providers/alibabacloud/services/sls/sls_root_account_usage_alert_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/sls/sls_root_account_usage_alert_enabled/sls_root_account_usage_alert_enabled_test.py b/tests/providers/alibabacloud/services/sls/sls_root_account_usage_alert_enabled/sls_root_account_usage_alert_enabled_test.py new file mode 100644 index 0000000000..c63f06d040 --- /dev/null +++ b/tests/providers/alibabacloud/services/sls/sls_root_account_usage_alert_enabled/sls_root_account_usage_alert_enabled_test.py @@ -0,0 +1,72 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestSlsRootAccountUsageAlertEnabled: + def test_alert_present_passes(self): + sls_client = mock.MagicMock() + sls_client.provider = set_mocked_alibabacloud_provider() + sls_client.audited_account = "1234567890" + alert = mock.MagicMock() + alert.name = "root-usage" + alert.arn = "arn:log:alert/root-usage" + alert.region = "cn-hangzhou" + alert.configuration = { + "queryList": [ + { + "query": "ConsoleSignin | event.userIdentity.type: root-account", + } + ] + } + sls_client.alerts = [alert] + sls_client.region = "cn-hangzhou" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=sls_client.provider, + ), + mock.patch( + "prowler.providers.alibabacloud.services.sls.sls_root_account_usage_alert_enabled.sls_root_account_usage_alert_enabled.sls_client", + new=sls_client, + ), + ): + from prowler.providers.alibabacloud.services.sls.sls_root_account_usage_alert_enabled.sls_root_account_usage_alert_enabled import ( + sls_root_account_usage_alert_enabled, + ) + + check = sls_root_account_usage_alert_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + + def test_no_alert_fails(self): + sls_client = mock.MagicMock() + sls_client.provider = set_mocked_alibabacloud_provider() + sls_client.audited_account = "1234567890" + sls_client.alerts = [] + sls_client.region = "cn-hangzhou" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=sls_client.provider, + ), + mock.patch( + "prowler.providers.alibabacloud.services.sls.sls_root_account_usage_alert_enabled.sls_root_account_usage_alert_enabled.sls_client", + new=sls_client, + ), + ): + from prowler.providers.alibabacloud.services.sls.sls_root_account_usage_alert_enabled.sls_root_account_usage_alert_enabled import ( + sls_root_account_usage_alert_enabled, + ) + + check = sls_root_account_usage_alert_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" diff --git a/tests/providers/alibabacloud/services/sls/sls_service_test.py b/tests/providers/alibabacloud/services/sls/sls_service_test.py new file mode 100644 index 0000000000..bd4db7851c --- /dev/null +++ b/tests/providers/alibabacloud/services/sls/sls_service_test.py @@ -0,0 +1,24 @@ +from unittest.mock import patch + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestSLSService: + def test_service(self): + alibabacloud_provider = set_mocked_alibabacloud_provider() + + with patch( + "prowler.providers.alibabacloud.services.sls.sls_service.Sls.__init__", + return_value=None, + ): + from prowler.providers.alibabacloud.services.sls.sls_service import Sls + + sls_client = Sls(alibabacloud_provider) + sls_client.service = "sls" + sls_client.provider = alibabacloud_provider + sls_client.regional_clients = {} + + assert sls_client.service == "sls" + assert sls_client.provider == alibabacloud_provider diff --git a/tests/providers/alibabacloud/services/sls/sls_unauthorized_api_calls_alert_enabled/__init__.py b/tests/providers/alibabacloud/services/sls/sls_unauthorized_api_calls_alert_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/sls/sls_unauthorized_api_calls_alert_enabled/sls_unauthorized_api_calls_alert_enabled_test.py b/tests/providers/alibabacloud/services/sls/sls_unauthorized_api_calls_alert_enabled/sls_unauthorized_api_calls_alert_enabled_test.py new file mode 100644 index 0000000000..1de4fa5d7e --- /dev/null +++ b/tests/providers/alibabacloud/services/sls/sls_unauthorized_api_calls_alert_enabled/sls_unauthorized_api_calls_alert_enabled_test.py @@ -0,0 +1,70 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestSlsUnauthorizedApiCallsAlertEnabled: + def test_alert_present_passes(self): + sls_client = mock.MagicMock() + sls_client.provider = set_mocked_alibabacloud_provider() + sls_client.audited_account = "1234567890" + alert = mock.MagicMock() + alert.name = "unauth-api" + alert.arn = "arn:log:alert/unauth" + alert.region = "cn-hangzhou" + alert.configuration = { + "queryList": [{"query": "ApiCall | NoPermission"}], + } + sls_client.alerts = [alert] + sls_client.region = "cn-hangzhou" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.sls.sls_unauthorized_api_calls_alert_enabled.sls_unauthorized_api_calls_alert_enabled.sls_client", + new=sls_client, + ), + ): + from prowler.providers.alibabacloud.services.sls.sls_unauthorized_api_calls_alert_enabled.sls_unauthorized_api_calls_alert_enabled import ( + sls_unauthorized_api_calls_alert_enabled, + ) + + check = sls_unauthorized_api_calls_alert_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert "configured for unauthorized API calls" in result[0].status_extended + + def test_no_alert_fails(self): + sls_client = mock.MagicMock() + sls_client.provider = set_mocked_alibabacloud_provider() + sls_client.audited_account = "1234567890" + sls_client.alerts = [] + sls_client.region = "cn-hangzhou" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.sls.sls_unauthorized_api_calls_alert_enabled.sls_unauthorized_api_calls_alert_enabled.sls_client", + new=sls_client, + ), + ): + from prowler.providers.alibabacloud.services.sls.sls_unauthorized_api_calls_alert_enabled.sls_unauthorized_api_calls_alert_enabled import ( + sls_unauthorized_api_calls_alert_enabled, + ) + + check = sls_unauthorized_api_calls_alert_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert "No SLS Alert configured" in result[0].status_extended diff --git a/tests/providers/alibabacloud/services/vpc/__init__.py b/tests/providers/alibabacloud/services/vpc/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/vpc/vpc_flow_logs_enabled/__init__.py b/tests/providers/alibabacloud/services/vpc/vpc_flow_logs_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/alibabacloud/services/vpc/vpc_flow_logs_enabled/vpc_flow_logs_enabled_test.py b/tests/providers/alibabacloud/services/vpc/vpc_flow_logs_enabled/vpc_flow_logs_enabled_test.py new file mode 100644 index 0000000000..19faf528ca --- /dev/null +++ b/tests/providers/alibabacloud/services/vpc/vpc_flow_logs_enabled/vpc_flow_logs_enabled_test.py @@ -0,0 +1,77 @@ +from unittest import mock + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestVPCFlowLogsEnabled: + def test_vpc_without_flow_logs_fails(self): + vpc_client = mock.MagicMock() + vpc_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.vpc.vpc_flow_logs_enabled.vpc_flow_logs_enabled.vpc_client", + new=vpc_client, + ), + ): + from prowler.providers.alibabacloud.services.vpc.vpc_flow_logs_enabled.vpc_flow_logs_enabled import ( + vpc_flow_logs_enabled, + ) + from prowler.providers.alibabacloud.services.vpc.vpc_service import VPCs + + vpc = VPCs( + id="vpc-1", + name="vpc-1", + region="cn-hangzhou", + cidr_block="10.0.0.0/16", + flow_log_enabled=False, + ) + vpc_client.vpcs = {vpc.id: vpc} + + check = vpc_flow_logs_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert "does not have flow logs enabled" in result[0].status_extended + + def test_vpc_with_flow_logs_passes(self): + vpc_client = mock.MagicMock() + vpc_client.audited_account = "1234567890" + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_alibabacloud_provider(), + ), + mock.patch( + "prowler.providers.alibabacloud.services.vpc.vpc_flow_logs_enabled.vpc_flow_logs_enabled.vpc_client", + new=vpc_client, + ), + ): + from prowler.providers.alibabacloud.services.vpc.vpc_flow_logs_enabled.vpc_flow_logs_enabled import ( + vpc_flow_logs_enabled, + ) + from prowler.providers.alibabacloud.services.vpc.vpc_service import VPCs + + vpc = VPCs( + id="vpc-2", + name="vpc-2", + region="cn-hangzhou", + cidr_block="10.1.0.0/16", + flow_log_enabled=True, + ) + vpc_client.vpcs = {vpc.id: vpc} + + check = vpc_flow_logs_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert "has flow logs enabled" in result[0].status_extended diff --git a/tests/providers/alibabacloud/services/vpc/vpc_service_test.py b/tests/providers/alibabacloud/services/vpc/vpc_service_test.py new file mode 100644 index 0000000000..74fd7fcce1 --- /dev/null +++ b/tests/providers/alibabacloud/services/vpc/vpc_service_test.py @@ -0,0 +1,24 @@ +from unittest.mock import patch + +from tests.providers.alibabacloud.alibabacloud_fixtures import ( + set_mocked_alibabacloud_provider, +) + + +class TestVPCService: + def test_service(self): + alibabacloud_provider = set_mocked_alibabacloud_provider() + + with patch( + "prowler.providers.alibabacloud.services.vpc.vpc_service.VPC.__init__", + return_value=None, + ): + from prowler.providers.alibabacloud.services.vpc.vpc_service import VPC + + vpc_client = VPC(alibabacloud_provider) + vpc_client.service = "vpc" + vpc_client.provider = alibabacloud_provider + vpc_client.regional_clients = {} + + assert vpc_client.service == "vpc" + assert vpc_client.provider == alibabacloud_provider