feat(alibaba): add Alibaba Cloud provider (#9329)

Co-authored-by: pedrooot <pedromarting3@gmail.com>
Co-authored-by: HugoPBrito <hugopbrit@gmail.com>
This commit is contained in:
Sergio Garcia
2025-12-03 11:47:55 -05:00
committed by GitHub
parent 53404dfa62
commit dbdce98cf2
371 changed files with 16974 additions and 10 deletions

View File

@@ -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 |

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

View File

@@ -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"
)

View File

@@ -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}",
),

View File

@@ -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")]

View File

@@ -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,
)

View File

@@ -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": [

View File

@@ -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`

View File

@@ -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**
<Warning>
Prowler does not accept credentials through command-line arguments. Provide credentials through environment variables or the Alibaba Cloud credential chain.
</Warning>
#### 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
```

767
poetry.lock generated
View File

@@ -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"

View File

@@ -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)

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -60,6 +60,7 @@ class Provider(str, Enum):
NHN = "nhn"
MONGODBATLAS = "mongodbatlas"
ORACLECLOUD = "oraclecloud"
ALIBABACLOUD = "alibabacloud"
# Compliance

View File

@@ -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"] = (

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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.

View File

@@ -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

View File

@@ -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"""
<li class="list-group-item">
<b>Account Name:</b> {account_name}
</li>"""
if account_name
else ""
)
return f"""
<div class="col-md-2">
<div class="card">
<div class="card-header">
Alibaba Cloud Assessment Summary
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item">
<b>Account ID:</b> {account_id}
</li>
{account_name_item}
<li class="list-group-item">
<b>Audited Regions:</b> {audited_regions}
</li>
</ul>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
Alibaba Cloud Credentials
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item">
<b>User Name:</b> {user_name}
</li>
<li class="list-group-item">
<b>Identity ARN:</b> {identity_arn}
</li>
</ul>
</div>
</div>"""
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}"
)
return ""
@staticmethod
def get_assessment_summary(provider: Provider) -> str:
"""

View File

@@ -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:

View File

@@ -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):

View File

@@ -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=<access_key>
- export ALIBABA_CLOUD_ACCESS_KEY_SECRET=<secret_key>
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

View File

@@ -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)",
}

View File

@@ -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
)

View File

@@ -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")

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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())

View File

@@ -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 <trail_name> --OssBucketName <oss_bucket_for_actiontrail> --RoleName aliyunactiontraildefaultrole --SlsProjectArn <sls_project_arn_for_actiontrail> --SlsWriteRoleArn <sls_role_arn_for_actiontrail> --EventRW <api_type_for_actiontrail>",
"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": ""
}

View File

@@ -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

View File

@@ -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://<bucketName> 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": ""
}

View File

@@ -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

View File

@@ -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

View File

@@ -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())

View File

@@ -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": ""
}

View File

@@ -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

View File

@@ -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": ""
}

View File

@@ -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

View File

@@ -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": ""
}

View File

@@ -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

View File

@@ -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": ""
}

View File

@@ -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

View File

@@ -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": ""
}

View File

@@ -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

View File

@@ -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": ""
}

View File

@@ -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

View File

@@ -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": ""
}

View File

@@ -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

View File

@@ -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": ""
}

View File

@@ -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

View File

@@ -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": ""
}

View File

@@ -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

View File

@@ -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

View File

@@ -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 <disk_name> --Size <size> --Encrypted true --KmsKeyId <kms_key_id>",
"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": ""
}

View File

@@ -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

View File

@@ -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())

View File

@@ -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": ""
}

View File

@@ -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

View File

@@ -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": ""
}

View File

@@ -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

View File

@@ -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 <instance_name> --ImageId <image_id> --InstanceType <instance_type> --VSwitchId <vswitch_id>",
"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": ""
}

View File

@@ -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

View File

@@ -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 <security_group_id> --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": ""
}

View File

@@ -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

View File

@@ -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 <security_group_id> --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": ""
}

View File

@@ -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

View File

@@ -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

View File

@@ -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 <disk_name> --Size <size> --Encrypted true --KmsKeyId <kms_key_id>",
"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": ""
}

Some files were not shown because too many files have changed in this diff Show More