mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-03-22 03:08:23 +00:00
Compare commits
13 Commits
9ae35029dc
...
add-search
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bcf3fd3683 | ||
|
|
8e2ae3631d | ||
|
|
5e0463abce | ||
|
|
c54ca54f70 | ||
|
|
eff42ff9fc | ||
|
|
56c8be226d | ||
|
|
358036d951 | ||
|
|
a7710ae2aa | ||
|
|
5cd4466d9a | ||
|
|
1b079eb14a | ||
|
|
87637b50f5 | ||
|
|
cb125d5cee | ||
|
|
7a21e04d90 |
@@ -5,11 +5,13 @@ All notable changes to the **Prowler API** are documented in this file.
|
||||
## [1.16.0] (Unreleased)
|
||||
|
||||
### Added
|
||||
|
||||
- New endpoint to retrieve an overview of the attack surfaces [(#9309)](https://github.com/prowler-cloud/prowler/pull/9309)
|
||||
- New endpoint `GET /api/v1/overviews/findings_severity/timeseries` to retrieve daily aggregated findings by severity level [(#9363)](https://github.com/prowler-cloud/prowler/pull/9363)
|
||||
- Lighthouse AI support for Amazon Bedrock API key [(#9343)](https://github.com/prowler-cloud/prowler/pull/9343)
|
||||
- Exception handler for provider deletions during scans [(#9414)](https://github.com/prowler-cloud/prowler/pull/9414)
|
||||
- Support to use admin credentials through the read replica database [(#9440)](https://github.com/prowler-cloud/prowler/pull/9440)
|
||||
- Support AlibabaCloud provider [(#9485)](https://github.com/prowler-cloud/prowler/pull/9485)
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
37
api/src/backend/api/migrations/0062_alibabacloud_provider.py
Normal file
37
api/src/backend/api/migrations/0062_alibabacloud_provider.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# Generated by Django migration for Alibaba Cloud provider support
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
import api.db_utils
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("api", "0061_daily_severity_summary"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="provider",
|
||||
name="provider",
|
||||
field=api.db_utils.ProviderEnumField(
|
||||
choices=[
|
||||
("aws", "AWS"),
|
||||
("azure", "Azure"),
|
||||
("gcp", "GCP"),
|
||||
("kubernetes", "Kubernetes"),
|
||||
("m365", "M365"),
|
||||
("github", "GitHub"),
|
||||
("mongodbatlas", "MongoDB Atlas"),
|
||||
("iac", "IaC"),
|
||||
("oraclecloud", "Oracle Cloud Infrastructure"),
|
||||
("alibabacloud", "Alibaba Cloud"),
|
||||
],
|
||||
default="aws",
|
||||
),
|
||||
),
|
||||
migrations.RunSQL(
|
||||
"ALTER TYPE provider ADD VALUE IF NOT EXISTS 'alibabacloud';",
|
||||
reverse_sql=migrations.RunSQL.noop,
|
||||
),
|
||||
]
|
||||
@@ -287,6 +287,7 @@ class Provider(RowLevelSecurityProtectedModel):
|
||||
MONGODBATLAS = "mongodbatlas", _("MongoDB Atlas")
|
||||
IAC = "iac", _("IaC")
|
||||
ORACLECLOUD = "oraclecloud", _("Oracle Cloud Infrastructure")
|
||||
ALIBABACLOUD = "alibabacloud", _("Alibaba Cloud")
|
||||
|
||||
@staticmethod
|
||||
def validate_aws_uid(value):
|
||||
@@ -391,6 +392,15 @@ class Provider(RowLevelSecurityProtectedModel):
|
||||
pointer="/data/attributes/uid",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def validate_alibabacloud_uid(value):
|
||||
if not re.match(r"^\d{16}$", value):
|
||||
raise ModelValidationError(
|
||||
detail="Alibaba Cloud account ID must be exactly 16 digits.",
|
||||
code="alibabacloud-uid",
|
||||
pointer="/data/attributes/uid",
|
||||
)
|
||||
|
||||
id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
|
||||
inserted_at = models.DateTimeField(auto_now_add=True, editable=False)
|
||||
updated_at = models.DateTimeField(auto_now=True, editable=False)
|
||||
|
||||
@@ -880,6 +880,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -890,6 +891,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -907,6 +909,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -919,6 +922,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -1419,6 +1423,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -1429,6 +1434,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -1446,6 +1452,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -1458,6 +1465,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -1866,6 +1874,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -1876,6 +1885,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -1893,6 +1903,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -1905,6 +1916,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -2311,6 +2323,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -2321,6 +2334,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -2338,6 +2352,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -2350,6 +2365,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -2744,6 +2760,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -2754,6 +2771,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -2771,6 +2789,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -2783,6 +2802,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -4635,6 +4655,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -4645,6 +4666,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -4662,6 +4684,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -4674,6 +4697,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -4824,6 +4848,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -4834,6 +4859,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -4851,6 +4877,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -4863,6 +4890,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -5005,6 +5033,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -5015,6 +5044,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -5031,6 +5061,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -5043,6 +5074,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
explode: false
|
||||
style: form
|
||||
- name: filter[search]
|
||||
@@ -5229,6 +5261,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -5239,6 +5272,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -5256,6 +5290,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -5268,6 +5303,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -5401,6 +5437,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -5411,6 +5448,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -5428,6 +5466,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -5440,6 +5479,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -6220,6 +6260,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -6230,6 +6271,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
- in: query
|
||||
name: filter[provider__in]
|
||||
schema:
|
||||
@@ -6247,6 +6289,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -6259,6 +6302,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -6276,6 +6320,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -6286,6 +6331,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -6303,6 +6349,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -6315,6 +6362,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
explode: false
|
||||
style: form
|
||||
- name: filter[search]
|
||||
@@ -6926,6 +6974,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -6936,6 +6985,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -6953,6 +7003,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -6965,6 +7016,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -7309,6 +7361,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -7319,6 +7372,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -7336,6 +7390,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -7348,6 +7403,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -7587,6 +7643,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -7597,6 +7654,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -7614,6 +7672,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -7626,6 +7685,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -7871,6 +7931,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -7881,6 +7942,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -7898,6 +7960,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -7910,6 +7973,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -8718,6 +8782,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
* `azure` - Azure
|
||||
@@ -8728,6 +8793,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -8745,6 +8811,7 @@ paths:
|
||||
- m365
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
description: |-
|
||||
Multiple values may be separated by commas.
|
||||
|
||||
@@ -8757,6 +8824,7 @@ paths:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -16199,6 +16267,7 @@ components:
|
||||
- mongodbatlas
|
||||
- iac
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
type: string
|
||||
description: |-
|
||||
* `aws` - AWS
|
||||
@@ -16210,6 +16279,7 @@ components:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
x-spec-enum-id: eca8c51e6bd28935
|
||||
uid:
|
||||
type: string
|
||||
@@ -16325,6 +16395,7 @@ components:
|
||||
- mongodbatlas
|
||||
- iac
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
type: string
|
||||
x-spec-enum-id: eca8c51e6bd28935
|
||||
description: |-
|
||||
@@ -16339,6 +16410,7 @@ components:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
uid:
|
||||
type: string
|
||||
title: Unique identifier for the provider, set by the provider
|
||||
@@ -16385,6 +16457,7 @@ components:
|
||||
- mongodbatlas
|
||||
- iac
|
||||
- oraclecloud
|
||||
- alibabacloud
|
||||
type: string
|
||||
x-spec-enum-id: eca8c51e6bd28935
|
||||
description: |-
|
||||
@@ -16399,6 +16472,7 @@ components:
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
* `alibabacloud` - Alibaba Cloud
|
||||
uid:
|
||||
type: string
|
||||
minLength: 3
|
||||
|
||||
@@ -16,6 +16,7 @@ from api.utils import (
|
||||
return_prowler_provider,
|
||||
validate_invitation,
|
||||
)
|
||||
from prowler.providers.alibabacloud.alibabacloud_provider import AlibabacloudProvider
|
||||
from prowler.providers.aws.aws_provider import AwsProvider
|
||||
from prowler.providers.aws.lib.security_hub.security_hub import SecurityHubConnection
|
||||
from prowler.providers.azure.azure_provider import AzureProvider
|
||||
@@ -116,6 +117,7 @@ class TestReturnProwlerProvider:
|
||||
(Provider.ProviderChoices.MONGODBATLAS.value, MongodbatlasProvider),
|
||||
(Provider.ProviderChoices.ORACLECLOUD.value, OraclecloudProvider),
|
||||
(Provider.ProviderChoices.IAC.value, IacProvider),
|
||||
(Provider.ProviderChoices.ALIBABACLOUD.value, AlibabacloudProvider),
|
||||
],
|
||||
)
|
||||
def test_return_prowler_provider(self, provider_type, expected_provider):
|
||||
|
||||
@@ -1165,6 +1165,11 @@ class TestProviderViewSet:
|
||||
"uid": "64b1d3c0e4b03b1234567890",
|
||||
"alias": "Atlas Organization",
|
||||
},
|
||||
{
|
||||
"provider": "alibabacloud",
|
||||
"uid": "1234567890123456",
|
||||
"alias": "Alibaba Cloud Account",
|
||||
},
|
||||
]
|
||||
),
|
||||
)
|
||||
@@ -1514,6 +1519,36 @@ class TestProviderViewSet:
|
||||
"mongodbatlas-uid",
|
||||
"uid",
|
||||
),
|
||||
# Alibaba Cloud UID validation - too short (not 16 digits)
|
||||
(
|
||||
{
|
||||
"provider": "alibabacloud",
|
||||
"uid": "123456789012345",
|
||||
"alias": "test",
|
||||
},
|
||||
"alibabacloud-uid",
|
||||
"uid",
|
||||
),
|
||||
# Alibaba Cloud UID validation - too long (not 16 digits)
|
||||
(
|
||||
{
|
||||
"provider": "alibabacloud",
|
||||
"uid": "12345678901234567",
|
||||
"alias": "test",
|
||||
},
|
||||
"alibabacloud-uid",
|
||||
"uid",
|
||||
),
|
||||
# Alibaba Cloud UID validation - contains non-digits
|
||||
(
|
||||
{
|
||||
"provider": "alibabacloud",
|
||||
"uid": "123456789012345a",
|
||||
"alias": "test",
|
||||
},
|
||||
"alibabacloud-uid",
|
||||
"uid",
|
||||
),
|
||||
]
|
||||
),
|
||||
)
|
||||
@@ -2251,6 +2286,46 @@ class TestProviderSecretViewSet:
|
||||
"atlas_private_key": "private-key",
|
||||
},
|
||||
),
|
||||
# Alibaba Cloud credentials (with access key only)
|
||||
(
|
||||
Provider.ProviderChoices.ALIBABACLOUD.value,
|
||||
ProviderSecret.TypeChoices.STATIC,
|
||||
{
|
||||
"access_key_id": "LTAI5t1234567890abcdef",
|
||||
"access_key_secret": "my-secret-access-key",
|
||||
},
|
||||
),
|
||||
# Alibaba Cloud credentials (with STS security token)
|
||||
(
|
||||
Provider.ProviderChoices.ALIBABACLOUD.value,
|
||||
ProviderSecret.TypeChoices.STATIC,
|
||||
{
|
||||
"access_key_id": "LTAI5t1234567890abcdef",
|
||||
"access_key_secret": "my-secret-access-key",
|
||||
"security_token": "my-security-token-for-sts",
|
||||
},
|
||||
),
|
||||
# Alibaba Cloud RAM Role Assumption (minimal required fields)
|
||||
(
|
||||
Provider.ProviderChoices.ALIBABACLOUD.value,
|
||||
ProviderSecret.TypeChoices.ROLE,
|
||||
{
|
||||
"role_arn": "acs:ram::1234567890123456:role/ProwlerRole",
|
||||
"access_key_id": "LTAI5t1234567890abcdef",
|
||||
"access_key_secret": "my-secret-access-key",
|
||||
},
|
||||
),
|
||||
# Alibaba Cloud RAM Role Assumption (with optional role_session_name)
|
||||
(
|
||||
Provider.ProviderChoices.ALIBABACLOUD.value,
|
||||
ProviderSecret.TypeChoices.ROLE,
|
||||
{
|
||||
"role_arn": "acs:ram::1234567890123456:role/ProwlerRole",
|
||||
"access_key_id": "LTAI5t1234567890abcdef",
|
||||
"access_key_secret": "my-secret-access-key",
|
||||
"role_session_name": "ProwlerAuditSession",
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_provider_secrets_create_valid(
|
||||
|
||||
@@ -11,6 +11,7 @@ from api.exceptions import InvitationTokenExpiredException
|
||||
from api.models import Integration, Invitation, Processor, Provider, Resource
|
||||
from api.v1.serializers import FindingMetadataSerializer
|
||||
from prowler.lib.outputs.jira.jira import Jira, JiraBasicAuthError
|
||||
from prowler.providers.alibabacloud.alibabacloud_provider import AlibabacloudProvider
|
||||
from prowler.providers.aws.aws_provider import AwsProvider
|
||||
from prowler.providers.aws.lib.s3.s3 import S3
|
||||
from prowler.providers.aws.lib.security_hub.security_hub import SecurityHub
|
||||
@@ -63,8 +64,9 @@ def merge_dicts(default_dict: dict, replacement_dict: dict) -> dict:
|
||||
|
||||
def return_prowler_provider(
|
||||
provider: Provider,
|
||||
) -> [
|
||||
AwsProvider
|
||||
) -> (
|
||||
AlibabacloudProvider
|
||||
| AwsProvider
|
||||
| AzureProvider
|
||||
| GcpProvider
|
||||
| GithubProvider
|
||||
@@ -73,14 +75,14 @@ def return_prowler_provider(
|
||||
| M365Provider
|
||||
| MongodbatlasProvider
|
||||
| OraclecloudProvider
|
||||
]:
|
||||
):
|
||||
"""Return the Prowler provider class based on the given provider type.
|
||||
|
||||
Args:
|
||||
provider (Provider): The provider object containing the provider type and associated secrets.
|
||||
|
||||
Returns:
|
||||
AwsProvider | AzureProvider | GcpProvider | GithubProvider | IacProvider | KubernetesProvider | M365Provider | OraclecloudProvider | MongodbatlasProvider: The corresponding provider class.
|
||||
AlibabacloudProvider | AwsProvider | AzureProvider | GcpProvider | GithubProvider | IacProvider | KubernetesProvider | M365Provider | MongodbatlasProvider | OraclecloudProvider: The corresponding provider class.
|
||||
|
||||
Raises:
|
||||
ValueError: If the provider type specified in `provider.provider` is not supported.
|
||||
@@ -104,6 +106,8 @@ def return_prowler_provider(
|
||||
prowler_provider = IacProvider
|
||||
case Provider.ProviderChoices.ORACLECLOUD.value:
|
||||
prowler_provider = OraclecloudProvider
|
||||
case Provider.ProviderChoices.ALIBABACLOUD.value:
|
||||
prowler_provider = AlibabacloudProvider
|
||||
case _:
|
||||
raise ValueError(f"Provider type {provider.provider} not supported")
|
||||
return prowler_provider
|
||||
@@ -169,7 +173,8 @@ def initialize_prowler_provider(
|
||||
provider: Provider,
|
||||
mutelist_processor: Processor | None = None,
|
||||
) -> (
|
||||
AwsProvider
|
||||
AlibabacloudProvider
|
||||
| AwsProvider
|
||||
| AzureProvider
|
||||
| GcpProvider
|
||||
| GithubProvider
|
||||
@@ -186,9 +191,8 @@ def initialize_prowler_provider(
|
||||
mutelist_processor (Processor): The mutelist processor object containing the mutelist configuration.
|
||||
|
||||
Returns:
|
||||
AwsProvider | AzureProvider | GcpProvider | GithubProvider | IacProvider | KubernetesProvider | M365Provider | OraclecloudProvider | MongodbatlasProvider: An instance of the corresponding provider class
|
||||
(`AwsProvider`, `AzureProvider`, `GcpProvider`, `GithubProvider`, `IacProvider`, `KubernetesProvider`, `M365Provider`, `OraclecloudProvider` or `MongodbatlasProvider`) initialized with the
|
||||
provider's secrets.
|
||||
AlibabacloudProvider | AwsProvider | AzureProvider | GcpProvider | GithubProvider | IacProvider | KubernetesProvider | M365Provider | MongodbatlasProvider | OraclecloudProvider: An instance of the corresponding provider class
|
||||
initialized with the provider's secrets.
|
||||
"""
|
||||
prowler_provider = return_prowler_provider(provider)
|
||||
prowler_provider_kwargs = get_prowler_provider_kwargs(provider, mutelist_processor)
|
||||
|
||||
@@ -304,6 +304,48 @@ from rest_framework_json_api import serializers
|
||||
},
|
||||
"required": ["atlas_public_key", "atlas_private_key"],
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"title": "Alibaba Cloud Static Credentials",
|
||||
"properties": {
|
||||
"access_key_id": {
|
||||
"type": "string",
|
||||
"description": "The Alibaba Cloud access key ID for authentication.",
|
||||
},
|
||||
"access_key_secret": {
|
||||
"type": "string",
|
||||
"description": "The Alibaba Cloud access key secret for authentication.",
|
||||
},
|
||||
"security_token": {
|
||||
"type": "string",
|
||||
"description": "The STS security token for temporary credentials (optional).",
|
||||
},
|
||||
},
|
||||
"required": ["access_key_id", "access_key_secret"],
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"title": "Alibaba Cloud RAM Role Assumption",
|
||||
"properties": {
|
||||
"role_arn": {
|
||||
"type": "string",
|
||||
"description": "The ARN of the RAM role to assume (e.g., acs:ram::1234567890123456:role/ProwlerRole).",
|
||||
},
|
||||
"access_key_id": {
|
||||
"type": "string",
|
||||
"description": "The Alibaba Cloud access key ID of the RAM user that will assume the role.",
|
||||
},
|
||||
"access_key_secret": {
|
||||
"type": "string",
|
||||
"description": "The Alibaba Cloud access key secret of the RAM user that will assume the role.",
|
||||
},
|
||||
"role_session_name": {
|
||||
"type": "string",
|
||||
"description": "An identifier for the role session (optional, defaults to 'ProwlerSession').",
|
||||
},
|
||||
},
|
||||
"required": ["role_arn", "access_key_id", "access_key_secret"],
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1388,12 +1388,23 @@ class BaseWriteProviderSecretSerializer(BaseWriteSerializer):
|
||||
serializer = OracleCloudProviderSecret(data=secret)
|
||||
elif provider_type == Provider.ProviderChoices.MONGODBATLAS.value:
|
||||
serializer = MongoDBAtlasProviderSecret(data=secret)
|
||||
elif provider_type == Provider.ProviderChoices.ALIBABACLOUD.value:
|
||||
serializer = AlibabaCloudProviderSecret(data=secret)
|
||||
else:
|
||||
raise serializers.ValidationError(
|
||||
{"provider": f"Provider type not supported {provider_type}"}
|
||||
)
|
||||
elif secret_type == ProviderSecret.TypeChoices.ROLE:
|
||||
serializer = AWSRoleAssumptionProviderSecret(data=secret)
|
||||
if provider_type == Provider.ProviderChoices.AWS.value:
|
||||
serializer = AWSRoleAssumptionProviderSecret(data=secret)
|
||||
elif provider_type == Provider.ProviderChoices.ALIBABACLOUD.value:
|
||||
serializer = AlibabaCloudRoleAssumptionProviderSecret(data=secret)
|
||||
else:
|
||||
raise serializers.ValidationError(
|
||||
{
|
||||
"secret_type": f"Role assumption not supported for provider type: {provider_type}"
|
||||
}
|
||||
)
|
||||
elif secret_type == ProviderSecret.TypeChoices.SERVICE_ACCOUNT:
|
||||
serializer = GCPServiceAccountProviderSecret(data=secret)
|
||||
else:
|
||||
@@ -1530,6 +1541,40 @@ class OracleCloudProviderSecret(serializers.Serializer):
|
||||
resource_name = "provider-secrets"
|
||||
|
||||
|
||||
class AlibabaCloudProviderSecret(serializers.Serializer):
|
||||
access_key_id = serializers.CharField()
|
||||
access_key_secret = serializers.CharField()
|
||||
security_token = serializers.CharField(required=False)
|
||||
|
||||
class Meta:
|
||||
resource_name = "provider-secrets"
|
||||
|
||||
|
||||
class AlibabaCloudRoleAssumptionProviderSecret(serializers.Serializer):
|
||||
"""Serializer for Alibaba Cloud RAM Role Assumption credentials.
|
||||
|
||||
This allows assuming a RAM role using access keys, similar to AWS STS AssumeRole.
|
||||
The SDK will automatically manage the STS token refresh.
|
||||
"""
|
||||
|
||||
role_arn = serializers.CharField(
|
||||
help_text="ARN of the RAM role to assume (e.g., acs:ram::1234567890123456:role/ProwlerRole)"
|
||||
)
|
||||
access_key_id = serializers.CharField(
|
||||
help_text="Access Key ID of the RAM user that will assume the role"
|
||||
)
|
||||
access_key_secret = serializers.CharField(
|
||||
help_text="Access Key Secret of the RAM user that will assume the role"
|
||||
)
|
||||
role_session_name = serializers.CharField(
|
||||
required=False,
|
||||
help_text="Session name for the assumed role session (optional, defaults to 'ProwlerSession')",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
resource_name = "provider-secrets"
|
||||
|
||||
|
||||
class AWSRoleAssumptionProviderSecret(serializers.Serializer):
|
||||
role_arn = serializers.CharField()
|
||||
external_id = serializers.CharField()
|
||||
|
||||
@@ -513,6 +513,12 @@ def providers_fixture(tenants_fixture):
|
||||
alias="mongodbatlas_testing",
|
||||
tenant_id=tenant.id,
|
||||
)
|
||||
provider9 = Provider.objects.create(
|
||||
provider="alibabacloud",
|
||||
uid="1234567890123456",
|
||||
alias="alibabacloud_testing",
|
||||
tenant_id=tenant.id,
|
||||
)
|
||||
|
||||
return (
|
||||
provider1,
|
||||
@@ -523,6 +529,7 @@ def providers_fixture(tenants_fixture):
|
||||
provider6,
|
||||
provider7,
|
||||
provider8,
|
||||
provider9,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -27,6 +27,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
|
||||
@@ -128,6 +129,9 @@ COMPLIANCE_CLASS_MAP = {
|
||||
"oraclecloud": [
|
||||
(lambda name: name.startswith("cis_"), OracleCloudCIS),
|
||||
],
|
||||
"alibabacloud": [
|
||||
(lambda name: name.startswith("cis_"), AlibabaCloudCIS),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -11,12 +11,12 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
- `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)
|
||||
- `compute_instance_deletion_protection_enabled` check for GCP provider [(#9358)](https://github.com/prowler-cloud/prowler/pull/9358)
|
||||
- Add needed changes to AlibabaCloud provider from the API [(#9485)](https://github.com/prowler-cloud/prowler/pull/9485)
|
||||
- Update SOC2 - Azure with Processing Integrity requirements [(#9463)](https://github.com/prowler-cloud/prowler/pull/9463)
|
||||
- Update SOC2 - GCP with Processing Integrity requirements [(#9464)](https://github.com/prowler-cloud/prowler/pull/9464)
|
||||
- Update SOC2 - AWS with Processing Integrity requirements [(#9462)](https://github.com/prowler-cloud/prowler/pull/9462)
|
||||
- RBI Cyber Security Framework compliance for Azure provider [(#8822)](https://github.com/prowler-cloud/prowler/pull/8822)
|
||||
|
||||
|
||||
### Changed
|
||||
- Update AWS Macie service metadata to new format [(#9265)](https://github.com/prowler-cloud/prowler/pull/9265)
|
||||
- Update AWS Lightsail service metadata to new format [(#9264)](https://github.com/prowler-cloud/prowler/pull/9264)
|
||||
|
||||
@@ -75,6 +75,9 @@ class AlibabacloudProvider(Provider):
|
||||
mutelist_path: str = None,
|
||||
mutelist_content: dict = None,
|
||||
fixer_config: dict = {},
|
||||
access_key_id: str = None,
|
||||
access_key_secret: str = None,
|
||||
security_token: str = None,
|
||||
):
|
||||
"""
|
||||
Initialize the AlibabaCloudProvider.
|
||||
@@ -91,6 +94,9 @@ class AlibabacloudProvider(Provider):
|
||||
mutelist_path: Path to the mutelist file
|
||||
mutelist_content: Content of the mutelist file
|
||||
fixer_config: Fixer configuration dictionary
|
||||
access_key_id: Alibaba Cloud Access Key ID
|
||||
access_key_secret: Alibaba Cloud Access Key Secret
|
||||
security_token: STS Security Token (for temporary credentials)
|
||||
|
||||
Raises:
|
||||
AlibabaCloudSetUpSessionError: If an error occurs during the setup process.
|
||||
@@ -107,6 +113,7 @@ class AlibabacloudProvider(Provider):
|
||||
- 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")
|
||||
- alibabacloud = AlibabacloudProvider(access_key_id="LTAI...", access_key_secret="...")
|
||||
"""
|
||||
logger.info("Initializing Alibaba Cloud Provider ...")
|
||||
|
||||
@@ -118,6 +125,9 @@ class AlibabacloudProvider(Provider):
|
||||
ecs_ram_role=ecs_ram_role,
|
||||
oidc_role_arn=oidc_role_arn,
|
||||
credentials_uri=credentials_uri,
|
||||
access_key_id=access_key_id,
|
||||
access_key_secret=access_key_secret,
|
||||
security_token=security_token,
|
||||
)
|
||||
logger.info("Alibaba Cloud session configured successfully")
|
||||
|
||||
@@ -234,6 +244,9 @@ class AlibabacloudProvider(Provider):
|
||||
ecs_ram_role: str = None,
|
||||
oidc_role_arn: str = None,
|
||||
credentials_uri: str = None,
|
||||
access_key_id: str = None,
|
||||
access_key_secret: str = None,
|
||||
security_token: str = None,
|
||||
) -> AlibabaCloudSession:
|
||||
"""
|
||||
Set up the Alibaba Cloud session.
|
||||
@@ -244,6 +257,9 @@ class AlibabacloudProvider(Provider):
|
||||
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
|
||||
access_key_id: Alibaba Cloud Access Key ID
|
||||
access_key_secret: Alibaba Cloud Access Key Secret
|
||||
security_token: STS Security Token (for temporary credentials)
|
||||
|
||||
Returns:
|
||||
AlibabaCloudSession object
|
||||
@@ -275,25 +291,22 @@ class AlibabacloudProvider(Provider):
|
||||
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
|
||||
# Check for access key credentials from parameters first, then fall back to environment variables
|
||||
# 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 not access_key_id:
|
||||
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_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"]
|
||||
if not access_key_secret:
|
||||
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:
|
||||
if not security_token and "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
|
||||
@@ -695,6 +708,9 @@ class AlibabacloudProvider(Provider):
|
||||
|
||||
@staticmethod
|
||||
def test_connection(
|
||||
access_key_id: str = None,
|
||||
access_key_secret: str = None,
|
||||
security_token: str = None,
|
||||
role_arn: str = None,
|
||||
role_session_name: str = None,
|
||||
ecs_ram_role: str = None,
|
||||
@@ -707,6 +723,9 @@ class AlibabacloudProvider(Provider):
|
||||
Test the connection to Alibaba Cloud with the provided credentials.
|
||||
|
||||
Args:
|
||||
access_key_id: Alibaba Cloud Access Key ID (for static credentials)
|
||||
access_key_secret: Alibaba Cloud Access Key Secret (for static credentials)
|
||||
security_token: STS Security Token (for temporary credentials)
|
||||
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
|
||||
@@ -734,17 +753,24 @@ class AlibabacloudProvider(Provider):
|
||||
raise_on_exception=False
|
||||
)
|
||||
Connection(is_connected=True, Error=None)
|
||||
>>> AlibabacloudProvider.test_connection(
|
||||
access_key_id="LTAI...",
|
||||
access_key_secret="...",
|
||||
raise_on_exception=False
|
||||
)
|
||||
Connection(is_connected=True, Error=None)
|
||||
"""
|
||||
try:
|
||||
session = None
|
||||
|
||||
# Setup session
|
||||
# Setup session - pass credentials directly instead of using env vars
|
||||
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,
|
||||
access_key_id=access_key_id,
|
||||
access_key_secret=access_key_secret,
|
||||
security_token=security_token,
|
||||
)
|
||||
|
||||
# Validate credentials
|
||||
@@ -755,10 +781,6 @@ class AlibabacloudProvider(Provider):
|
||||
|
||||
# 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}'",
|
||||
|
||||
671
tests/providers/alibabacloud/alibabacloud_provider_test.py
Normal file
671
tests/providers/alibabacloud/alibabacloud_provider_test.py
Normal file
@@ -0,0 +1,671 @@
|
||||
import os
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from prowler.providers.alibabacloud.alibabacloud_provider import AlibabacloudProvider
|
||||
from prowler.providers.alibabacloud.exceptions.exceptions import (
|
||||
AlibabaCloudInvalidCredentialsError,
|
||||
AlibabaCloudSetUpSessionError,
|
||||
)
|
||||
from prowler.providers.alibabacloud.models import AlibabaCloudCallerIdentity
|
||||
from prowler.providers.common.models import Connection
|
||||
|
||||
|
||||
class TestAlibabacloudProviderTestConnection:
|
||||
"""Tests for the AlibabacloudProvider.test_connection method."""
|
||||
|
||||
def test_test_connection_with_static_credentials_success(self):
|
||||
"""Test successful connection with static access key credentials."""
|
||||
mock_session = MagicMock()
|
||||
mock_caller_identity = AlibabaCloudCallerIdentity(
|
||||
account_id="1234567890",
|
||||
principal_id="123456",
|
||||
arn="acs:ram::1234567890:user/test-user",
|
||||
identity_type="RamUser",
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"setup_session",
|
||||
return_value=mock_session,
|
||||
) as mock_setup_session,
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"validate_credentials",
|
||||
return_value=mock_caller_identity,
|
||||
) as mock_validate_credentials,
|
||||
):
|
||||
result = AlibabacloudProvider.test_connection(
|
||||
access_key_id="LTAI1234567890",
|
||||
access_key_secret="test-secret-key",
|
||||
raise_on_exception=False,
|
||||
)
|
||||
|
||||
assert isinstance(result, Connection)
|
||||
assert result.is_connected is True
|
||||
assert result.error is None
|
||||
mock_setup_session.assert_called_once()
|
||||
mock_validate_credentials.assert_called_once()
|
||||
|
||||
def test_test_connection_with_sts_token_success(self):
|
||||
"""Test successful connection with STS temporary credentials."""
|
||||
mock_session = MagicMock()
|
||||
mock_caller_identity = AlibabaCloudCallerIdentity(
|
||||
account_id="1234567890",
|
||||
principal_id="123456",
|
||||
arn="acs:ram::1234567890:user/test-user",
|
||||
identity_type="RamUser",
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"setup_session",
|
||||
return_value=mock_session,
|
||||
),
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"validate_credentials",
|
||||
return_value=mock_caller_identity,
|
||||
),
|
||||
):
|
||||
result = AlibabacloudProvider.test_connection(
|
||||
access_key_id="STS.LTAI1234567890",
|
||||
access_key_secret="test-secret-key",
|
||||
security_token="test-security-token",
|
||||
raise_on_exception=False,
|
||||
)
|
||||
|
||||
assert isinstance(result, Connection)
|
||||
assert result.is_connected is True
|
||||
assert result.error is None
|
||||
|
||||
def test_test_connection_with_role_arn_success(self):
|
||||
"""Test successful connection with RAM role assumption."""
|
||||
mock_session = MagicMock()
|
||||
mock_caller_identity = AlibabaCloudCallerIdentity(
|
||||
account_id="1234567890",
|
||||
principal_id="123456",
|
||||
arn="acs:ram::1234567890:role/ProwlerRole",
|
||||
identity_type="AssumedRoleUser",
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"setup_session",
|
||||
return_value=mock_session,
|
||||
) as mock_setup_session,
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"validate_credentials",
|
||||
return_value=mock_caller_identity,
|
||||
),
|
||||
):
|
||||
result = AlibabacloudProvider.test_connection(
|
||||
role_arn="acs:ram::1234567890:role/ProwlerRole",
|
||||
role_session_name="prowler-session",
|
||||
raise_on_exception=False,
|
||||
)
|
||||
|
||||
assert isinstance(result, Connection)
|
||||
assert result.is_connected is True
|
||||
assert result.error is None
|
||||
mock_setup_session.assert_called_once_with(
|
||||
role_arn="acs:ram::1234567890:role/ProwlerRole",
|
||||
role_session_name="prowler-session",
|
||||
ecs_ram_role=None,
|
||||
oidc_role_arn=None,
|
||||
credentials_uri=None,
|
||||
access_key_id=None,
|
||||
access_key_secret=None,
|
||||
security_token=None,
|
||||
)
|
||||
|
||||
def test_test_connection_with_provider_id_validation_success(self):
|
||||
"""Test successful connection with provider_id validation."""
|
||||
mock_session = MagicMock()
|
||||
mock_caller_identity = AlibabaCloudCallerIdentity(
|
||||
account_id="1234567890",
|
||||
principal_id="123456",
|
||||
arn="acs:ram::1234567890:user/test-user",
|
||||
identity_type="RamUser",
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"setup_session",
|
||||
return_value=mock_session,
|
||||
),
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"validate_credentials",
|
||||
return_value=mock_caller_identity,
|
||||
),
|
||||
):
|
||||
result = AlibabacloudProvider.test_connection(
|
||||
access_key_id="LTAI1234567890",
|
||||
access_key_secret="test-secret-key",
|
||||
provider_id="1234567890",
|
||||
raise_on_exception=False,
|
||||
)
|
||||
|
||||
assert isinstance(result, Connection)
|
||||
assert result.is_connected is True
|
||||
assert result.error is None
|
||||
|
||||
def test_test_connection_with_provider_id_mismatch_raises_exception(self):
|
||||
"""Test connection with provider_id mismatch raises exception."""
|
||||
mock_session = MagicMock()
|
||||
mock_caller_identity = AlibabaCloudCallerIdentity(
|
||||
account_id="1234567890",
|
||||
principal_id="123456",
|
||||
arn="acs:ram::1234567890:user/test-user",
|
||||
identity_type="RamUser",
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"setup_session",
|
||||
return_value=mock_session,
|
||||
),
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"validate_credentials",
|
||||
return_value=mock_caller_identity,
|
||||
),
|
||||
):
|
||||
with pytest.raises(AlibabaCloudInvalidCredentialsError) as exception:
|
||||
AlibabacloudProvider.test_connection(
|
||||
access_key_id="LTAI1234567890",
|
||||
access_key_secret="test-secret-key",
|
||||
provider_id="different-account-id",
|
||||
raise_on_exception=True,
|
||||
)
|
||||
|
||||
assert "Provider ID mismatch" in str(exception.value)
|
||||
assert "expected 'different-account-id'" in str(exception.value)
|
||||
assert "got '1234567890'" in str(exception.value)
|
||||
|
||||
def test_test_connection_with_provider_id_mismatch_no_raise(self):
|
||||
"""Test connection with provider_id mismatch returns error without raising."""
|
||||
mock_session = MagicMock()
|
||||
mock_caller_identity = AlibabaCloudCallerIdentity(
|
||||
account_id="1234567890",
|
||||
principal_id="123456",
|
||||
arn="acs:ram::1234567890:user/test-user",
|
||||
identity_type="RamUser",
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"setup_session",
|
||||
return_value=mock_session,
|
||||
),
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"validate_credentials",
|
||||
return_value=mock_caller_identity,
|
||||
),
|
||||
):
|
||||
result = AlibabacloudProvider.test_connection(
|
||||
access_key_id="LTAI1234567890",
|
||||
access_key_secret="test-secret-key",
|
||||
provider_id="different-account-id",
|
||||
raise_on_exception=False,
|
||||
)
|
||||
|
||||
assert isinstance(result, Connection)
|
||||
assert result.is_connected is False
|
||||
assert result.error is not None
|
||||
assert isinstance(result.error, AlibabaCloudInvalidCredentialsError)
|
||||
|
||||
def test_test_connection_setup_session_error_raises_exception(self):
|
||||
"""Test connection when setup_session raises an exception."""
|
||||
with patch.object(
|
||||
AlibabacloudProvider,
|
||||
"setup_session",
|
||||
side_effect=AlibabaCloudSetUpSessionError(
|
||||
file="test_file",
|
||||
original_exception=Exception("Simulated setup error"),
|
||||
),
|
||||
):
|
||||
with pytest.raises(AlibabaCloudSetUpSessionError) as exception:
|
||||
AlibabacloudProvider.test_connection(
|
||||
access_key_id="LTAI1234567890",
|
||||
access_key_secret="test-secret-key",
|
||||
raise_on_exception=True,
|
||||
)
|
||||
|
||||
assert exception.type == AlibabaCloudSetUpSessionError
|
||||
|
||||
def test_test_connection_setup_session_error_no_raise(self):
|
||||
"""Test connection when setup_session raises an exception without raising."""
|
||||
setup_error = AlibabaCloudSetUpSessionError(
|
||||
file="test_file",
|
||||
original_exception=Exception("Simulated setup error"),
|
||||
)
|
||||
|
||||
with patch.object(
|
||||
AlibabacloudProvider,
|
||||
"setup_session",
|
||||
side_effect=setup_error,
|
||||
):
|
||||
result = AlibabacloudProvider.test_connection(
|
||||
access_key_id="LTAI1234567890",
|
||||
access_key_secret="test-secret-key",
|
||||
raise_on_exception=False,
|
||||
)
|
||||
|
||||
assert isinstance(result, Connection)
|
||||
assert result.is_connected is False
|
||||
assert result.error is setup_error
|
||||
|
||||
def test_test_connection_invalid_credentials_raises_exception(self):
|
||||
"""Test connection when validate_credentials raises an exception."""
|
||||
mock_session = MagicMock()
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"setup_session",
|
||||
return_value=mock_session,
|
||||
),
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"validate_credentials",
|
||||
side_effect=AlibabaCloudInvalidCredentialsError(
|
||||
file="test_file",
|
||||
original_exception=Exception("Invalid credentials"),
|
||||
),
|
||||
),
|
||||
):
|
||||
with pytest.raises(AlibabaCloudInvalidCredentialsError) as exception:
|
||||
AlibabacloudProvider.test_connection(
|
||||
access_key_id="LTAI-invalid",
|
||||
access_key_secret="invalid-secret",
|
||||
raise_on_exception=True,
|
||||
)
|
||||
|
||||
assert exception.type == AlibabaCloudInvalidCredentialsError
|
||||
|
||||
def test_test_connection_invalid_credentials_no_raise(self):
|
||||
"""Test connection when validate_credentials raises an exception without raising."""
|
||||
mock_session = MagicMock()
|
||||
auth_error = AlibabaCloudInvalidCredentialsError(
|
||||
file="test_file",
|
||||
original_exception=Exception("Invalid credentials"),
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"setup_session",
|
||||
return_value=mock_session,
|
||||
),
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"validate_credentials",
|
||||
side_effect=auth_error,
|
||||
),
|
||||
):
|
||||
result = AlibabacloudProvider.test_connection(
|
||||
access_key_id="LTAI-invalid",
|
||||
access_key_secret="invalid-secret",
|
||||
raise_on_exception=False,
|
||||
)
|
||||
|
||||
assert isinstance(result, Connection)
|
||||
assert result.is_connected is False
|
||||
assert result.error is auth_error
|
||||
|
||||
def test_test_connection_generic_exception_raises(self):
|
||||
"""Test connection when a generic exception occurs."""
|
||||
mock_session = MagicMock()
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"setup_session",
|
||||
return_value=mock_session,
|
||||
),
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"validate_credentials",
|
||||
side_effect=Exception("Unexpected error"),
|
||||
),
|
||||
):
|
||||
with pytest.raises(Exception) as exception:
|
||||
AlibabacloudProvider.test_connection(
|
||||
access_key_id="LTAI1234567890",
|
||||
access_key_secret="test-secret-key",
|
||||
raise_on_exception=True,
|
||||
)
|
||||
|
||||
assert str(exception.value) == "Unexpected error"
|
||||
|
||||
def test_test_connection_generic_exception_no_raise(self):
|
||||
"""Test connection when a generic exception occurs without raising."""
|
||||
mock_session = MagicMock()
|
||||
generic_error = Exception("Unexpected error")
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"setup_session",
|
||||
return_value=mock_session,
|
||||
),
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"validate_credentials",
|
||||
side_effect=generic_error,
|
||||
),
|
||||
):
|
||||
result = AlibabacloudProvider.test_connection(
|
||||
access_key_id="LTAI1234567890",
|
||||
access_key_secret="test-secret-key",
|
||||
raise_on_exception=False,
|
||||
)
|
||||
|
||||
assert isinstance(result, Connection)
|
||||
assert result.is_connected is False
|
||||
assert result.error is generic_error
|
||||
|
||||
def test_test_connection_passes_credentials_to_setup_session(self):
|
||||
"""Test that credentials are passed directly to setup_session."""
|
||||
mock_session = MagicMock()
|
||||
mock_caller_identity = AlibabaCloudCallerIdentity(
|
||||
account_id="1234567890",
|
||||
principal_id="123456",
|
||||
arn="acs:ram::1234567890:user/test-user",
|
||||
identity_type="RamUser",
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"setup_session",
|
||||
return_value=mock_session,
|
||||
) as mock_setup_session,
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"validate_credentials",
|
||||
return_value=mock_caller_identity,
|
||||
),
|
||||
):
|
||||
result = AlibabacloudProvider.test_connection(
|
||||
access_key_id="LTAI1234567890",
|
||||
access_key_secret="test-secret-key",
|
||||
security_token="test-token",
|
||||
raise_on_exception=False,
|
||||
)
|
||||
|
||||
assert result.is_connected is True
|
||||
|
||||
# Verify credentials are passed directly to setup_session
|
||||
mock_setup_session.assert_called_once_with(
|
||||
role_arn=None,
|
||||
role_session_name=None,
|
||||
ecs_ram_role=None,
|
||||
oidc_role_arn=None,
|
||||
credentials_uri=None,
|
||||
access_key_id="LTAI1234567890",
|
||||
access_key_secret="test-secret-key",
|
||||
security_token="test-token",
|
||||
)
|
||||
|
||||
def test_test_connection_does_not_set_environment_variables(self):
|
||||
"""Test that test_connection does not set environment variables."""
|
||||
mock_session = MagicMock()
|
||||
mock_caller_identity = AlibabaCloudCallerIdentity(
|
||||
account_id="1234567890",
|
||||
principal_id="123456",
|
||||
arn="acs:ram::1234567890:user/test-user",
|
||||
identity_type="RamUser",
|
||||
)
|
||||
|
||||
# Ensure env vars don't exist before the test
|
||||
for var in [
|
||||
"ALIBABA_CLOUD_ACCESS_KEY_ID",
|
||||
"ALIBABA_CLOUD_ACCESS_KEY_SECRET",
|
||||
"ALIBABA_CLOUD_SECURITY_TOKEN",
|
||||
]:
|
||||
if var in os.environ:
|
||||
del os.environ[var]
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"setup_session",
|
||||
return_value=mock_session,
|
||||
),
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"validate_credentials",
|
||||
return_value=mock_caller_identity,
|
||||
),
|
||||
):
|
||||
result = AlibabacloudProvider.test_connection(
|
||||
access_key_id="LTAI1234567890",
|
||||
access_key_secret="test-secret-key",
|
||||
security_token="test-token",
|
||||
raise_on_exception=False,
|
||||
)
|
||||
|
||||
assert result.is_connected is True
|
||||
|
||||
# Verify environment variables are not set
|
||||
assert "ALIBABA_CLOUD_ACCESS_KEY_ID" not in os.environ
|
||||
assert "ALIBABA_CLOUD_ACCESS_KEY_SECRET" not in os.environ
|
||||
assert "ALIBABA_CLOUD_SECURITY_TOKEN" not in os.environ
|
||||
|
||||
def test_test_connection_with_ecs_ram_role(self):
|
||||
"""Test successful connection with ECS RAM role."""
|
||||
mock_session = MagicMock()
|
||||
mock_caller_identity = AlibabaCloudCallerIdentity(
|
||||
account_id="1234567890",
|
||||
principal_id="123456",
|
||||
arn="acs:ram::1234567890:role/ECS-Prowler-Role",
|
||||
identity_type="AssumedRoleUser",
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"setup_session",
|
||||
return_value=mock_session,
|
||||
) as mock_setup_session,
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"validate_credentials",
|
||||
return_value=mock_caller_identity,
|
||||
),
|
||||
):
|
||||
result = AlibabacloudProvider.test_connection(
|
||||
ecs_ram_role="ECS-Prowler-Role",
|
||||
raise_on_exception=False,
|
||||
)
|
||||
|
||||
assert isinstance(result, Connection)
|
||||
assert result.is_connected is True
|
||||
assert result.error is None
|
||||
mock_setup_session.assert_called_once_with(
|
||||
role_arn=None,
|
||||
role_session_name=None,
|
||||
ecs_ram_role="ECS-Prowler-Role",
|
||||
oidc_role_arn=None,
|
||||
credentials_uri=None,
|
||||
access_key_id=None,
|
||||
access_key_secret=None,
|
||||
security_token=None,
|
||||
)
|
||||
|
||||
def test_test_connection_with_oidc_role_arn(self):
|
||||
"""Test successful connection with OIDC role ARN."""
|
||||
mock_session = MagicMock()
|
||||
mock_caller_identity = AlibabaCloudCallerIdentity(
|
||||
account_id="1234567890",
|
||||
principal_id="123456",
|
||||
arn="acs:ram::1234567890:role/OIDCRole",
|
||||
identity_type="AssumedRoleUser",
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"setup_session",
|
||||
return_value=mock_session,
|
||||
) as mock_setup_session,
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"validate_credentials",
|
||||
return_value=mock_caller_identity,
|
||||
),
|
||||
):
|
||||
result = AlibabacloudProvider.test_connection(
|
||||
oidc_role_arn="acs:ram::1234567890:role/OIDCRole",
|
||||
raise_on_exception=False,
|
||||
)
|
||||
|
||||
assert isinstance(result, Connection)
|
||||
assert result.is_connected is True
|
||||
assert result.error is None
|
||||
mock_setup_session.assert_called_once_with(
|
||||
role_arn=None,
|
||||
role_session_name=None,
|
||||
ecs_ram_role=None,
|
||||
oidc_role_arn="acs:ram::1234567890:role/OIDCRole",
|
||||
credentials_uri=None,
|
||||
access_key_id=None,
|
||||
access_key_secret=None,
|
||||
security_token=None,
|
||||
)
|
||||
|
||||
def test_test_connection_with_credentials_uri(self):
|
||||
"""Test successful connection with credentials URI."""
|
||||
mock_session = MagicMock()
|
||||
mock_caller_identity = AlibabaCloudCallerIdentity(
|
||||
account_id="1234567890",
|
||||
principal_id="123456",
|
||||
arn="acs:ram::1234567890:user/test-user",
|
||||
identity_type="RamUser",
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"setup_session",
|
||||
return_value=mock_session,
|
||||
) as mock_setup_session,
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"validate_credentials",
|
||||
return_value=mock_caller_identity,
|
||||
),
|
||||
):
|
||||
result = AlibabacloudProvider.test_connection(
|
||||
credentials_uri="http://localhost:8080/credentials",
|
||||
raise_on_exception=False,
|
||||
)
|
||||
|
||||
assert isinstance(result, Connection)
|
||||
assert result.is_connected is True
|
||||
assert result.error is None
|
||||
mock_setup_session.assert_called_once_with(
|
||||
role_arn=None,
|
||||
role_session_name=None,
|
||||
ecs_ram_role=None,
|
||||
oidc_role_arn=None,
|
||||
credentials_uri="http://localhost:8080/credentials",
|
||||
access_key_id=None,
|
||||
access_key_secret=None,
|
||||
security_token=None,
|
||||
)
|
||||
|
||||
def test_test_connection_without_any_credentials(self):
|
||||
"""Test connection without any credentials uses default credential chain."""
|
||||
mock_session = MagicMock()
|
||||
mock_caller_identity = AlibabaCloudCallerIdentity(
|
||||
account_id="1234567890",
|
||||
principal_id="123456",
|
||||
arn="acs:ram::1234567890:user/test-user",
|
||||
identity_type="RamUser",
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"setup_session",
|
||||
return_value=mock_session,
|
||||
) as mock_setup_session,
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"validate_credentials",
|
||||
return_value=mock_caller_identity,
|
||||
),
|
||||
):
|
||||
result = AlibabacloudProvider.test_connection(
|
||||
raise_on_exception=False,
|
||||
)
|
||||
|
||||
assert isinstance(result, Connection)
|
||||
assert result.is_connected is True
|
||||
assert result.error is None
|
||||
# Should call setup_session with all None values
|
||||
mock_setup_session.assert_called_once_with(
|
||||
role_arn=None,
|
||||
role_session_name=None,
|
||||
ecs_ram_role=None,
|
||||
oidc_role_arn=None,
|
||||
credentials_uri=None,
|
||||
access_key_id=None,
|
||||
access_key_secret=None,
|
||||
security_token=None,
|
||||
)
|
||||
|
||||
def test_test_connection_preserves_existing_env_vars(self):
|
||||
"""Test that existing environment variables are not affected by test_connection."""
|
||||
# Set up existing env vars
|
||||
original_key = "original-key-id"
|
||||
os.environ["ALIBABA_CLOUD_ACCESS_KEY_ID"] = original_key
|
||||
|
||||
mock_session = MagicMock()
|
||||
mock_caller_identity = AlibabaCloudCallerIdentity(
|
||||
account_id="1234567890",
|
||||
principal_id="123456",
|
||||
arn="acs:ram::1234567890:user/test-user",
|
||||
identity_type="RamUser",
|
||||
)
|
||||
|
||||
try:
|
||||
with (
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"setup_session",
|
||||
return_value=mock_session,
|
||||
),
|
||||
patch.object(
|
||||
AlibabacloudProvider,
|
||||
"validate_credentials",
|
||||
return_value=mock_caller_identity,
|
||||
),
|
||||
):
|
||||
result = AlibabacloudProvider.test_connection(
|
||||
access_key_id="LTAI1234567890",
|
||||
access_key_secret="test-secret-key",
|
||||
raise_on_exception=False,
|
||||
)
|
||||
|
||||
assert result.is_connected is True
|
||||
# Verify test_connection does not modify existing env vars
|
||||
# (credentials are passed directly to setup_session, not via env vars)
|
||||
assert os.environ.get("ALIBABA_CLOUD_ACCESS_KEY_ID") == original_key
|
||||
finally:
|
||||
# Clean up
|
||||
if "ALIBABA_CLOUD_ACCESS_KEY_ID" in os.environ:
|
||||
del os.environ["ALIBABA_CLOUD_ACCESS_KEY_ID"]
|
||||
@@ -10,6 +10,8 @@ All notable changes to the **Prowler UI** are documented in this file.
|
||||
- Navigation progress bar for page transitions using Next.js `onRouterTransitionStart` [(#9465)](https://github.com/prowler-cloud/prowler/pull/9465)
|
||||
- Finding Severity Over Time chart component to Overview page [(#9405)](https://github.com/prowler-cloud/prowler/pull/9405)
|
||||
- Attack Surface component to Overview page [(#9412)](https://github.com/prowler-cloud/prowler/pull/9412)
|
||||
- Add Alibaba Cloud provider [(#9501)](https://github.com/prowler-cloud/prowler/pull/9501)
|
||||
- Add searchbar when adding a provider [(#9514)](https://github.com/prowler-cloud/prowler/pull/9514)
|
||||
|
||||
### 🔄 Changed
|
||||
|
||||
|
||||
@@ -2,6 +2,14 @@ import { getProviderDisplayName } from "@/types/providers";
|
||||
|
||||
import { RegionsOverviewResponse } from "./types";
|
||||
|
||||
export const RISK_LEVELS = {
|
||||
LOW_HIGH: "low-high",
|
||||
HIGH: "high",
|
||||
CRITICAL: "critical",
|
||||
} as const;
|
||||
|
||||
export type RiskLevel = (typeof RISK_LEVELS)[keyof typeof RISK_LEVELS];
|
||||
|
||||
export interface ThreatMapLocation {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -11,7 +19,7 @@ export interface ThreatMapLocation {
|
||||
coordinates: [number, number];
|
||||
totalFindings: number;
|
||||
failFindings: number;
|
||||
riskLevel: "low-high" | "high" | "critical";
|
||||
riskLevel: RiskLevel;
|
||||
severityData: Array<{
|
||||
name: string;
|
||||
value: number;
|
||||
@@ -215,6 +223,44 @@ const MONGODBATLAS_COORDINATES: Record<string, { lat: number; lng: number }> = {
|
||||
global: { lat: 40.8, lng: -74.0 }, // Global fallback
|
||||
};
|
||||
|
||||
// Alibaba Cloud regions
|
||||
const ALIBABACLOUD_COORDINATES: Record<string, { lat: number; lng: number }> = {
|
||||
// China regions
|
||||
"cn-hangzhou": { lat: 30.3, lng: 120.2 }, // Hangzhou
|
||||
"cn-shanghai": { lat: 31.2, lng: 121.5 }, // Shanghai
|
||||
"cn-beijing": { lat: 39.9, lng: 116.4 }, // Beijing
|
||||
"cn-shenzhen": { lat: 22.5, lng: 114.1 }, // Shenzhen
|
||||
"cn-zhangjiakou": { lat: 40.8, lng: 114.9 }, // Zhangjiakou
|
||||
"cn-huhehaote": { lat: 40.8, lng: 111.7 }, // Hohhot
|
||||
"cn-wulanchabu": { lat: 41.0, lng: 113.1 }, // Ulanqab
|
||||
"cn-chengdu": { lat: 30.7, lng: 104.1 }, // Chengdu
|
||||
"cn-qingdao": { lat: 36.1, lng: 120.4 }, // Qingdao
|
||||
"cn-nanjing": { lat: 32.1, lng: 118.8 }, // Nanjing
|
||||
"cn-fuzhou": { lat: 26.1, lng: 119.3 }, // Fuzhou
|
||||
"cn-guangzhou": { lat: 23.1, lng: 113.3 }, // Guangzhou
|
||||
"cn-heyuan": { lat: 23.7, lng: 114.7 }, // Heyuan
|
||||
"cn-hongkong": { lat: 22.3, lng: 114.2 }, // Hong Kong
|
||||
// Asia Pacific regions
|
||||
"ap-southeast-1": { lat: 1.4, lng: 103.8 }, // Singapore
|
||||
"ap-southeast-2": { lat: -33.9, lng: 151.2 }, // Sydney
|
||||
"ap-southeast-3": { lat: 3.1, lng: 101.7 }, // Kuala Lumpur
|
||||
"ap-southeast-5": { lat: -6.2, lng: 106.8 }, // Jakarta
|
||||
"ap-southeast-6": { lat: 13.8, lng: 100.5 }, // Bangkok
|
||||
"ap-southeast-7": { lat: 10.8, lng: 106.6 }, // Ho Chi Minh City
|
||||
"ap-northeast-1": { lat: 35.7, lng: 139.7 }, // Tokyo
|
||||
"ap-northeast-2": { lat: 37.6, lng: 127.0 }, // Seoul
|
||||
"ap-south-1": { lat: 19.1, lng: 72.9 }, // Mumbai
|
||||
// US & Europe regions
|
||||
"us-west-1": { lat: 37.4, lng: -121.9 }, // Silicon Valley
|
||||
"us-east-1": { lat: 39.0, lng: -77.5 }, // Virginia
|
||||
"eu-west-1": { lat: 51.5, lng: -0.1 }, // London
|
||||
"eu-central-1": { lat: 50.1, lng: 8.7 }, // Frankfurt
|
||||
// Middle East regions
|
||||
"me-east-1": { lat: 25.3, lng: 55.3 }, // Dubai
|
||||
"me-central-1": { lat: 24.5, lng: 54.4 }, // Riyadh
|
||||
global: { lat: 30.3, lng: 120.2 }, // Global fallback (Hangzhou HQ)
|
||||
};
|
||||
|
||||
const PROVIDER_COORDINATES: Record<
|
||||
string,
|
||||
Record<string, { lat: number; lng: number }>
|
||||
@@ -230,6 +276,7 @@ const PROVIDER_COORDINATES: Record<
|
||||
iac: IAC_COORDINATES,
|
||||
oraclecloud: ORACLECLOUD_COORDINATES,
|
||||
mongodbatlas: MONGODBATLAS_COORDINATES,
|
||||
alibabacloud: ALIBABACLOUD_COORDINATES,
|
||||
};
|
||||
|
||||
// Returns [lng, lat] format for D3/GeoJSON compatibility
|
||||
@@ -253,10 +300,10 @@ function getRegionCoordinates(
|
||||
return coords ? [coords.lng, coords.lat] : null;
|
||||
}
|
||||
|
||||
function getRiskLevel(failRate: number): "low-high" | "high" | "critical" {
|
||||
if (failRate >= 0.5) return "critical";
|
||||
if (failRate >= 0.25) return "high";
|
||||
return "low-high";
|
||||
function getRiskLevel(failRate: number): RiskLevel {
|
||||
if (failRate >= 0.5) return RISK_LEVELS.CRITICAL;
|
||||
if (failRate >= 0.25) return RISK_LEVELS.HIGH;
|
||||
return RISK_LEVELS.LOW_HIGH;
|
||||
}
|
||||
|
||||
// CSS variables are used for Recharts inline styles, not className
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
import {
|
||||
AlibabaCloudProviderBadge,
|
||||
AWSProviderBadge,
|
||||
AzureProviderBadge,
|
||||
GCPProviderBadge,
|
||||
@@ -33,6 +34,7 @@ const PROVIDER_ICON: Record<ProviderType, ReactNode> = {
|
||||
iac: <IacProviderBadge width={18} height={18} />,
|
||||
oraclecloud: <OracleCloudProviderBadge width={18} height={18} />,
|
||||
mongodbatlas: <MongoDBAtlasProviderBadge width={18} height={18} />,
|
||||
alibabacloud: <AlibabaCloudProviderBadge width={18} height={18} />,
|
||||
};
|
||||
|
||||
interface AccountsSelectorProps {
|
||||
|
||||
@@ -57,6 +57,11 @@ const MongoDBAtlasProviderBadge = lazy(() =>
|
||||
default: m.MongoDBAtlasProviderBadge,
|
||||
})),
|
||||
);
|
||||
const AlibabaCloudProviderBadge = lazy(() =>
|
||||
import("@/components/icons/providers-badge").then((m) => ({
|
||||
default: m.AlibabaCloudProviderBadge,
|
||||
})),
|
||||
);
|
||||
|
||||
type IconProps = { width: number; height: number };
|
||||
|
||||
@@ -104,6 +109,10 @@ const PROVIDER_DATA: Record<
|
||||
label: "MongoDB Atlas",
|
||||
icon: MongoDBAtlasProviderBadge,
|
||||
},
|
||||
alibabacloud: {
|
||||
label: "Alibaba Cloud",
|
||||
icon: AlibabaCloudProviderBadge,
|
||||
},
|
||||
};
|
||||
|
||||
type ProviderTypeSelectorProps = {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import React from "react";
|
||||
|
||||
import { getProvider } from "@/actions/providers/providers";
|
||||
import {
|
||||
AddViaCredentialsForm,
|
||||
AddViaRoleForm,
|
||||
} from "@/components/providers/workflow/forms";
|
||||
import { SelectViaAlibabaCloud } from "@/components/providers/workflow/forms/select-credentials-type/alibabacloud";
|
||||
import { SelectViaAWS } from "@/components/providers/workflow/forms/select-credentials-type/aws";
|
||||
import {
|
||||
AddViaServiceAccountForm,
|
||||
@@ -42,6 +41,8 @@ export default async function AddCredentialsPage({ searchParams }: Props) {
|
||||
if (providerType === "github")
|
||||
return <SelectViaGitHub initialVia={via} />;
|
||||
if (providerType === "m365") return <SelectViaM365 initialVia={via} />;
|
||||
if (providerType === "alibabacloud")
|
||||
return <SelectViaAlibabaCloud initialVia={via} />;
|
||||
return null;
|
||||
|
||||
case "credentials":
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
AlibabaCloudProviderBadge,
|
||||
AWSProviderBadge,
|
||||
AzureProviderBadge,
|
||||
GCPProviderBadge,
|
||||
@@ -92,3 +91,12 @@ export const CustomProviderInputOracleCloud = () => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const CustomProviderInputAlibabaCloud = () => {
|
||||
return (
|
||||
<div className="flex items-center gap-x-2">
|
||||
<AlibabaCloudProviderBadge width={25} height={25} />
|
||||
<p className="text-sm">Alibaba Cloud</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
import { Select, SelectItem } from "@heroui/select";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import React, { useCallback, useMemo } from "react";
|
||||
import { ReactElement } from "react";
|
||||
|
||||
import { PROVIDER_TYPES, ProviderType } from "@/types/providers";
|
||||
|
||||
import {
|
||||
CustomProviderInputAlibabaCloud,
|
||||
CustomProviderInputAWS,
|
||||
CustomProviderInputAzure,
|
||||
CustomProviderInputGCP,
|
||||
@@ -20,7 +21,7 @@ import {
|
||||
|
||||
const providerDisplayData: Record<
|
||||
ProviderType,
|
||||
{ label: string; component: React.ReactElement }
|
||||
{ label: string; component: ReactElement }
|
||||
> = {
|
||||
aws: {
|
||||
label: "Amazon Web Services",
|
||||
@@ -58,6 +59,10 @@ const providerDisplayData: Record<
|
||||
label: "Oracle Cloud Infrastructure",
|
||||
component: <CustomProviderInputOracleCloud />,
|
||||
},
|
||||
alibabacloud: {
|
||||
label: "Alibaba Cloud",
|
||||
component: <CustomProviderInputAlibabaCloud />,
|
||||
},
|
||||
};
|
||||
|
||||
const dataInputsProvider = PROVIDER_TYPES.map((providerType) => ({
|
||||
@@ -66,32 +71,27 @@ const dataInputsProvider = PROVIDER_TYPES.map((providerType) => ({
|
||||
value: providerDisplayData[providerType].component,
|
||||
}));
|
||||
|
||||
export const CustomSelectProvider: React.FC = () => {
|
||||
export const CustomSelectProvider = () => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const applyProviderFilter = useCallback(
|
||||
(value: string) => {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
if (value) {
|
||||
params.set("filter[provider_type]", value);
|
||||
} else {
|
||||
params.delete("filter[provider_type]");
|
||||
}
|
||||
router.push(`?${params.toString()}`, { scroll: false });
|
||||
},
|
||||
[router, searchParams],
|
||||
);
|
||||
const applyProviderFilter = (value: string) => {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
if (value) {
|
||||
params.set("filter[provider_type]", value);
|
||||
} else {
|
||||
params.delete("filter[provider_type]");
|
||||
}
|
||||
router.push(`?${params.toString()}`, { scroll: false });
|
||||
};
|
||||
|
||||
const currentProvider = searchParams.get("filter[provider_type]") || "";
|
||||
|
||||
const selectedKeys = useMemo(() => {
|
||||
return dataInputsProvider.some(
|
||||
(provider) => provider.key === currentProvider,
|
||||
)
|
||||
? [currentProvider]
|
||||
: [];
|
||||
}, [currentProvider]);
|
||||
const selectedKeys = dataInputsProvider.some(
|
||||
(provider) => provider.key === currentProvider,
|
||||
)
|
||||
? [currentProvider]
|
||||
: [];
|
||||
|
||||
return (
|
||||
<Select
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { FC } from "react";
|
||||
|
||||
import { IconSvgProps } from "@/types";
|
||||
|
||||
export const AlibabaCloudProviderBadge: FC<IconSvgProps> = ({
|
||||
size,
|
||||
width,
|
||||
height,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height={size || height}
|
||||
role="presentation"
|
||||
viewBox="0 0 256 256"
|
||||
width={size || width}
|
||||
{...props}
|
||||
>
|
||||
<g fill="none">
|
||||
<rect width="256" height="256" fill="#f4f2ed" rx="60" />
|
||||
<g transform="translate(28, 66) scale(1.66)">
|
||||
{/* Horizontal bar */}
|
||||
<rect x="40.1" y="32.8" fill="#FF6A00" width="40.1" height="9" />
|
||||
{/* Right bracket */}
|
||||
<path
|
||||
fill="#FF6A00"
|
||||
d="M100.2,0H73.7l6.4,9.1L99.5,15c3.6,1.1,5.9,4.5,5.8,8c0,0,0,0,0,0V52c0,0,0,0,0,0c0,3.6-2.3,6.9-5.8,8l-19.3,5.9L73.7,75h26.5c11.1,0,20-9,20-20V20C120.3,9,111.3,0,100.2,0"
|
||||
/>
|
||||
{/* Left bracket */}
|
||||
<path
|
||||
fill="#FF6A00"
|
||||
d="M20,0h26.5l-6.4,9.1L20.8,15c-3.6,1.1-5.9,4.5-5.8,8c0,0,0,0,0,0V52c0,0,0,0,0,0c0,3.6,2.3,6.9,5.8,8l19.3,5.9l6.4,9.1H20C9,75,0,66,0,55V20C0,9,9,0,20,0"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
@@ -1,5 +1,8 @@
|
||||
import type { FC } from "react";
|
||||
|
||||
import { IconSvgProps } from "@/types";
|
||||
|
||||
import { AlibabaCloudProviderBadge } from "./alibabacloud-provider-badge";
|
||||
import { AWSProviderBadge } from "./aws-provider-badge";
|
||||
import { AzureProviderBadge } from "./azure-provider-badge";
|
||||
import { GCPProviderBadge } from "./gcp-provider-badge";
|
||||
@@ -11,6 +14,7 @@ import { MongoDBAtlasProviderBadge } from "./mongodbatlas-provider-badge";
|
||||
import { OracleCloudProviderBadge } from "./oraclecloud-provider-badge";
|
||||
|
||||
export {
|
||||
AlibabaCloudProviderBadge,
|
||||
AWSProviderBadge,
|
||||
AzureProviderBadge,
|
||||
GCPProviderBadge,
|
||||
@@ -23,7 +27,7 @@ export {
|
||||
};
|
||||
|
||||
// Map provider display names to their icon components
|
||||
export const PROVIDER_ICONS: Record<string, React.FC<IconSvgProps>> = {
|
||||
export const PROVIDER_ICONS: Record<string, FC<IconSvgProps>> = {
|
||||
AWS: AWSProviderBadge,
|
||||
Azure: AzureProviderBadge,
|
||||
"Google Cloud": GCPProviderBadge,
|
||||
@@ -33,4 +37,5 @@ export const PROVIDER_ICONS: Record<string, React.FC<IconSvgProps>> = {
|
||||
"Infrastructure as Code": IacProviderBadge,
|
||||
"Oracle Cloud Infrastructure": OracleCloudProviderBadge,
|
||||
"MongoDB Atlas": MongoDBAtlasProviderBadge,
|
||||
"Alibaba Cloud": AlibabaCloudProviderBadge,
|
||||
};
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
import { Input } from "@heroui/input";
|
||||
import { Select, SelectItem } from "@heroui/select";
|
||||
import { SharedSelection } from "@heroui/system";
|
||||
import { CheckSquare, Search, Square } from "lucide-react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { Control } from "react-hook-form";
|
||||
import { useState } from "react";
|
||||
import { Control, FieldValues, Path } from "react-hook-form";
|
||||
|
||||
import { Button } from "@/components/shadcn";
|
||||
import { FormControl, FormField, FormMessage } from "@/components/ui/form";
|
||||
@@ -20,11 +21,12 @@ const providerTypeLabels: Record<ProviderType, string> = {
|
||||
iac: "Infrastructure as Code",
|
||||
oraclecloud: "Oracle Cloud Infrastructure",
|
||||
mongodbatlas: "MongoDB Atlas",
|
||||
alibabacloud: "Alibaba Cloud",
|
||||
};
|
||||
|
||||
interface EnhancedProviderSelectorProps {
|
||||
control: Control<any>;
|
||||
name: string;
|
||||
interface EnhancedProviderSelectorProps<T extends FieldValues> {
|
||||
control: Control<T>;
|
||||
name: Path<T>;
|
||||
providers: ProviderProps[];
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
@@ -36,7 +38,7 @@ interface EnhancedProviderSelectorProps {
|
||||
disabledProviderIds?: string[];
|
||||
}
|
||||
|
||||
export const EnhancedProviderSelector = ({
|
||||
export const EnhancedProviderSelector = <T extends FieldValues>({
|
||||
control,
|
||||
name,
|
||||
providers,
|
||||
@@ -48,10 +50,10 @@ export const EnhancedProviderSelector = ({
|
||||
providerType,
|
||||
enableSearch = false,
|
||||
disabledProviderIds = [],
|
||||
}: EnhancedProviderSelectorProps) => {
|
||||
}: EnhancedProviderSelectorProps<T>) => {
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
|
||||
const filteredProviders = useMemo(() => {
|
||||
const filteredProviders = (() => {
|
||||
let filtered = providers;
|
||||
|
||||
// Filter by provider type if specified
|
||||
@@ -83,7 +85,7 @@ export const EnhancedProviderSelector = ({
|
||||
const nameB = b.attributes.alias || b.attributes.uid;
|
||||
return nameA.localeCompare(nameB);
|
||||
});
|
||||
}, [providers, providerType, searchValue, enableSearch]);
|
||||
})();
|
||||
|
||||
return (
|
||||
<FormField
|
||||
@@ -91,7 +93,11 @@ export const EnhancedProviderSelector = ({
|
||||
name={name}
|
||||
render={({ field: { onChange, value, onBlur } }) => {
|
||||
const isMultiple = selectionMode === "multiple";
|
||||
const selectedIds = isMultiple ? value || [] : value ? [value] : [];
|
||||
const selectedIds: string[] = isMultiple
|
||||
? (value as string[] | undefined) || []
|
||||
: value
|
||||
? [value as string]
|
||||
: [];
|
||||
const allProviderIds = filteredProviders
|
||||
.filter((p) => !disabledProviderIds.includes(p.id))
|
||||
.map((p) => p.id);
|
||||
@@ -108,13 +114,17 @@ export const EnhancedProviderSelector = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectionChange = (keys: any) => {
|
||||
const handleSelectionChange = (keys: SharedSelection) => {
|
||||
if (keys === "all") {
|
||||
onChange(allProviderIds);
|
||||
return;
|
||||
}
|
||||
if (isMultiple) {
|
||||
const selectedArray = Array.from(keys);
|
||||
const selectedArray = Array.from(keys).map(String);
|
||||
onChange(selectedArray);
|
||||
} else {
|
||||
const selectedValue = Array.from(keys)[0];
|
||||
onChange(selectedValue || "");
|
||||
onChange(selectedValue ? String(selectedValue) : "");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import { Input } from "@heroui/input";
|
||||
import { RadioGroup } from "@heroui/radio";
|
||||
import React from "react";
|
||||
import { SearchIcon, XCircle } from "lucide-react";
|
||||
import { FC, useState } from "react";
|
||||
import { Control, Controller } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { addProviderFormSchema } from "@/types";
|
||||
|
||||
import {
|
||||
AlibabaCloudProviderBadge,
|
||||
AWSProviderBadge,
|
||||
AzureProviderBadge,
|
||||
GCPProviderBadge,
|
||||
@@ -21,95 +24,160 @@ import {
|
||||
import { CustomRadio } from "../ui/custom";
|
||||
import { FormMessage } from "../ui/form";
|
||||
|
||||
const PROVIDERS = [
|
||||
{
|
||||
value: "aws",
|
||||
label: "Amazon Web Services",
|
||||
badge: AWSProviderBadge,
|
||||
},
|
||||
{
|
||||
value: "gcp",
|
||||
label: "Google Cloud Platform",
|
||||
badge: GCPProviderBadge,
|
||||
},
|
||||
{
|
||||
value: "azure",
|
||||
label: "Microsoft Azure",
|
||||
badge: AzureProviderBadge,
|
||||
},
|
||||
{
|
||||
value: "m365",
|
||||
label: "Microsoft 365",
|
||||
badge: M365ProviderBadge,
|
||||
},
|
||||
{
|
||||
value: "mongodbatlas",
|
||||
label: "MongoDB Atlas",
|
||||
badge: MongoDBAtlasProviderBadge,
|
||||
},
|
||||
{
|
||||
value: "kubernetes",
|
||||
label: "Kubernetes",
|
||||
badge: KS8ProviderBadge,
|
||||
},
|
||||
{
|
||||
value: "github",
|
||||
label: "GitHub",
|
||||
badge: GitHubProviderBadge,
|
||||
},
|
||||
{
|
||||
value: "iac",
|
||||
label: "Infrastructure as Code",
|
||||
badge: IacProviderBadge,
|
||||
},
|
||||
{
|
||||
value: "oraclecloud",
|
||||
label: "Oracle Cloud Infrastructure",
|
||||
badge: OracleCloudProviderBadge,
|
||||
},
|
||||
{
|
||||
value: "alibabacloud",
|
||||
label: "Alibaba Cloud",
|
||||
badge: AlibabaCloudProviderBadge,
|
||||
},
|
||||
] as const;
|
||||
|
||||
interface RadioGroupProviderProps {
|
||||
control: Control<z.infer<typeof addProviderFormSchema>>;
|
||||
isInvalid: boolean;
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
export const RadioGroupProvider: React.FC<RadioGroupProviderProps> = ({
|
||||
export const RadioGroupProvider: FC<RadioGroupProviderProps> = ({
|
||||
control,
|
||||
isInvalid,
|
||||
errorMessage,
|
||||
}) => {
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
|
||||
const lowerSearch = searchTerm.trim().toLowerCase();
|
||||
const filteredProviders = lowerSearch
|
||||
? PROVIDERS.filter(
|
||||
(provider) =>
|
||||
provider.label.toLowerCase().includes(lowerSearch) ||
|
||||
provider.value.toLowerCase().includes(lowerSearch),
|
||||
)
|
||||
: PROVIDERS;
|
||||
|
||||
return (
|
||||
<Controller
|
||||
name="providerType"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<>
|
||||
<RadioGroup
|
||||
className="flex flex-wrap"
|
||||
isInvalid={isInvalid}
|
||||
{...field}
|
||||
value={field.value || ""}
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
<CustomRadio description="Amazon Web Services" value="aws">
|
||||
<div className="flex items-center">
|
||||
<AWSProviderBadge size={26} />
|
||||
<span className="ml-2">Amazon Web Services</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
<CustomRadio description="Google Cloud Platform" value="gcp">
|
||||
<div className="flex items-center">
|
||||
<GCPProviderBadge size={26} />
|
||||
<span className="ml-2">Google Cloud Platform</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
<CustomRadio description="Microsoft Azure" value="azure">
|
||||
<div className="flex items-center">
|
||||
<AzureProviderBadge size={26} />
|
||||
<span className="ml-2">Microsoft Azure</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
<CustomRadio description="Microsoft 365" value="m365">
|
||||
<div className="flex items-center">
|
||||
<M365ProviderBadge size={26} />
|
||||
<span className="ml-2">Microsoft 365</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
<CustomRadio description="MongoDB Atlas" value="mongodbatlas">
|
||||
<div className="flex items-center">
|
||||
<MongoDBAtlasProviderBadge size={26} />
|
||||
<span className="ml-2">MongoDB Atlas</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
<CustomRadio description="Kubernetes" value="kubernetes">
|
||||
<div className="flex items-center">
|
||||
<KS8ProviderBadge size={26} />
|
||||
<span className="ml-2">Kubernetes</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
<CustomRadio description="GitHub" value="github">
|
||||
<div className="flex items-center">
|
||||
<GitHubProviderBadge size={26} />
|
||||
<span className="ml-2">GitHub</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
<CustomRadio description="Infrastructure as Code" value="iac">
|
||||
<div className="flex items-center">
|
||||
<IacProviderBadge size={26} />
|
||||
<span className="ml-2">Infrastructure as Code</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
<CustomRadio
|
||||
description="Oracle Cloud Infrastructure"
|
||||
value="oraclecloud"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<OracleCloudProviderBadge size={26} />
|
||||
<span className="ml-2">Oracle Cloud Infrastructure</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<div className="flex h-[calc(100vh-200px)] flex-col gap-2">
|
||||
<div className="shrink-0">
|
||||
<Input
|
||||
aria-label="Search providers"
|
||||
placeholder="Search providers..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
startContent={
|
||||
<SearchIcon
|
||||
className="text-bg-button-secondary shrink-0"
|
||||
width={16}
|
||||
/>
|
||||
}
|
||||
endContent={
|
||||
searchTerm && (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Clear search"
|
||||
onClick={() => setSearchTerm("")}
|
||||
className="text-bg-button-secondary shrink-0 focus:outline-none"
|
||||
>
|
||||
<XCircle className="h-4 w-4" />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
classNames={{
|
||||
base: "w-full",
|
||||
input:
|
||||
"text-bg-button-secondary placeholder:text-bg-button-secondary text-sm",
|
||||
inputWrapper:
|
||||
"!border-border-input-primary !bg-bg-input-primary dark:!bg-input/30 dark:hover:!bg-input/50 hover:!bg-bg-neutral-secondary !border !rounded-lg !shadow-xs !transition-[color,box-shadow] focus-within:!border-border-input-primary-press focus-within:!ring-1 focus-within:!ring-border-input-primary-press focus-within:!ring-offset-1 !h-10 !px-4 !py-3 !outline-none",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="minimal-scrollbar flex-1 overflow-y-scroll pr-4">
|
||||
<RadioGroup
|
||||
className="flex flex-wrap"
|
||||
isInvalid={isInvalid}
|
||||
{...field}
|
||||
value={field.value || ""}
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
{filteredProviders.length > 0 ? (
|
||||
filteredProviders.map((provider) => {
|
||||
const BadgeComponent = provider.badge;
|
||||
return (
|
||||
<CustomRadio
|
||||
key={provider.value}
|
||||
description={provider.label}
|
||||
value={provider.value}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<BadgeComponent size={26} />
|
||||
<span className="ml-2">{provider.label}</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<p className="text-default-500 py-4 text-sm">
|
||||
No providers found matching "{searchTerm}"
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
{errorMessage && (
|
||||
<FormMessage className="text-text-error">
|
||||
{errorMessage}
|
||||
</FormMessage>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { Divider } from "@heroui/divider";
|
||||
import { ChevronLeftIcon, ChevronRightIcon, Loader2 } from "lucide-react";
|
||||
import { Control } from "react-hook-form";
|
||||
import { Control, UseFormSetValue } from "react-hook-form";
|
||||
|
||||
import { Button } from "@/components/shadcn";
|
||||
import { Form } from "@/components/ui/form";
|
||||
@@ -11,6 +11,9 @@ import { getAWSCredentialsTemplateLinks } from "@/lib";
|
||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||
import { requiresBackButton } from "@/lib/provider-helpers";
|
||||
import {
|
||||
AlibabaCloudCredentials,
|
||||
AlibabaCloudCredentialsRole,
|
||||
ApiError,
|
||||
AWSCredentials,
|
||||
AWSCredentialsRole,
|
||||
AzureCredentials,
|
||||
@@ -26,6 +29,10 @@ import {
|
||||
} from "@/types";
|
||||
|
||||
import { ProviderTitleDocs } from "../provider-title-docs";
|
||||
import {
|
||||
AlibabaCloudRoleCredentialsForm,
|
||||
AlibabaCloudStaticCredentialsForm,
|
||||
} from "./select-credentials-type/alibabacloud/credentials-type";
|
||||
import { AWSStaticCredentialsForm } from "./select-credentials-type/aws/credentials-type";
|
||||
import { AWSRoleCredentialsForm } from "./select-credentials-type/aws/credentials-type/aws-role-credentials-form";
|
||||
import { GCPDefaultCredentialsForm } from "./select-credentials-type/gcp/credentials-type";
|
||||
@@ -41,11 +48,19 @@ import { KubernetesCredentialsForm } from "./via-credentials/k8s-credentials-for
|
||||
import { MongoDBAtlasCredentialsForm } from "./via-credentials/mongodbatlas-credentials-form";
|
||||
import { OracleCloudCredentialsForm } from "./via-credentials/oraclecloud-credentials-form";
|
||||
|
||||
type ApiResponse = {
|
||||
error?: string;
|
||||
errors?: ApiError[];
|
||||
data?: unknown;
|
||||
success?: boolean;
|
||||
status?: number;
|
||||
};
|
||||
|
||||
type BaseCredentialsFormProps = {
|
||||
providerType: ProviderType;
|
||||
providerId: string;
|
||||
providerUid?: string;
|
||||
onSubmit: (formData: FormData) => Promise<any>;
|
||||
onSubmit: (formData: FormData) => Promise<ApiResponse>;
|
||||
successNavigationUrl: string;
|
||||
submitButtonText?: string;
|
||||
showBackButton?: boolean;
|
||||
@@ -108,7 +123,9 @@ export const BaseCredentialsForm = ({
|
||||
{providerType === "aws" && searchParamsObj.get("via") === "role" && (
|
||||
<AWSRoleCredentialsForm
|
||||
control={form.control as unknown as Control<AWSCredentialsRole>}
|
||||
setValue={form.setValue as any}
|
||||
setValue={
|
||||
form.setValue as unknown as UseFormSetValue<AWSCredentialsRole>
|
||||
}
|
||||
externalId={externalId}
|
||||
templateLinks={templateLinks}
|
||||
/>
|
||||
@@ -181,6 +198,22 @@ export const BaseCredentialsForm = ({
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{providerType === "alibabacloud" &&
|
||||
searchParamsObj.get("via") === "role" && (
|
||||
<AlibabaCloudRoleCredentialsForm
|
||||
control={
|
||||
form.control as unknown as Control<AlibabaCloudCredentialsRole>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{providerType === "alibabacloud" &&
|
||||
searchParamsObj.get("via") !== "role" && (
|
||||
<AlibabaCloudStaticCredentialsForm
|
||||
control={
|
||||
form.control as unknown as Control<AlibabaCloudCredentials>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="flex w-full justify-end gap-4">
|
||||
{showBackButton && requiresBackButton(searchParamsObj.get("via")) && (
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ChevronLeftIcon, ChevronRightIcon, Loader2 } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import * as z from "zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { addProvider } from "@/actions/providers/providers";
|
||||
import { ProviderTitleDocs } from "@/components/providers/workflow/provider-title-docs";
|
||||
@@ -67,6 +67,11 @@ const getProviderFieldDetails = (providerType?: ProviderType) => {
|
||||
label: "Organization ID",
|
||||
placeholder: "e.g. 5f43a8c4e1234567890abcde",
|
||||
};
|
||||
case "alibabacloud":
|
||||
return {
|
||||
label: "Account ID",
|
||||
placeholder: "e.g. 1234567890123456",
|
||||
};
|
||||
default:
|
||||
return {
|
||||
label: "Provider UID",
|
||||
@@ -151,13 +156,16 @@ export const ConnectAccountForm = () => {
|
||||
|
||||
router.push(`/providers/add-credentials?type=${providerType}&id=${id}`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error during submission:", error);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Submission Error",
|
||||
description: error.message || "Something went wrong. Please try again.",
|
||||
description:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Something went wrong. Please try again.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { Divider } from "@heroui/divider";
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomInput } from "@/components/ui/custom";
|
||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||
import { AlibabaCloudCredentialsRole } from "@/types";
|
||||
|
||||
export const AlibabaCloudRoleCredentialsForm = ({
|
||||
control,
|
||||
}: {
|
||||
control: Control<AlibabaCloudCredentialsRole>;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col">
|
||||
<div className="text-md text-default-foreground leading-9 font-bold">
|
||||
Connect assuming RAM Role
|
||||
</div>
|
||||
<div className="text-default-500 text-sm">
|
||||
Provide the RAM Role ARN to assume, along with the Access Keys of a
|
||||
RAM user that has permission to assume the role.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span className="text-default-500 text-xs font-bold">
|
||||
RAM Role to Assume
|
||||
</span>
|
||||
|
||||
<CustomInput
|
||||
control={control}
|
||||
name={ProviderCredentialFields.ALIBABACLOUD_ROLE_ARN}
|
||||
type="text"
|
||||
label="Role ARN"
|
||||
labelPlacement="inside"
|
||||
placeholder="e.g. acs:ram::1234567890123456:role/ProwlerRole"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
|
||||
<Divider />
|
||||
|
||||
<span className="text-default-500 text-xs font-bold">
|
||||
Credentials for Role Assumption
|
||||
</span>
|
||||
|
||||
<CustomInput
|
||||
control={control}
|
||||
name={ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_ID}
|
||||
type="text"
|
||||
label="Access Key ID"
|
||||
labelPlacement="inside"
|
||||
placeholder="e.g. LTAI5txxxxxxxxxx"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
<CustomInput
|
||||
control={control}
|
||||
name={ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_SECRET}
|
||||
type="password"
|
||||
label="Access Key Secret"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the access key secret"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
|
||||
<span className="text-default-500 text-xs">Optional fields</span>
|
||||
|
||||
<CustomInput
|
||||
control={control}
|
||||
name={ProviderCredentialFields.ALIBABACLOUD_ROLE_SESSION_NAME}
|
||||
type="text"
|
||||
label="Role Session Name"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the role session name (default: ProwlerSession)"
|
||||
variant="bordered"
|
||||
isRequired={false}
|
||||
/>
|
||||
|
||||
<div className="text-default-400 text-xs">
|
||||
Keys never leave your browser unencrypted and are stored as secrets in
|
||||
the backend. The role will be assumed using STS to obtain temporary
|
||||
credentials.
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomInput } from "@/components/ui/custom";
|
||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||
import { AlibabaCloudCredentials } from "@/types";
|
||||
|
||||
export const AlibabaCloudStaticCredentialsForm = ({
|
||||
control,
|
||||
}: {
|
||||
control: Control<AlibabaCloudCredentials>;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col">
|
||||
<div className="text-md text-default-foreground leading-9 font-bold">
|
||||
Connect via Access Keys
|
||||
</div>
|
||||
<div className="text-default-500 text-sm">
|
||||
Provide a RAM user Access Key ID and Access Key Secret with read
|
||||
access to the resources you want Prowler to assess.
|
||||
</div>
|
||||
</div>
|
||||
<CustomInput
|
||||
control={control}
|
||||
name={ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_ID}
|
||||
type="text"
|
||||
label="Access Key ID"
|
||||
labelPlacement="inside"
|
||||
placeholder="e.g. LTAI5txxxxxxxxxx"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
<CustomInput
|
||||
control={control}
|
||||
name={ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_SECRET}
|
||||
type="password"
|
||||
label="Access Key Secret"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the access key secret"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
<div className="text-default-400 text-xs">
|
||||
Keys never leave your browser unencrypted and are stored as secrets in
|
||||
the backend. Rotate the key from Alibaba Cloud RAM console anytime if
|
||||
needed.
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./alibabacloud-role-credentials-form";
|
||||
export * from "./alibabacloud-static-credentials-form";
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./radio-group-alibabacloud-via-credentials-type-form";
|
||||
export * from "./select-via-alibabacloud";
|
||||
@@ -0,0 +1,71 @@
|
||||
"use client";
|
||||
|
||||
import { RadioGroup } from "@heroui/radio";
|
||||
import { Control, Controller, FieldValues, Path } from "react-hook-form";
|
||||
|
||||
import { CustomRadio } from "@/components/ui/custom";
|
||||
import { FormMessage } from "@/components/ui/form";
|
||||
|
||||
type RadioGroupAlibabaCloudViaCredentialsFormProps<T extends FieldValues> = {
|
||||
control: Control<T>;
|
||||
isInvalid: boolean;
|
||||
errorMessage?: string;
|
||||
onChange?: (value: string) => void;
|
||||
};
|
||||
|
||||
export const RadioGroupAlibabaCloudViaCredentialsTypeForm = <
|
||||
T extends FieldValues,
|
||||
>({
|
||||
control,
|
||||
isInvalid,
|
||||
errorMessage,
|
||||
onChange,
|
||||
}: RadioGroupAlibabaCloudViaCredentialsFormProps<T>) => {
|
||||
return (
|
||||
<Controller
|
||||
name={"alibabacloudCredentialsType" as Path<T>}
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<>
|
||||
<RadioGroup
|
||||
className="flex flex-wrap"
|
||||
isInvalid={isInvalid}
|
||||
{...field}
|
||||
value={field.value || ""}
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value);
|
||||
if (onChange) {
|
||||
onChange(value);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
<span className="text-default-500 text-sm">Using RAM Role</span>
|
||||
<CustomRadio description="Connect assuming RAM Role" value="role">
|
||||
<div className="flex items-center">
|
||||
<span className="ml-2">Connect assuming RAM Role</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
<span className="text-default-500 text-sm">
|
||||
Using Credentials
|
||||
</span>
|
||||
<CustomRadio
|
||||
description="Connect via Access Keys"
|
||||
value="credentials"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<span className="ml-2">Connect via Access Keys</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
{errorMessage && (
|
||||
<FormMessage className="text-text-error">
|
||||
{errorMessage}
|
||||
</FormMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
import { Form } from "@/components/ui/form";
|
||||
|
||||
import { RadioGroupAlibabaCloudViaCredentialsTypeForm } from "./radio-group-alibabacloud-via-credentials-type-form";
|
||||
|
||||
interface SelectViaAlibabaCloudProps {
|
||||
initialVia?: string;
|
||||
}
|
||||
|
||||
export const SelectViaAlibabaCloud = ({
|
||||
initialVia,
|
||||
}: SelectViaAlibabaCloudProps) => {
|
||||
const router = useRouter();
|
||||
const form = useForm({
|
||||
defaultValues: {
|
||||
alibabacloudCredentialsType: initialVia || "",
|
||||
},
|
||||
});
|
||||
|
||||
const handleSelectionChange = (value: string) => {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set("via", value);
|
||||
router.push(url.toString());
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RadioGroupAlibabaCloudViaCredentialsTypeForm
|
||||
control={form.control}
|
||||
isInvalid={!!form.formState.errors.alibabacloudCredentialsType}
|
||||
errorMessage={
|
||||
form.formState.errors.alibabacloudCredentialsType?.message as string
|
||||
}
|
||||
onChange={handleSelectionChange}
|
||||
/>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
"use client";
|
||||
|
||||
import {
|
||||
AlibabaCloudProviderBadge,
|
||||
AWSProviderBadge,
|
||||
AzureProviderBadge,
|
||||
GCPProviderBadge,
|
||||
@@ -33,6 +34,8 @@ export const getProviderLogo = (provider: ProviderType) => {
|
||||
return <OracleCloudProviderBadge width={35} height={35} />;
|
||||
case "mongodbatlas":
|
||||
return <MongoDBAtlasProviderBadge width={35} height={35} />;
|
||||
case "alibabacloud":
|
||||
return <AlibabaCloudProviderBadge width={35} height={35} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -58,6 +61,8 @@ export const getProviderName = (provider: ProviderType): string => {
|
||||
return "Oracle Cloud Infrastructure";
|
||||
case "mongodbatlas":
|
||||
return "MongoDB Atlas";
|
||||
case "alibabacloud":
|
||||
return "Alibaba Cloud";
|
||||
default:
|
||||
return "Unknown Provider";
|
||||
}
|
||||
|
||||
@@ -11,14 +11,23 @@ import {
|
||||
addCredentialsFormSchema,
|
||||
addCredentialsRoleFormSchema,
|
||||
addCredentialsServiceAccountFormSchema,
|
||||
ApiError,
|
||||
ProviderType,
|
||||
} from "@/types";
|
||||
|
||||
type ApiResponse = {
|
||||
error?: string;
|
||||
errors?: ApiError[];
|
||||
data?: unknown;
|
||||
success?: boolean;
|
||||
status?: number;
|
||||
};
|
||||
|
||||
type UseCredentialsFormProps = {
|
||||
providerType: ProviderType;
|
||||
providerId: string;
|
||||
providerUid?: string;
|
||||
onSubmit: (formData: FormData) => Promise<any>;
|
||||
onSubmit: (formData: FormData) => Promise<ApiResponse>;
|
||||
successNavigationUrl: string;
|
||||
};
|
||||
|
||||
@@ -39,6 +48,9 @@ export const useCredentialsForm = ({
|
||||
if (providerType === "aws" && via === "role") {
|
||||
return addCredentialsRoleFormSchema(providerType);
|
||||
}
|
||||
if (providerType === "alibabacloud" && via === "role") {
|
||||
return addCredentialsRoleFormSchema(providerType);
|
||||
}
|
||||
if (providerType === "gcp" && via === "service-account") {
|
||||
return addCredentialsServiceAccountFormSchema(providerType);
|
||||
}
|
||||
@@ -173,6 +185,22 @@ export const useCredentialsForm = ({
|
||||
[ProviderCredentialFields.ATLAS_PUBLIC_KEY]: "",
|
||||
[ProviderCredentialFields.ATLAS_PRIVATE_KEY]: "",
|
||||
};
|
||||
case "alibabacloud":
|
||||
// AlibabaCloud Role credentials
|
||||
if (via === "role") {
|
||||
return {
|
||||
...baseDefaults,
|
||||
[ProviderCredentialFields.ALIBABACLOUD_ROLE_ARN]: "",
|
||||
[ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_ID]: "",
|
||||
[ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_SECRET]: "",
|
||||
[ProviderCredentialFields.ALIBABACLOUD_ROLE_SESSION_NAME]: "",
|
||||
};
|
||||
}
|
||||
return {
|
||||
...baseDefaults,
|
||||
[ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_ID]: "",
|
||||
[ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_SECRET]: "",
|
||||
};
|
||||
default:
|
||||
return baseDefaults;
|
||||
}
|
||||
|
||||
@@ -47,6 +47,11 @@ export const getProviderHelpText = (provider: string) => {
|
||||
text: "Need help connecting your MongoDB Atlas organization?",
|
||||
link: "https://goto.prowler.com/provider-mongodbatlas",
|
||||
};
|
||||
case "alibabacloud":
|
||||
return {
|
||||
text: "Need help connecting your Alibaba Cloud account?",
|
||||
link: "https://goto.prowler.com/provider-alibabacloud",
|
||||
};
|
||||
default:
|
||||
return {
|
||||
text: "How to setup a provider?",
|
||||
|
||||
@@ -211,6 +211,45 @@ export const buildMongoDBAtlasSecret = (formData: FormData) => {
|
||||
return filterEmptyValues(secret);
|
||||
};
|
||||
|
||||
export const buildAlibabaCloudSecret = (
|
||||
formData: FormData,
|
||||
isRole: boolean,
|
||||
) => {
|
||||
if (isRole) {
|
||||
const secret = {
|
||||
[ProviderCredentialFields.ALIBABACLOUD_ROLE_ARN]: getFormValue(
|
||||
formData,
|
||||
ProviderCredentialFields.ALIBABACLOUD_ROLE_ARN,
|
||||
),
|
||||
[ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_ID]: getFormValue(
|
||||
formData,
|
||||
ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_ID,
|
||||
),
|
||||
[ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_SECRET]: getFormValue(
|
||||
formData,
|
||||
ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_SECRET,
|
||||
),
|
||||
[ProviderCredentialFields.ALIBABACLOUD_ROLE_SESSION_NAME]: getFormValue(
|
||||
formData,
|
||||
ProviderCredentialFields.ALIBABACLOUD_ROLE_SESSION_NAME,
|
||||
),
|
||||
};
|
||||
return filterEmptyValues(secret);
|
||||
}
|
||||
|
||||
const secret = {
|
||||
[ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_ID]: getFormValue(
|
||||
formData,
|
||||
ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_ID,
|
||||
),
|
||||
[ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_SECRET]: getFormValue(
|
||||
formData,
|
||||
ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_SECRET,
|
||||
),
|
||||
};
|
||||
return filterEmptyValues(secret);
|
||||
};
|
||||
|
||||
export const buildIacSecret = (formData: FormData) => {
|
||||
const secret = {
|
||||
[ProviderCredentialFields.REPOSITORY_URL]: getFormValue(
|
||||
@@ -326,6 +365,14 @@ export const buildSecretConfig = (
|
||||
secretType: "static",
|
||||
secret: buildMongoDBAtlasSecret(formData),
|
||||
}),
|
||||
alibabacloud: () => {
|
||||
const isRole =
|
||||
formData.get(ProviderCredentialFields.ALIBABACLOUD_ROLE_ARN) !== null;
|
||||
return {
|
||||
secretType: isRole ? "role" : "static",
|
||||
secret: buildAlibabaCloudSecret(formData, isRole),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const builder = secretBuilders[providerType];
|
||||
|
||||
@@ -61,6 +61,12 @@ export const ProviderCredentialFields = {
|
||||
OCI_TENANCY: "tenancy",
|
||||
OCI_REGION: "region",
|
||||
OCI_PASS_PHRASE: "pass_phrase",
|
||||
|
||||
// Alibaba Cloud fields
|
||||
ALIBABACLOUD_ACCESS_KEY_ID: "access_key_id",
|
||||
ALIBABACLOUD_ACCESS_KEY_SECRET: "access_key_secret",
|
||||
ALIBABACLOUD_ROLE_ARN: "role_arn",
|
||||
ALIBABACLOUD_ROLE_SESSION_NAME: "role_session_name",
|
||||
} as const;
|
||||
|
||||
// Type for credential field values
|
||||
@@ -101,6 +107,10 @@ export const ErrorPointers = {
|
||||
OCI_PASS_PHRASE: "/data/attributes/secret/pass_phrase",
|
||||
ATLAS_PUBLIC_KEY: "/data/attributes/secret/atlas_public_key",
|
||||
ATLAS_PRIVATE_KEY: "/data/attributes/secret/atlas_private_key",
|
||||
ALIBABACLOUD_ACCESS_KEY_ID: "/data/attributes/secret/access_key_id",
|
||||
ALIBABACLOUD_ACCESS_KEY_SECRET: "/data/attributes/secret/access_key_secret",
|
||||
ALIBABACLOUD_ROLE_ARN: "/data/attributes/secret/role_arn",
|
||||
ALIBABACLOUD_ROLE_SESSION_NAME: "/data/attributes/secret/role_session_name",
|
||||
} as const;
|
||||
|
||||
export type ErrorPointer = (typeof ErrorPointers)[keyof typeof ErrorPointers];
|
||||
|
||||
@@ -122,7 +122,13 @@ export const getProviderFormType = (
|
||||
via?: string,
|
||||
): ProviderFormType => {
|
||||
// Providers that need credential type selection
|
||||
const needsSelector = ["aws", "gcp", "github", "m365"].includes(providerType);
|
||||
const needsSelector = [
|
||||
"aws",
|
||||
"gcp",
|
||||
"github",
|
||||
"m365",
|
||||
"alibabacloud",
|
||||
].includes(providerType);
|
||||
|
||||
// Show selector if no via parameter and provider needs it
|
||||
if (needsSelector && !via) {
|
||||
@@ -157,6 +163,12 @@ export const getProviderFormType = (
|
||||
return "credentials";
|
||||
}
|
||||
|
||||
// AlibabaCloud specific forms
|
||||
if (providerType === "alibabacloud") {
|
||||
if (via === "role") return "role";
|
||||
if (via === "credentials") return "credentials";
|
||||
}
|
||||
|
||||
// Other providers go directly to credentials form
|
||||
if (!needsSelector) {
|
||||
return "credentials";
|
||||
@@ -179,6 +191,7 @@ export const requiresBackButton = (via?: string | null): boolean => {
|
||||
"app_client_secret",
|
||||
"app_certificate",
|
||||
];
|
||||
// Note: "role" is already included for AWS, now also used by AlibabaCloud
|
||||
|
||||
return validViaTypes.includes(via);
|
||||
};
|
||||
|
||||
@@ -270,6 +270,20 @@ export type MongoDBAtlasCredentials = {
|
||||
[ProviderCredentialFields.PROVIDER_ID]: string;
|
||||
};
|
||||
|
||||
export type AlibabaCloudCredentials = {
|
||||
[ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_ID]: string;
|
||||
[ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_SECRET]: string;
|
||||
[ProviderCredentialFields.PROVIDER_ID]: string;
|
||||
};
|
||||
|
||||
export type AlibabaCloudCredentialsRole = {
|
||||
[ProviderCredentialFields.ALIBABACLOUD_ROLE_ARN]: string;
|
||||
[ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_ID]: string;
|
||||
[ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_SECRET]: string;
|
||||
[ProviderCredentialFields.ALIBABACLOUD_ROLE_SESSION_NAME]?: string;
|
||||
[ProviderCredentialFields.PROVIDER_ID]: string;
|
||||
};
|
||||
|
||||
export type CredentialsFormSchema =
|
||||
| AWSCredentials
|
||||
| AzureCredentials
|
||||
@@ -279,7 +293,9 @@ export type CredentialsFormSchema =
|
||||
| IacCredentials
|
||||
| M365Credentials
|
||||
| OCICredentials
|
||||
| MongoDBAtlasCredentials;
|
||||
| MongoDBAtlasCredentials
|
||||
| AlibabaCloudCredentials
|
||||
| AlibabaCloudCredentialsRole;
|
||||
|
||||
export interface SearchParamsProps {
|
||||
[key: string]: string | string[] | undefined;
|
||||
|
||||
@@ -125,6 +125,11 @@ export const addProviderFormSchema = z
|
||||
[ProviderCredentialFields.PROVIDER_ALIAS]: z.string(),
|
||||
providerUid: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
providerType: z.literal("alibabacloud"),
|
||||
[ProviderCredentialFields.PROVIDER_ALIAS]: z.string(),
|
||||
providerUid: z.string(),
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
@@ -245,9 +250,18 @@ export const addCredentialsFormSchema = (
|
||||
.string()
|
||||
.min(1, "Atlas Private Key is required"),
|
||||
}
|
||||
: {}),
|
||||
: providerType === "alibabacloud"
|
||||
? {
|
||||
[ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_ID]:
|
||||
z.string().min(1, "Access Key ID is required"),
|
||||
[ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_SECRET]:
|
||||
z
|
||||
.string()
|
||||
.min(1, "Access Key Secret is required"),
|
||||
}
|
||||
: {}),
|
||||
})
|
||||
.superRefine((data: Record<string, any>, ctx) => {
|
||||
.superRefine((data: Record<string, string | undefined>, ctx) => {
|
||||
if (providerType === "m365") {
|
||||
// Validate based on the via parameter
|
||||
if (via === "app_client_secret") {
|
||||
@@ -339,10 +353,27 @@ export const addCredentialsRoleFormSchema = (providerType: string) =>
|
||||
path: [ProviderCredentialFields.AWS_ACCESS_KEY_ID],
|
||||
},
|
||||
)
|
||||
: z.object({
|
||||
providerId: z.string(),
|
||||
providerType: z.string(),
|
||||
});
|
||||
: providerType === "alibabacloud"
|
||||
? z.object({
|
||||
[ProviderCredentialFields.PROVIDER_ID]: z.string(),
|
||||
[ProviderCredentialFields.PROVIDER_TYPE]: z.string(),
|
||||
[ProviderCredentialFields.ALIBABACLOUD_ROLE_ARN]: z
|
||||
.string()
|
||||
.min(1, "RAM Role ARN is required"),
|
||||
[ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_ID]: z
|
||||
.string()
|
||||
.min(1, "Access Key ID is required"),
|
||||
[ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_SECRET]: z
|
||||
.string()
|
||||
.min(1, "Access Key Secret is required"),
|
||||
[ProviderCredentialFields.ALIBABACLOUD_ROLE_SESSION_NAME]: z
|
||||
.string()
|
||||
.optional(),
|
||||
})
|
||||
: z.object({
|
||||
providerId: z.string(),
|
||||
providerType: z.string(),
|
||||
});
|
||||
|
||||
export const addCredentialsServiceAccountFormSchema = (
|
||||
providerType: ProviderType,
|
||||
|
||||
@@ -8,6 +8,7 @@ export const PROVIDER_TYPES = [
|
||||
"github",
|
||||
"iac",
|
||||
"oraclecloud",
|
||||
"alibabacloud",
|
||||
] as const;
|
||||
|
||||
export type ProviderType = (typeof PROVIDER_TYPES)[number];
|
||||
@@ -22,6 +23,7 @@ export const PROVIDER_DISPLAY_NAMES: Record<ProviderType, string> = {
|
||||
github: "GitHub",
|
||||
iac: "Infrastructure as Code",
|
||||
oraclecloud: "Oracle Cloud Infrastructure",
|
||||
alibabacloud: "Alibaba Cloud",
|
||||
};
|
||||
|
||||
export function getProviderDisplayName(providerId: string): string {
|
||||
|
||||
Reference in New Issue
Block a user