mirror of
https://github.com/prowler-cloud/prowler.git
synced 2025-12-18 21:07:48 +00:00
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:
@@ -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 |
|
||||
|
||||
BIN
dashboard/assets/images/providers/alibabacloud_provider.png
Normal file
BIN
dashboard/assets/images/providers/alibabacloud_provider.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 90 KiB |
24
dashboard/compliance/cis_2_0_alibabacloud.py
Normal file
24
dashboard/compliance/cis_2_0_alibabacloud.py
Normal 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"
|
||||
)
|
||||
@@ -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}",
|
||||
),
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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": [
|
||||
|
||||
112
docs/user-guide/providers/alibabacloud/authentication.mdx
Normal file
112
docs/user-guide/providers/alibabacloud/authentication.mdx
Normal 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`
|
||||
@@ -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
767
poetry.lock
generated
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
0
prowler/compliance/alibabacloud/__init__.py
Normal file
0
prowler/compliance/alibabacloud/__init__.py
Normal file
1833
prowler/compliance/alibabacloud/cis_2.0_alibabacloud.json
Normal file
1833
prowler/compliance/alibabacloud/cis_2.0_alibabacloud.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -60,6 +60,7 @@ class Provider(str, Enum):
|
||||
NHN = "nhn"
|
||||
MONGODBATLAS = "mongodbatlas"
|
||||
ORACLECLOUD = "oraclecloud"
|
||||
ALIBABACLOUD = "alibabacloud"
|
||||
|
||||
|
||||
# Compliance
|
||||
|
||||
@@ -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"] = (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
106
prowler/lib/outputs/compliance/cis/cis_alibabacloud.py
Normal file
106
prowler/lib/outputs/compliance/cis/cis_alibabacloud.py
Normal 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)
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
"""
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
0
prowler/providers/alibabacloud/__init__.py
Normal file
0
prowler/providers/alibabacloud/__init__.py
Normal file
872
prowler/providers/alibabacloud/alibabacloud_provider.py
Normal file
872
prowler/providers/alibabacloud/alibabacloud_provider.py
Normal 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
|
||||
41
prowler/providers/alibabacloud/config.py
Normal file
41
prowler/providers/alibabacloud/config.py
Normal 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)",
|
||||
}
|
||||
116
prowler/providers/alibabacloud/exceptions/exceptions.py
Normal file
116
prowler/providers/alibabacloud/exceptions/exceptions.py
Normal 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
|
||||
)
|
||||
0
prowler/providers/alibabacloud/lib/__init__.py
Normal file
0
prowler/providers/alibabacloud/lib/__init__.py
Normal file
58
prowler/providers/alibabacloud/lib/arguments/arguments.py
Normal file
58
prowler/providers/alibabacloud/lib/arguments/arguments.py
Normal 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")
|
||||
175
prowler/providers/alibabacloud/lib/mutelist/mutelist.py
Normal file
175
prowler/providers/alibabacloud/lib/mutelist/mutelist.py
Normal 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
|
||||
113
prowler/providers/alibabacloud/lib/service/service.py
Normal file
113
prowler/providers/alibabacloud/lib/service/service.py
Normal 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
|
||||
266
prowler/providers/alibabacloud/models.py
Normal file
266
prowler/providers/alibabacloud/models.py
Normal 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
|
||||
0
prowler/providers/alibabacloud/services/__init__.py
Normal file
0
prowler/providers/alibabacloud/services/__init__.py
Normal 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())
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
4
prowler/providers/alibabacloud/services/cs/cs_client.py
Normal file
4
prowler/providers/alibabacloud/services/cs/cs_client.py
Normal 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())
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
|
||||
354
prowler/providers/alibabacloud/services/cs/cs_service.py
Normal file
354
prowler/providers/alibabacloud/services/cs/cs_service.py
Normal 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
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
|
||||
@@ -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())
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
|
||||
380
prowler/providers/alibabacloud/services/ecs/ecs_service.py
Normal file
380
prowler/providers/alibabacloud/services/ecs/ecs_service.py
Normal 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
|
||||
@@ -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
Reference in New Issue
Block a user