Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 55327704dd | |||
| a8c244849f | |||
| df8d82345d | |||
| 3e4458c8f3 | |||
| e12e0dc1aa | |||
| beb2daa30d | |||
| 14b60b8bee | |||
| cab9b008d1 | |||
| ced0b8def4 | |||
| f31e230537 | |||
| c6cc82c527 | |||
| 5cc3cdc466 | |||
| b7f83da012 | |||
| 4169611a6a | |||
| 9ad2e1ef98 | |||
| 78ce4d8d9b | |||
| 49585ac6c7 | |||
| 0c3c6aea0e | |||
| 144d59de45 | |||
| e3027190de | |||
| 9f4b5e01cf | |||
| 8acdf8e65b | |||
| 35c727c7e4 | |||
| 18fa788268 | |||
| b6e04f507c | |||
| 85c90cac31 | |||
| 4ed27e1aaa | |||
| 53b5030f00 | |||
| 627d6da699 | |||
| 352f136a0f | |||
| ab4d7e0c19 | |||
| 47532cf498 | |||
| afb8701450 | |||
| 942177ae59 | |||
| 750182cd6d | |||
| 9bfa1e740c | |||
| e58e939f55 | |||
| d7f0b5b190 |
@@ -46,6 +46,11 @@ provider/oci:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "prowler/providers/oraclecloud/**"
|
||||
- any-glob-to-any-file: "tests/providers/oraclecloud/**"
|
||||
|
||||
provider/alibabacloud:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "prowler/providers/alibabacloud/**"
|
||||
- any-glob-to-any-file: "tests/providers/alibabacloud/**"
|
||||
|
||||
github_actions:
|
||||
- changed-files:
|
||||
@@ -69,6 +74,8 @@ mutelist:
|
||||
- any-glob-to-any-file: "tests/providers/gcp/lib/mutelist/**"
|
||||
- any-glob-to-any-file: "tests/providers/kubernetes/lib/mutelist/**"
|
||||
- any-glob-to-any-file: "tests/providers/mongodbatlas/lib/mutelist/**"
|
||||
- any-glob-to-any-file: "tests/providers/oci/lib/mutelist/**"
|
||||
- any-glob-to-any-file: "tests/providers/alibabacloud/lib/mutelist/**"
|
||||
|
||||
integration/s3:
|
||||
- changed-files:
|
||||
|
||||
@@ -120,7 +120,7 @@ jobs:
|
||||
# Create and push multi-architecture manifest
|
||||
create-manifest:
|
||||
needs: [setup, container-build-push]
|
||||
if: github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
||||
if: always() && needs.setup.result == 'success' && needs.container-build-push.result == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -198,8 +198,8 @@ jobs:
|
||||
update-ts: ${{ needs.notify-release-started.outputs.message-ts }}
|
||||
|
||||
trigger-deployment:
|
||||
if: github.event_name == 'push'
|
||||
needs: [setup, container-build-push]
|
||||
if: always() && github.event_name == 'push' && needs.setup.result == 'success' && needs.container-build-push.result == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
|
||||
@@ -126,7 +126,7 @@ jobs:
|
||||
# Create and push multi-architecture manifest
|
||||
create-manifest:
|
||||
needs: [setup, container-build-push]
|
||||
if: github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
||||
if: always() && needs.setup.result == 'success' && needs.container-build-push.result == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -204,8 +204,8 @@ jobs:
|
||||
update-ts: ${{ needs.notify-release-started.outputs.message-ts }}
|
||||
|
||||
trigger-deployment:
|
||||
if: github.event_name == 'push'
|
||||
needs: [setup, container-build-push]
|
||||
if: always() && github.event_name == 'push' && needs.setup.result == 'success' && needs.container-build-push.result == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
|
||||
@@ -188,7 +188,7 @@ jobs:
|
||||
# Create and push multi-architecture manifest
|
||||
create-manifest:
|
||||
needs: [setup, container-build-push]
|
||||
if: github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
||||
if: always() && needs.setup.result == 'success' && needs.container-build-push.result == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -280,8 +280,8 @@ jobs:
|
||||
update-ts: ${{ needs.notify-release-started.outputs.message-ts }}
|
||||
|
||||
dispatch-v3-deployment:
|
||||
if: needs.setup.outputs.prowler_version_major == '3'
|
||||
needs: [setup, container-build-push]
|
||||
if: always() && needs.setup.outputs.prowler_version_major == '3' && needs.setup.result == 'success' && needs.container-build-push.result == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
|
||||
@@ -125,7 +125,7 @@ jobs:
|
||||
# Create and push multi-architecture manifest
|
||||
create-manifest:
|
||||
needs: [setup, container-build-push]
|
||||
if: github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
||||
if: always() && needs.setup.result == 'success' && needs.container-build-push.result == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -203,8 +203,8 @@ jobs:
|
||||
update-ts: ${{ needs.notify-release-started.outputs.message-ts }}
|
||||
|
||||
trigger-deployment:
|
||||
if: github.event_name == 'push'
|
||||
needs: [setup, container-build-push]
|
||||
if: always() && github.event_name == 'push' && needs.setup.result == 'success' && needs.container-build-push.result == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
|
||||
@@ -148,9 +148,9 @@ If your workstation's architecture is incompatible, you can resolve this by:
|
||||
### Common Issues with Docker Pull Installation
|
||||
|
||||
> [!Note]
|
||||
If you want to use AWS role assumption (e.g., with the "Connect assuming IAM Role" option), you may need to mount your local `.aws` directory into the container as a volume (e.g., `- "${HOME}/.aws:/home/prowler/.aws:ro"`). There are several ways to configure credentials for Docker containers. See the [Troubleshooting](./docs/troubleshooting.md) section for more details and examples.
|
||||
If you want to use AWS role assumption (e.g., with the "Connect assuming IAM Role" option), you may need to mount your local `.aws` directory into the container as a volume (e.g., `- "${HOME}/.aws:/home/prowler/.aws:ro"`). There are several ways to configure credentials for Docker containers. See the [Troubleshooting](./docs/troubleshooting.mdx) section for more details and examples.
|
||||
|
||||
You can find more information in the [Troubleshooting](./docs/troubleshooting.md) section.
|
||||
You can find more information in the [Troubleshooting](./docs/troubleshooting.mdx) section.
|
||||
|
||||
|
||||
### From GitHub
|
||||
|
||||
@@ -9,6 +9,13 @@ All notable changes to the **Prowler API** are documented in this file.
|
||||
|
||||
---
|
||||
|
||||
## [1.17.2] (Prowler v5.16.2)
|
||||
|
||||
### Security
|
||||
- Updated dependencies to patch security vulnerabilities: Django 5.1.15 (CVE-2025-64460, CVE-2025-13372), Werkzeug 3.1.4 (CVE-2025-66221), sqlparse 0.5.5 (PVE-2025-82038), fonttools 4.60.2 (CVE-2025-66034) [(#9730)](https://github.com/prowler-cloud/prowler/pull/9730)
|
||||
|
||||
---
|
||||
|
||||
## [1.17.1] (Prowler v5.16.1)
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -7,7 +7,7 @@ authors = [{name = "Prowler Engineering", email = "engineering@prowler.com"}]
|
||||
dependencies = [
|
||||
"celery[pytest] (>=5.4.0,<6.0.0)",
|
||||
"dj-rest-auth[with_social,jwt] (==7.0.1)",
|
||||
"django (==5.1.14)",
|
||||
"django (==5.1.15)",
|
||||
"django-allauth[saml] (>=65.8.0,<66.0.0)",
|
||||
"django-celery-beat (>=2.7.0,<3.0.0)",
|
||||
"django-celery-results (>=2.5.1,<3.0.0)",
|
||||
@@ -36,7 +36,10 @@ dependencies = [
|
||||
"drf-simple-apikey (==2.2.1)",
|
||||
"matplotlib (>=3.10.6,<4.0.0)",
|
||||
"reportlab (>=4.4.4,<5.0.0)",
|
||||
"gevent (>=25.9.1,<26.0.0)"
|
||||
"gevent (>=25.9.1,<26.0.0)",
|
||||
"werkzeug (>=3.1.4)",
|
||||
"sqlparse (>=0.5.4)",
|
||||
"fonttools (>=4.60.2)"
|
||||
]
|
||||
description = "Prowler's API (Django/DRF)"
|
||||
license = "Apache-2.0"
|
||||
|
||||
@@ -957,6 +957,26 @@ class ProcessorFilter(FilterSet):
|
||||
)
|
||||
|
||||
|
||||
class IntegrationGitHubFindingsFilter(FilterSet):
|
||||
# To be expanded as needed
|
||||
finding_id = UUIDFilter(field_name="id", lookup_expr="exact")
|
||||
finding_id__in = UUIDInFilter(field_name="id", lookup_expr="in")
|
||||
|
||||
class Meta:
|
||||
model = Finding
|
||||
fields = {}
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
# Validate that there is at least one filter provided
|
||||
if not self.data:
|
||||
raise ValidationError(
|
||||
{
|
||||
"findings": "No finding filters provided. At least one filter is required."
|
||||
}
|
||||
)
|
||||
return super().filter_queryset(queryset)
|
||||
|
||||
|
||||
class IntegrationJiraFindingsFilter(FilterSet):
|
||||
# To be expanded as needed
|
||||
finding_id = UUIDFilter(field_name="id", lookup_expr="exact")
|
||||
|
||||
@@ -1586,6 +1586,7 @@ class Integration(RowLevelSecurityProtectedModel):
|
||||
class IntegrationChoices(models.TextChoices):
|
||||
AMAZON_S3 = "amazon_s3", _("Amazon S3")
|
||||
AWS_SECURITY_HUB = "aws_security_hub", _("AWS Security Hub")
|
||||
GITHUB = "github", _("GitHub")
|
||||
JIRA = "jira", _("JIRA")
|
||||
SLACK = "slack", _("Slack")
|
||||
|
||||
|
||||
@@ -287,6 +287,20 @@ def prowler_integration_connection_test(integration: Integration) -> Connection:
|
||||
integration.save()
|
||||
|
||||
return connection
|
||||
elif integration.integration_type == Integration.IntegrationChoices.GITHUB:
|
||||
from prowler.lib.outputs.github.github import GitHub
|
||||
|
||||
github_connection = GitHub.test_connection(
|
||||
**integration.credentials,
|
||||
raise_on_exception=False,
|
||||
)
|
||||
repositories = (
|
||||
github_connection.repositories if github_connection.is_connected else {}
|
||||
)
|
||||
with rls_transaction(str(integration.tenant_id)):
|
||||
integration.configuration["repositories"] = repositories
|
||||
integration.save()
|
||||
return github_connection
|
||||
elif integration.integration_type == Integration.IntegrationChoices.JIRA:
|
||||
jira_connection = Jira.test_connection(
|
||||
**integration.credentials,
|
||||
@@ -406,9 +420,22 @@ def get_findings_metadata_no_aggregations(tenant_id: str, filtered_queryset):
|
||||
return serializer.data
|
||||
|
||||
|
||||
def initialize_prowler_integration(integration: Integration) -> Jira:
|
||||
def initialize_prowler_integration(integration: Integration):
|
||||
# TODO Refactor other integrations to use this function
|
||||
if integration.integration_type == Integration.IntegrationChoices.JIRA:
|
||||
if integration.integration_type == Integration.IntegrationChoices.GITHUB:
|
||||
from prowler.lib.outputs.github.exceptions import GitHubAuthenticationError
|
||||
from prowler.lib.outputs.github.github import GitHub
|
||||
|
||||
try:
|
||||
return GitHub(**integration.credentials)
|
||||
except GitHubAuthenticationError as github_auth_error:
|
||||
with rls_transaction(str(integration.tenant_id)):
|
||||
integration.configuration["repositories"] = {}
|
||||
integration.connected = False
|
||||
integration.connection_last_checked_at = datetime.now(tz=timezone.utc)
|
||||
integration.save()
|
||||
raise github_auth_error
|
||||
elif integration.integration_type == Integration.IntegrationChoices.JIRA:
|
||||
try:
|
||||
return Jira(**integration.credentials)
|
||||
except JiraBasicAuthError as jira_auth_error:
|
||||
|
||||
@@ -67,6 +67,14 @@ class SecurityHubConfigSerializer(BaseValidateSerializer):
|
||||
resource_name = "integrations"
|
||||
|
||||
|
||||
class GitHubConfigSerializer(BaseValidateSerializer):
|
||||
owner = serializers.CharField(read_only=True)
|
||||
repositories = serializers.DictField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
resource_name = "integrations"
|
||||
|
||||
|
||||
class JiraConfigSerializer(BaseValidateSerializer):
|
||||
domain = serializers.CharField(read_only=True)
|
||||
issue_types = serializers.ListField(
|
||||
@@ -93,6 +101,14 @@ class AWSCredentialSerializer(BaseValidateSerializer):
|
||||
resource_name = "integrations"
|
||||
|
||||
|
||||
class GitHubCredentialSerializer(BaseValidateSerializer):
|
||||
token = serializers.CharField(required=True)
|
||||
owner = serializers.CharField(required=False)
|
||||
|
||||
class Meta:
|
||||
resource_name = "integrations"
|
||||
|
||||
|
||||
class JiraCredentialSerializer(BaseValidateSerializer):
|
||||
user_mail = serializers.EmailField(required=True)
|
||||
api_token = serializers.CharField(required=True)
|
||||
@@ -153,6 +169,23 @@ class JiraCredentialSerializer(BaseValidateSerializer):
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"title": "GitHub Credentials",
|
||||
"properties": {
|
||||
"token": {
|
||||
"type": "string",
|
||||
"description": "GitHub Personal Access Token (PAT) with repo scope. Can be generated from "
|
||||
"GitHub Settings > Developer settings > Personal access tokens.",
|
||||
},
|
||||
"owner": {
|
||||
"type": "string",
|
||||
"description": "Repository owner (username or organization name). Optional - if not provided, "
|
||||
"all accessible repositories will be available.",
|
||||
},
|
||||
},
|
||||
"required": ["token"],
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"title": "JIRA Credentials",
|
||||
@@ -221,6 +254,14 @@ class IntegrationCredentialField(serializers.JSONField):
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"title": "GitHub",
|
||||
"description": "GitHub integration does not accept any configuration in the payload. Leave it as an "
|
||||
"empty JSON object (`{}`).",
|
||||
"properties": {},
|
||||
"additionalProperties": False,
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"title": "JIRA",
|
||||
|
||||
@@ -54,6 +54,8 @@ from api.models import (
|
||||
from api.rls import Tenant
|
||||
from api.v1.serializer_utils.integrations import (
|
||||
AWSCredentialSerializer,
|
||||
GitHubConfigSerializer,
|
||||
GitHubCredentialSerializer,
|
||||
IntegrationConfigField,
|
||||
IntegrationCredentialField,
|
||||
JiraConfigSerializer,
|
||||
@@ -2432,6 +2434,28 @@ class BaseWriteIntegrationSerializer(BaseWriteSerializer):
|
||||
)
|
||||
config_serializer = SecurityHubConfigSerializer
|
||||
credentials_serializers = [AWSCredentialSerializer]
|
||||
elif integration_type == Integration.IntegrationChoices.GITHUB:
|
||||
if providers:
|
||||
raise serializers.ValidationError(
|
||||
{
|
||||
"providers": "Relationship field is not accepted. This integration applies to all providers."
|
||||
}
|
||||
)
|
||||
if configuration:
|
||||
raise serializers.ValidationError(
|
||||
{
|
||||
"configuration": "This integration does not support custom configuration."
|
||||
}
|
||||
)
|
||||
config_serializer = GitHubConfigSerializer
|
||||
# Create non-editable configuration for GitHub integration
|
||||
configuration.update(
|
||||
{
|
||||
"repositories": {},
|
||||
"owner": credentials.get("owner", ""),
|
||||
}
|
||||
)
|
||||
credentials_serializers = [GitHubCredentialSerializer]
|
||||
elif integration_type == Integration.IntegrationChoices.JIRA:
|
||||
if providers:
|
||||
raise serializers.ValidationError(
|
||||
@@ -2519,7 +2543,11 @@ class IntegrationSerializer(RLSSerializer):
|
||||
for provider in representation["providers"]
|
||||
if provider["id"] in allowed_provider_ids
|
||||
]
|
||||
if instance.integration_type == Integration.IntegrationChoices.JIRA:
|
||||
if instance.integration_type == Integration.IntegrationChoices.GITHUB:
|
||||
representation["configuration"].update(
|
||||
{"owner": instance.credentials.get("owner", "")}
|
||||
)
|
||||
elif instance.integration_type == Integration.IntegrationChoices.JIRA:
|
||||
representation["configuration"].update(
|
||||
{"domain": instance.credentials.get("domain")}
|
||||
)
|
||||
@@ -2666,6 +2694,51 @@ class IntegrationUpdateSerializer(BaseWriteIntegrationSerializer):
|
||||
return representation
|
||||
|
||||
|
||||
class IntegrationGitHubDispatchSerializer(BaseSerializerV1):
|
||||
"""
|
||||
Serializer for dispatching findings to GitHub integration.
|
||||
"""
|
||||
|
||||
repository = serializers.CharField(required=True)
|
||||
labels = serializers.ListField(
|
||||
child=serializers.CharField(), required=False, default=list
|
||||
)
|
||||
|
||||
class JSONAPIMeta:
|
||||
resource_name = "integrations-github-dispatches"
|
||||
|
||||
def validate(self, attrs):
|
||||
validated_attrs = super().validate(attrs)
|
||||
integration_instance = Integration.objects.get(
|
||||
id=self.context.get("integration_id")
|
||||
)
|
||||
if (
|
||||
integration_instance.integration_type
|
||||
!= Integration.IntegrationChoices.GITHUB
|
||||
):
|
||||
raise ValidationError(
|
||||
{
|
||||
"integration_type": "The given integration is not a GitHub integration"
|
||||
}
|
||||
)
|
||||
|
||||
if not integration_instance.enabled:
|
||||
raise ValidationError(
|
||||
{"integration": "The given integration is not enabled"}
|
||||
)
|
||||
|
||||
repository = attrs.get("repository")
|
||||
if repository not in integration_instance.configuration.get("repositories", {}):
|
||||
raise ValidationError(
|
||||
{
|
||||
"repository": "The given repository is not available for this GitHub integration. Refresh the "
|
||||
"connection if this is an error."
|
||||
}
|
||||
)
|
||||
|
||||
return validated_attrs
|
||||
|
||||
|
||||
class IntegrationJiraDispatchSerializer(BaseSerializerV1):
|
||||
"""
|
||||
Serializer for dispatching findings to JIRA integration.
|
||||
|
||||
@@ -12,6 +12,7 @@ from api.v1.views import (
|
||||
FindingViewSet,
|
||||
GithubSocialLoginView,
|
||||
GoogleSocialLoginView,
|
||||
IntegrationGitHubViewSet,
|
||||
IntegrationJiraViewSet,
|
||||
IntegrationViewSet,
|
||||
InvitationAcceptViewSet,
|
||||
@@ -94,6 +95,9 @@ users_router.register(r"memberships", MembershipViewSet, basename="user-membersh
|
||||
integrations_router = routers.NestedSimpleRouter(
|
||||
router, r"integrations", lookup="integration"
|
||||
)
|
||||
integrations_router.register(
|
||||
r"github", IntegrationGitHubViewSet, basename="integration-github"
|
||||
)
|
||||
integrations_router.register(
|
||||
r"jira", IntegrationJiraViewSet, basename="integration-jira"
|
||||
)
|
||||
|
||||
@@ -83,6 +83,7 @@ from tasks.tasks import (
|
||||
check_provider_connection_task,
|
||||
delete_provider_task,
|
||||
delete_tenant_task,
|
||||
github_integration_task,
|
||||
jira_integration_task,
|
||||
mute_historical_findings_task,
|
||||
perform_scan_task,
|
||||
@@ -105,6 +106,7 @@ from api.filters import (
|
||||
DailySeveritySummaryFilter,
|
||||
FindingFilter,
|
||||
IntegrationFilter,
|
||||
IntegrationGitHubFindingsFilter,
|
||||
IntegrationJiraFindingsFilter,
|
||||
InvitationFilter,
|
||||
LatestFindingFilter,
|
||||
@@ -190,6 +192,7 @@ from api.v1.serializers import (
|
||||
FindingSerializer,
|
||||
FindingsSeverityOverTimeSerializer,
|
||||
IntegrationCreateSerializer,
|
||||
IntegrationGitHubDispatchSerializer,
|
||||
IntegrationJiraDispatchSerializer,
|
||||
IntegrationSerializer,
|
||||
IntegrationUpdateSerializer,
|
||||
@@ -5131,6 +5134,86 @@ class IntegrationViewSet(BaseRLSViewSet):
|
||||
)
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
dispatches=extend_schema(
|
||||
tags=["Integration"],
|
||||
summary="Send findings to a GitHub integration",
|
||||
description="Send a set of filtered findings to the given GitHub integration as issues. At least one finding "
|
||||
"filter must be provided.",
|
||||
responses={202: OpenApiResponse(response=TaskSerializer)},
|
||||
filters=True,
|
||||
)
|
||||
)
|
||||
class IntegrationGitHubViewSet(BaseRLSViewSet):
|
||||
queryset = Finding.all_objects.all()
|
||||
serializer_class = IntegrationGitHubDispatchSerializer
|
||||
http_method_names = ["post"]
|
||||
filter_backends = [CustomDjangoFilterBackend]
|
||||
filterset_class = IntegrationGitHubFindingsFilter
|
||||
# RBAC required permissions
|
||||
required_permissions = [Permissions.MANAGE_INTEGRATIONS]
|
||||
|
||||
@extend_schema(exclude=True)
|
||||
def create(self, request, *args, **kwargs):
|
||||
raise MethodNotAllowed(method="POST")
|
||||
|
||||
def get_queryset(self):
|
||||
tenant_id = self.request.tenant_id
|
||||
user_roles = get_role(self.request.user)
|
||||
if user_roles.unlimited_visibility:
|
||||
# User has unlimited visibility, return all findings
|
||||
queryset = Finding.all_objects.filter(tenant_id=tenant_id)
|
||||
else:
|
||||
# User lacks permission, filter findings based on provider groups associated with the role
|
||||
queryset = Finding.all_objects.filter(
|
||||
scan__provider__in=get_providers(user_roles)
|
||||
)
|
||||
|
||||
return queryset
|
||||
|
||||
@action(detail=False, methods=["post"], url_name="dispatches")
|
||||
def dispatches(self, request, integration_pk=None):
|
||||
get_object_or_404(Integration, pk=integration_pk)
|
||||
serializer = self.get_serializer(
|
||||
data=request.data, context={"integration_id": integration_pk}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
if self.filter_queryset(self.get_queryset()).count() == 0:
|
||||
raise ValidationError(
|
||||
{"findings": "No findings match the provided filters"}
|
||||
)
|
||||
|
||||
finding_ids = [
|
||||
str(finding_id)
|
||||
for finding_id in self.filter_queryset(self.get_queryset()).values_list(
|
||||
"id", flat=True
|
||||
)
|
||||
]
|
||||
repository = serializer.validated_data["repository"]
|
||||
labels = serializer.validated_data.get("labels", [])
|
||||
|
||||
with transaction.atomic():
|
||||
task = github_integration_task.delay(
|
||||
tenant_id=self.request.tenant_id,
|
||||
integration_id=integration_pk,
|
||||
repository=repository,
|
||||
labels=labels,
|
||||
finding_ids=finding_ids,
|
||||
)
|
||||
prowler_task = Task.objects.get(id=task.id)
|
||||
serializer = TaskSerializer(prowler_task)
|
||||
return Response(
|
||||
data=serializer.data,
|
||||
status=status.HTTP_202_ACCEPTED,
|
||||
headers={
|
||||
"Content-Location": reverse(
|
||||
"task-detail", kwargs={"pk": prowler_task.id}
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
dispatches=extend_schema(
|
||||
tags=["Integration"],
|
||||
|
||||
@@ -17,11 +17,11 @@ from prowler.lib.outputs.html.html import HTML
|
||||
from prowler.lib.outputs.ocsf.ocsf import OCSF
|
||||
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
|
||||
from prowler.providers.common.models import Connection
|
||||
from prowler.providers.aws.lib.security_hub.exceptions.exceptions import (
|
||||
SecurityHubNoEnabledRegionsError,
|
||||
)
|
||||
from prowler.providers.aws.lib.security_hub.security_hub import SecurityHub
|
||||
from prowler.providers.common.models import Connection
|
||||
|
||||
logger = get_task_logger(__name__)
|
||||
|
||||
@@ -436,6 +436,81 @@ def upload_security_hub_integration(
|
||||
return False
|
||||
|
||||
|
||||
def send_findings_to_github(
|
||||
tenant_id: str,
|
||||
integration_id: str,
|
||||
repository: str,
|
||||
labels: list[str],
|
||||
finding_ids: list[str],
|
||||
):
|
||||
with rls_transaction(tenant_id):
|
||||
integration = Integration.objects.get(id=integration_id)
|
||||
github_integration = initialize_prowler_integration(integration)
|
||||
|
||||
num_issues_created = 0
|
||||
for finding_id in finding_ids:
|
||||
with rls_transaction(tenant_id):
|
||||
finding_instance = (
|
||||
Finding.all_objects.select_related("scan__provider")
|
||||
.prefetch_related("resources")
|
||||
.get(id=finding_id)
|
||||
)
|
||||
|
||||
# Extract resource information
|
||||
resource = (
|
||||
finding_instance.resources.first()
|
||||
if finding_instance.resources.exists()
|
||||
else None
|
||||
)
|
||||
resource_uid = resource.uid if resource else ""
|
||||
resource_name = resource.name if resource else ""
|
||||
resource_tags = {}
|
||||
if resource and hasattr(resource, "tags"):
|
||||
resource_tags = resource.get_tags(tenant_id)
|
||||
|
||||
# Get region
|
||||
region = resource.region if resource and resource.region else ""
|
||||
|
||||
# Extract remediation information from check_metadata
|
||||
check_metadata = finding_instance.check_metadata
|
||||
remediation = check_metadata.get("remediation", {})
|
||||
recommendation = remediation.get("recommendation", {})
|
||||
remediation_code = remediation.get("code", {})
|
||||
|
||||
# Send the individual finding to GitHub
|
||||
result = github_integration.send_finding(
|
||||
check_id=finding_instance.check_id,
|
||||
check_title=check_metadata.get("checktitle", ""),
|
||||
severity=finding_instance.severity,
|
||||
status=finding_instance.status,
|
||||
status_extended=finding_instance.status_extended or "",
|
||||
provider=finding_instance.scan.provider.provider,
|
||||
region=region,
|
||||
resource_uid=resource_uid,
|
||||
resource_name=resource_name,
|
||||
risk=check_metadata.get("risk", ""),
|
||||
recommendation_text=recommendation.get("text", ""),
|
||||
recommendation_url=recommendation.get("url", ""),
|
||||
remediation_code_native_iac=remediation_code.get("nativeiac", ""),
|
||||
remediation_code_terraform=remediation_code.get("terraform", ""),
|
||||
remediation_code_cli=remediation_code.get("cli", ""),
|
||||
remediation_code_other=remediation_code.get("other", ""),
|
||||
resource_tags=resource_tags,
|
||||
compliance=finding_instance.compliance or {},
|
||||
repository=repository,
|
||||
issue_labels=labels,
|
||||
)
|
||||
if result:
|
||||
num_issues_created += 1
|
||||
else:
|
||||
logger.error(f"Failed to send finding {finding_id} to GitHub")
|
||||
|
||||
return {
|
||||
"created_count": num_issues_created,
|
||||
"failed_count": len(finding_ids) - num_issues_created,
|
||||
}
|
||||
|
||||
|
||||
def send_findings_to_jira(
|
||||
tenant_id: str,
|
||||
integration_id: str,
|
||||
|
||||
@@ -28,6 +28,7 @@ from tasks.jobs.export import (
|
||||
_upload_to_s3,
|
||||
)
|
||||
from tasks.jobs.integrations import (
|
||||
send_findings_to_github,
|
||||
send_findings_to_jira,
|
||||
upload_s3_integration,
|
||||
upload_security_hub_integration,
|
||||
@@ -791,6 +792,23 @@ def security_hub_integration_task(
|
||||
return upload_security_hub_integration(tenant_id, provider_id, scan_id)
|
||||
|
||||
|
||||
@shared_task(
|
||||
base=RLSTask,
|
||||
name="integration-github",
|
||||
queue="integrations",
|
||||
)
|
||||
def github_integration_task(
|
||||
tenant_id: str,
|
||||
integration_id: str,
|
||||
repository: str,
|
||||
labels: list[str],
|
||||
finding_ids: list[str],
|
||||
):
|
||||
return send_findings_to_github(
|
||||
tenant_id, integration_id, repository, labels, finding_ids
|
||||
)
|
||||
|
||||
|
||||
@shared_task(
|
||||
base=RLSTask,
|
||||
name="integration-jira",
|
||||
|
||||
@@ -312,3 +312,28 @@ def create_table_row_dropdown(table_rows: list) -> html.Div:
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def create_category_dropdown(categories: list) -> html.Div:
|
||||
"""
|
||||
Dropdown to select the category.
|
||||
Args:
|
||||
categories (list): List of categories.
|
||||
Returns:
|
||||
html.Div: Dropdown to select the category.
|
||||
"""
|
||||
return html.Div(
|
||||
[
|
||||
html.Label(
|
||||
"Category:", className="text-prowler-stone-900 font-bold text-sm"
|
||||
),
|
||||
dcc.Dropdown(
|
||||
id="category-filter",
|
||||
options=[{"label": i, "value": i} for i in categories],
|
||||
value=["All"],
|
||||
clearable=False,
|
||||
multi=True,
|
||||
style={"color": "#000000"},
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -12,6 +12,7 @@ def create_layout_overview(
|
||||
provider_dropdown: html.Div,
|
||||
table_row_dropdown: html.Div,
|
||||
status_dropdown: html.Div,
|
||||
category_dropdown: html.Div,
|
||||
table_div_header: html.Div,
|
||||
amount_providers: int,
|
||||
) -> html.Div:
|
||||
@@ -51,8 +52,9 @@ def create_layout_overview(
|
||||
html.Div([service_dropdown], className=""),
|
||||
html.Div([provider_dropdown], className=""),
|
||||
html.Div([status_dropdown], className=""),
|
||||
html.Div([category_dropdown], className=""),
|
||||
],
|
||||
className="grid gap-x-4 mb-[30px] sm:grid-cols-2 lg:grid-cols-4",
|
||||
className="grid gap-x-4 mb-[30px] sm:grid-cols-2 lg:grid-cols-5",
|
||||
),
|
||||
html.Div(
|
||||
[
|
||||
|
||||
@@ -35,6 +35,7 @@ from dashboard.config import (
|
||||
from dashboard.lib.cards import create_provider_card
|
||||
from dashboard.lib.dropdowns import (
|
||||
create_account_dropdown,
|
||||
create_category_dropdown,
|
||||
create_date_dropdown,
|
||||
create_provider_dropdown,
|
||||
create_region_dropdown,
|
||||
@@ -343,6 +344,18 @@ else:
|
||||
status = [x for x in status if str(x) != "nan" and x.__class__.__name__ == "str"]
|
||||
|
||||
status_dropdown = create_status_dropdown(status)
|
||||
|
||||
# Create the category dropdown
|
||||
categories = []
|
||||
if "CATEGORIES" in data.columns:
|
||||
for cat_list in data["CATEGORIES"].dropna().unique():
|
||||
if cat_list and str(cat_list) != "nan":
|
||||
for cat in str(cat_list).split(","):
|
||||
cat = cat.strip()
|
||||
if cat and cat not in categories:
|
||||
categories.append(cat)
|
||||
categories = ["All"] + sorted(categories)
|
||||
category_dropdown = create_category_dropdown(categories)
|
||||
table_div_header = []
|
||||
table_div_header.append(
|
||||
html.Div(
|
||||
@@ -504,6 +517,7 @@ else:
|
||||
provider_dropdown,
|
||||
table_row_dropdown,
|
||||
status_dropdown,
|
||||
category_dropdown,
|
||||
table_div_header,
|
||||
len(data["PROVIDER"].unique()),
|
||||
)
|
||||
@@ -540,6 +554,8 @@ else:
|
||||
Output("table-rows", "options"),
|
||||
Output("status-filter", "value"),
|
||||
Output("status-filter", "options"),
|
||||
Output("category-filter", "value"),
|
||||
Output("category-filter", "options"),
|
||||
Output("aws_card", "n_clicks"),
|
||||
Output("azure_card", "n_clicks"),
|
||||
Output("gcp_card", "n_clicks"),
|
||||
@@ -557,6 +573,7 @@ else:
|
||||
Input("provider-filter", "value"),
|
||||
Input("table-rows", "value"),
|
||||
Input("status-filter", "value"),
|
||||
Input("category-filter", "value"),
|
||||
Input("search-input", "value"),
|
||||
Input("aws_card", "n_clicks"),
|
||||
Input("azure_card", "n_clicks"),
|
||||
@@ -582,6 +599,7 @@ def filter_data(
|
||||
provider_values,
|
||||
table_row_values,
|
||||
status_values,
|
||||
category_values,
|
||||
search_value,
|
||||
aws_clicks,
|
||||
azure_clicks,
|
||||
@@ -965,6 +983,41 @@ def filter_data(
|
||||
|
||||
status_filter_options = ["All"] + list(filtered_data["STATUS"].unique())
|
||||
|
||||
# Filter Category
|
||||
if "CATEGORIES" in filtered_data.columns:
|
||||
if category_values == ["All"]:
|
||||
updated_category_values = None
|
||||
elif "All" in category_values and len(category_values) > 1:
|
||||
category_values.remove("All")
|
||||
updated_category_values = category_values
|
||||
elif len(category_values) == 0:
|
||||
updated_category_values = None
|
||||
category_values = ["All"]
|
||||
else:
|
||||
updated_category_values = category_values
|
||||
|
||||
if updated_category_values:
|
||||
filtered_data = filtered_data[
|
||||
filtered_data["CATEGORIES"].apply(
|
||||
lambda x: any(
|
||||
cat.strip() in updated_category_values
|
||||
for cat in str(x).split(",")
|
||||
if str(x) != "nan"
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
category_filter_options = ["All"]
|
||||
for cat_list in filtered_data["CATEGORIES"].dropna().unique():
|
||||
if cat_list and str(cat_list) != "nan":
|
||||
for cat in str(cat_list).split(","):
|
||||
cat = cat.strip()
|
||||
if cat and cat not in category_filter_options:
|
||||
category_filter_options.append(cat)
|
||||
category_filter_options = sorted(category_filter_options)
|
||||
else:
|
||||
category_filter_options = ["All"]
|
||||
|
||||
if len(filtered_data_sp) == 0:
|
||||
fig = px.pie()
|
||||
fig.update_layout(
|
||||
@@ -1512,6 +1565,8 @@ def filter_data(
|
||||
table_row_options,
|
||||
status_values,
|
||||
status_filter_options,
|
||||
category_values,
|
||||
category_filter_options,
|
||||
aws_clicks,
|
||||
azure_clicks,
|
||||
gcp_clicks,
|
||||
@@ -1549,6 +1604,8 @@ def filter_data(
|
||||
table_row_options,
|
||||
status_values,
|
||||
status_filter_options,
|
||||
category_values,
|
||||
category_filter_options,
|
||||
aws_clicks,
|
||||
azure_clicks,
|
||||
gcp_clicks,
|
||||
|
||||
@@ -479,6 +479,66 @@ Effective headers and section titles enhance document readability and structure,
|
||||
|
||||
---
|
||||
|
||||
## Version Badge for Feature Documentation
|
||||
|
||||
The Version Badge component indicates when a specific feature or functionality was introduced in Prowler. This component is located at `docs/snippets/version-badge.mdx` and should be used consistently across the documentation.
|
||||
|
||||
### When to Use the Version Badge
|
||||
|
||||
Use the Version Badge when documenting:
|
||||
|
||||
* New features added in a specific version.
|
||||
* New CLI options or flags.
|
||||
* New API endpoints or SDK methods.
|
||||
* New compliance frameworks or security checks.
|
||||
* Breaking changes or deprecated features (with appropriate context).
|
||||
|
||||
### How to Use the Version Badge
|
||||
|
||||
1. **Import the Component**
|
||||
|
||||
At the top of the MDX file, import the snippet:
|
||||
|
||||
```mdx
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
```
|
||||
|
||||
2. **Place the Badge**
|
||||
|
||||
Insert the badge immediately after the section header or feature title:
|
||||
|
||||
```mdx
|
||||
## New Feature Name
|
||||
|
||||
<VersionBadge version="4.5.0" />
|
||||
|
||||
Description of the feature...
|
||||
```
|
||||
|
||||
3. **Version Format**
|
||||
|
||||
Use semantic versioning format (e.g., `4.5.0`, `5.0.0`). Do not include the "v" prefix.
|
||||
|
||||
### Placement Guidelines
|
||||
|
||||
* Place the Version Badge on its own line, directly below the header.
|
||||
* Leave a blank line after the badge before continuing with the content.
|
||||
* For subsections, place the badge only if the subsection introduces something new independently from the parent section.
|
||||
|
||||
**Example:**
|
||||
|
||||
```mdx
|
||||
## Tag-Based Scanning
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
||||
<VersionBadge version="4.3.0" />
|
||||
|
||||
Tag-Based Scanning allows filtering resources by AWS tags during security assessments...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Avoid Assumptions Regarding Audience’s Expertise
|
||||
|
||||
### Understand Your Audience’s Expertise
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
---
|
||||
title: 'Alibaba Cloud Provider'
|
||||
---
|
||||
|
||||
This page details the [Alibaba Cloud](https://www.alibabacloud.com/) provider implementation in Prowler.
|
||||
|
||||
By default, Prowler will audit all the Alibaba Cloud regions that are available. To configure it, follow the [Alibaba Cloud getting started guide](/user-guide/providers/alibabacloud/getting-started-alibabacloud).
|
||||
|
||||
## Alibaba Cloud Provider Classes Architecture
|
||||
|
||||
The Alibaba Cloud provider implementation follows the general [Provider structure](/developer-guide/provider). This section focuses on the Alibaba Cloud-specific implementation, highlighting how the generic provider concepts are realized for Alibaba Cloud in Prowler. For a full overview of the provider pattern, base classes, and extension guidelines, see [Provider documentation](/developer-guide/provider).
|
||||
|
||||
### Main Class
|
||||
|
||||
- **Location:** [`prowler/providers/alibabacloud/alibabacloud_provider.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/alibabacloud/alibabacloud_provider.py)
|
||||
- **Base Class:** Inherits from `Provider` (see [base class details](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/common/provider.py)).
|
||||
- **Purpose:** Central orchestrator for Alibaba Cloud-specific logic, session management, credential validation, and configuration.
|
||||
- **Key Alibaba Cloud Responsibilities:**
|
||||
- Initializes and manages Alibaba Cloud sessions (supports Access Keys, STS Temporary Credentials, RAM Role Assumption, ECS RAM Role, OIDC Authentication, and Credentials URI).
|
||||
- Validates credentials using STS GetCallerIdentity.
|
||||
- Loads and manages configuration, mutelist, and fixer settings.
|
||||
- Discovers and manages Alibaba Cloud regions.
|
||||
- Provides properties and methods for downstream Alibaba Cloud service classes to access session, identity, and configuration data.
|
||||
|
||||
### Data Models
|
||||
|
||||
- **Location:** [`prowler/providers/alibabacloud/models.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/alibabacloud/models.py)
|
||||
- **Purpose:** Define structured data for Alibaba Cloud identity, session, credentials, and region info.
|
||||
- **Key Alibaba Cloud Models:**
|
||||
- `AlibabaCloudCallerIdentity`: Stores caller identity information from STS GetCallerIdentity (account_id, principal_id, arn, identity_type).
|
||||
- `AlibabaCloudIdentityInfo`: Holds Alibaba Cloud identity metadata including account ID, user info, profile, and audited regions.
|
||||
- `AlibabaCloudCredentials`: Stores credentials (access_key_id, access_key_secret, security_token).
|
||||
- `AlibabaCloudRegion`: Represents an Alibaba Cloud region with region_id and region_name.
|
||||
- `AlibabaCloudSession`: Manages the session and provides methods to create service clients.
|
||||
|
||||
### `AlibabaCloudService` (Service Base Class)
|
||||
|
||||
- **Location:** [`prowler/providers/alibabacloud/lib/service/service.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/alibabacloud/lib/service/service.py)
|
||||
- **Purpose:** Abstract base class that all Alibaba Cloud service-specific classes inherit from. This implements the generic service pattern (described in [service page](/developer-guide/services#service-base-class)) specifically for Alibaba Cloud.
|
||||
- **Key Alibaba Cloud Responsibilities:**
|
||||
- Receives an `AlibabacloudProvider` instance to access session, identity, and configuration.
|
||||
- Manages regional clients for services that are region-specific.
|
||||
- Provides `__threading_call__` method to make API calls in parallel by region or resource.
|
||||
- Exposes common audit context (`audited_account`, `audited_account_name`, `audit_resources`, `audit_config`) to subclasses.
|
||||
|
||||
### Exception Handling
|
||||
|
||||
- **Location:** [`prowler/providers/alibabacloud/exceptions/exceptions.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/alibabacloud/exceptions/exceptions.py)
|
||||
- **Purpose:** Custom exception classes for Alibaba Cloud-specific error handling.
|
||||
- **Key Alibaba Cloud Exceptions:**
|
||||
- `AlibabaCloudClientError`: General client errors
|
||||
- `AlibabaCloudNoCredentialsError`: No credentials found
|
||||
- `AlibabaCloudInvalidCredentialsError`: Invalid credentials provided
|
||||
- `AlibabaCloudSetUpSessionError`: Session setup failures
|
||||
- `AlibabaCloudAssumeRoleError`: RAM role assumption failures
|
||||
- `AlibabaCloudInvalidRegionError`: Invalid region specified
|
||||
- `AlibabaCloudHTTPError`: HTTP/API errors
|
||||
|
||||
### Session and Utility Helpers
|
||||
|
||||
- **Location:** [`prowler/providers/alibabacloud/lib/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/alibabacloud/lib/)
|
||||
- **Purpose:** Helpers for argument parsing, mutelist management, and other cross-cutting concerns.
|
||||
|
||||
## Specific Patterns in Alibaba Cloud Services
|
||||
|
||||
The generic service pattern is described in [service page](/developer-guide/services#service-structure-and-initialisation). You can find all the currently implemented services in the following locations:
|
||||
|
||||
- Directly in the code, in location [`prowler/providers/alibabacloud/services/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/alibabacloud/services)
|
||||
- In the [Prowler Hub](https://hub.prowler.com/) for a more human-readable view.
|
||||
|
||||
The best reference to understand how to implement a new service is following the [service implementation documentation](/developer-guide/services#adding-a-new-service) and taking other services already implemented as reference. In next subsection you can find a list of common patterns that are used across all Alibaba Cloud services.
|
||||
|
||||
### Alibaba Cloud Service Common Patterns
|
||||
|
||||
- Services communicate with Alibaba Cloud using the official Alibaba Cloud Python SDKs. Documentation for individual services can be found in the [Alibaba Cloud SDK documentation](https://www.alibabacloud.com/help/en/sdk).
|
||||
- Every Alibaba Cloud service class inherits from `AlibabaCloudService`, ensuring access to session, identity, configuration, and client utilities.
|
||||
- The constructor (`__init__`) always calls `super().__init__` with the service name, provider, and optionally `global_service=True` for services that are not regional (e.g., RAM).
|
||||
- Resource containers **must** be initialized in the constructor. For regional services, resources are typically stored in dictionaries keyed by region and resource ID.
|
||||
- All Alibaba Cloud resources are represented as Pydantic `BaseModel` classes, providing type safety and structured access to resource attributes.
|
||||
- Alibaba Cloud SDK functions are wrapped in try/except blocks, with specific handling for errors, always logging errors.
|
||||
- Regional services use `self.regional_clients` to maintain clients for each audited region.
|
||||
- The `__threading_call__` method is used for parallel execution across regions or resources.
|
||||
|
||||
### Example Service Implementation
|
||||
|
||||
```python
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.providers.alibabacloud.lib.service.service import AlibabaCloudService
|
||||
|
||||
|
||||
class MyService(AlibabaCloudService):
|
||||
def __init__(self, provider):
|
||||
# Initialize parent class with service name
|
||||
super().__init__("myservice", provider)
|
||||
|
||||
# Initialize resource containers
|
||||
self.resources = {}
|
||||
|
||||
# Discover resources using threading
|
||||
self.__threading_call__(self._describe_resources)
|
||||
|
||||
def _describe_resources(self, regional_client):
|
||||
try:
|
||||
region = regional_client.region
|
||||
response = regional_client.describe_resources()
|
||||
|
||||
for resource in response.body.resources:
|
||||
self.resources[resource.id] = MyResource(
|
||||
id=resource.id,
|
||||
name=resource.name,
|
||||
region=region,
|
||||
# ... other attributes
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
```
|
||||
|
||||
## Specific Patterns in Alibaba Cloud Checks
|
||||
|
||||
The Alibaba Cloud checks pattern is described in [checks page](/developer-guide/checks). You can find all the currently implemented checks:
|
||||
|
||||
- Directly in the code, within each service folder, each check has its own folder named after the name of the check. (e.g. [`prowler/providers/alibabacloud/services/ram/ram_no_root_access_key/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/alibabacloud/services/ram/ram_no_root_access_key))
|
||||
- In the [Prowler Hub](https://hub.prowler.com/) for a more human-readable view.
|
||||
|
||||
The best reference to understand how to implement a new check is following the [check implementation documentation](/developer-guide/checks#creating-a-check) and taking other similar checks as reference.
|
||||
|
||||
### Check Report Class
|
||||
|
||||
The `CheckReportAlibabaCloud` class models a single finding for an Alibaba Cloud resource in a check report. It is defined in [`prowler/lib/check/models.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/lib/check/models.py) and inherits from the generic `Check_Report` base class.
|
||||
|
||||
#### Purpose
|
||||
|
||||
`CheckReportAlibabaCloud` extends the base report structure with Alibaba Cloud-specific fields, enabling detailed tracking of the resource, resource ID, ARN, and region associated with each finding.
|
||||
|
||||
#### Constructor and Attribute Population
|
||||
|
||||
When you instantiate `CheckReportAlibabaCloud`, you must provide the check metadata and a resource object. The class will attempt to automatically populate its Alibaba Cloud-specific attributes from the resource, using the following logic:
|
||||
|
||||
- **`resource_id`**:
|
||||
- Uses `resource.id` if present.
|
||||
- Otherwise, uses `resource.name` if present.
|
||||
- Defaults to an empty string if not available.
|
||||
|
||||
- **`resource_arn`**:
|
||||
- Uses `resource.arn` if present.
|
||||
- Defaults to an empty string if not available.
|
||||
|
||||
- **`region`**:
|
||||
- Uses `resource.region` if present.
|
||||
- Defaults to an empty string if not available.
|
||||
|
||||
If the resource object does not contain the required attributes, you must set them manually in the check logic.
|
||||
|
||||
Other attributes are inherited from the `Check_Report` class, from which you **always** have to set the `status` and `status_extended` attributes in the check logic.
|
||||
|
||||
#### Example Usage
|
||||
|
||||
```python
|
||||
from prowler.lib.check.models import Check, CheckReportAlibabaCloud
|
||||
from prowler.providers.alibabacloud.services.myservice.myservice_client import myservice_client
|
||||
|
||||
|
||||
class myservice_example_check(Check):
|
||||
def execute(self) -> list[CheckReportAlibabaCloud]:
|
||||
findings = []
|
||||
|
||||
for resource in myservice_client.resources.values():
|
||||
report = CheckReportAlibabaCloud(
|
||||
metadata=self.metadata(),
|
||||
resource=resource
|
||||
)
|
||||
report.region = resource.region
|
||||
report.resource_id = resource.id
|
||||
report.resource_arn = f"acs:myservice::{myservice_client.audited_account}:resource/{resource.id}"
|
||||
|
||||
if resource.is_compliant:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Resource {resource.name} is compliant."
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Resource {resource.name} is not compliant."
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
```
|
||||
|
||||
## Authentication Methods
|
||||
|
||||
The Alibaba Cloud provider supports multiple authentication methods, prioritized in the following order:
|
||||
|
||||
1. **Credentials URI** - Retrieve credentials from an external URI endpoint
|
||||
2. **OIDC Role Authentication** - For applications running in ACK with RRSA enabled
|
||||
3. **ECS RAM Role** - For ECS instances with attached RAM roles
|
||||
4. **RAM Role Assumption** - Cross-account access with role assumption
|
||||
5. **STS Temporary Credentials** - Pre-obtained temporary credentials
|
||||
6. **Permanent Access Keys** - Static access key credentials
|
||||
7. **Default Credential Chain** - Automatic credential discovery
|
||||
|
||||
For detailed authentication configuration, see the [Authentication documentation](/user-guide/providers/alibabacloud/authentication).
|
||||
|
||||
## Regions
|
||||
|
||||
Alibaba Cloud has multiple regions across the globe. By default, Prowler audits all available regions. You can specify specific regions using the `--regions` CLI argument:
|
||||
|
||||
```bash
|
||||
prowler alibabacloud --regions cn-hangzhou cn-shanghai
|
||||
```
|
||||
|
||||
The list of supported regions is maintained in [`prowler/providers/alibabacloud/config.py`](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/alibabacloud/config.py).
|
||||
@@ -237,6 +237,7 @@ Below is a generic example of a check metadata file. **Do not include comments i
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "Other",
|
||||
"ResourceGroup": "security",
|
||||
"Description": "This check verifies that the service resource has the required **security setting** enabled to protect against potential vulnerabilities.\n\nIt ensures that the resource follows security best practices and maintains proper access controls. The check evaluates whether the security configuration is properly implemented and active.",
|
||||
"Risk": "Without proper security settings, the resource may be vulnerable to:\n\n- **Unauthorized access** - Malicious actors could gain entry\n- **Data breaches** - Sensitive information could be compromised\n- **Security threats** - Various attack vectors could be exploited\n\nThis could result in compliance violations and potential financial or reputational damage.",
|
||||
"RelatedUrl": "",
|
||||
@@ -315,6 +316,31 @@ The type of resource being audited. This field helps categorize and organize fin
|
||||
- **Oracle Cloud Infrastructure**: Use types from [Oracle Cloud Infrastructure documentation](https://docs.public.oneportal.content.oci.oraclecloud.com/en-us/iaas/Content/Search/Tasks/queryingresources_topic-Listing_Supported_Resource_Types.htm).
|
||||
- **M365 / GitHub / MongoDB Atlas**: Leave empty due to lack of standardized types.
|
||||
|
||||
#### ResourceGroup
|
||||
|
||||
A high-level classification that groups checks by the type of cloud resource they audit. This field enables filtering and organizing findings by resource category across all providers. The value must be one of the following predefined groups:
|
||||
|
||||
| Group | Description |
|
||||
|-------|-------------|
|
||||
| `compute` | Virtual machines, instances, auto-scaling groups, workspaces, streaming |
|
||||
| `container` | Container orchestration, Kubernetes, registries, pods |
|
||||
| `serverless` | Functions, step functions, event-driven compute |
|
||||
| `database` | Relational, NoSQL, caches, search engines, data warehouses, graph databases |
|
||||
| `storage` | Object storage, block storage, file systems, backups, archives |
|
||||
| `network` | VPCs, subnets, load balancers, DNS, VPN, firewalls, CDN |
|
||||
| `IAM` | IAM users, roles, policies, access keys, service accounts, directories |
|
||||
| `messaging` | Queues, topics, event buses, streaming, email services |
|
||||
| `security` | WAF, secrets, KMS, certificates, security tools, defenders, DDoS protection |
|
||||
| `monitoring` | Logs, metrics, alerts, audit trails, observability, config tracking |
|
||||
| `api_gateway` | API management, REST APIs, GraphQL endpoints |
|
||||
| `ai_ml` | Machine learning, AI services, notebooks, training, LLM |
|
||||
| `governance` | Accounts, organizations, projects, policies, settings, compliance tools |
|
||||
| `collaboration` | Productivity SaaS apps (Exchange, Teams, SharePoint) |
|
||||
| `devops` | CI/CD, infrastructure as code, automation, code repositories, version control |
|
||||
| `analytics` | Data warehouses, query engines, ETL pipelines, BI tools, data lakes |
|
||||
|
||||
The group is determined by the resource type being audited, not the service. For example, an EC2 security group check would use `network` (not `compute`), while an EC2 instance check would use `compute`.
|
||||
|
||||
#### Description
|
||||
|
||||
A concise, natural language explanation that **clearly describes what the finding means**, focusing on clarity and context rather than technical implementation details. Use simple paragraphs with line breaks if needed, but avoid sections, code blocks, or complex formatting. This field is limited to maximum 400 characters.
|
||||
|
||||
@@ -294,6 +294,7 @@
|
||||
"developer-guide/aws-details",
|
||||
"developer-guide/azure-details",
|
||||
"developer-guide/gcp-details",
|
||||
"developer-guide/alibabacloud-details",
|
||||
"developer-guide/kubernetes-details",
|
||||
"developer-guide/m365-details",
|
||||
"developer-guide/github-details",
|
||||
|
||||
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 534 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 555 KiB |
|
After Width: | Height: | Size: 555 KiB |
|
After Width: | Height: | Size: 812 KiB |
|
After Width: | Height: | Size: 860 KiB |
|
After Width: | Height: | Size: 526 KiB |
|
After Width: | Height: | Size: 574 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 717 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 42 KiB |
@@ -2,46 +2,87 @@
|
||||
title: 'Troubleshooting'
|
||||
---
|
||||
|
||||
- **Running `prowler` I get `[File: utils.py:15] [Module: utils] CRITICAL: path/redacted: OSError[13]`**:
|
||||
## Running `prowler` I get `[File: utils.py:15] [Module: utils] CRITICAL: path/redacted: OSError[13]`
|
||||
|
||||
That is an error related to file descriptors or opened files allowed by your operating system.
|
||||
That is an error related to file descriptors or opened files allowed by your operating system.
|
||||
|
||||
In macOS Ventura, the default value for the `file descriptors` is `256`. With the following command `ulimit -n 1000` you'll increase that value and solve the issue.
|
||||
In macOS Ventura, the default value for the `file descriptors` is `256`. With the following command `ulimit -n 1000` you'll increase that value and solve the issue.
|
||||
|
||||
If you have a different OS and you are experiencing the same, please increase the value of your `file descriptors`. You can check it running `ulimit -a | grep "file descriptors"`.
|
||||
|
||||
This error is also related with a lack of system requirements. To improve performance, Prowler stores information in memory so it may need to be run in a system with more than 1GB of memory.
|
||||
If you have a different OS and you are experiencing the same, please increase the value of your `file descriptors`. You can check it running `ulimit -a | grep "file descriptors"`.
|
||||
|
||||
This error is also related with a lack of system requirements. To improve performance, Prowler stores information in memory so it may need to be run in a system with more than 1GB of memory.
|
||||
|
||||
See section [Logging](/user-guide/cli/tutorials/logging) for further information or [contact us](/contact).
|
||||
|
||||
## Common Issues with Docker Compose Installation
|
||||
|
||||
- **Problem adding AWS Provider using "Connect assuming IAM Role" in Docker (see [GitHub Issue #7745](https://github.com/prowler-cloud/prowler/issues/7745))**:
|
||||
### Problem adding AWS Provider using "Connect assuming IAM Role" in Docker
|
||||
|
||||
When running Prowler App via Docker, you may encounter errors such as `Provider not set`, `AWS assume role error - Unable to locate credentials`, or `Provider has no secret` when trying to add an AWS Provider using the "Connect assuming IAM Role" option. This typically happens because the container does not have access to the necessary AWS credentials or profiles.
|
||||
See [GitHub Issue #7745](https://github.com/prowler-cloud/prowler/issues/7745) for more details.
|
||||
|
||||
**Workaround:**
|
||||
When running Prowler App via Docker, you may encounter errors such as `Provider not set`, `AWS assume role error - Unable to locate credentials`, or `Provider has no secret` when trying to add an AWS Provider using the "Connect assuming IAM Role" option. This typically happens because the container does not have access to the necessary AWS credentials or profiles.
|
||||
|
||||
- Ensure your AWS credentials and configuration are available to the Docker container. You can do this by mounting your local `.aws` directory into the container. For example, in your `docker-compose.yaml`, add the following volume to the relevant services:
|
||||
**Workaround:**
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- "${HOME}/.aws:/home/prowler/.aws:ro"
|
||||
```
|
||||
This should be added to the `api`, `worker`, and `worker-beat` services.
|
||||
- Ensure your AWS credentials and configuration are available to the Docker container. You can do this by mounting your local `.aws` directory into the container. For example, in your `docker-compose.yaml`, add the following volume to the relevant services:
|
||||
|
||||
- Create or update your `~/.aws/config` and `~/.aws/credentials` files with the appropriate profiles and roles. For example:
|
||||
```yaml
|
||||
volumes:
|
||||
- "${HOME}/.aws:/home/prowler/.aws:ro"
|
||||
```
|
||||
|
||||
```ini
|
||||
[profile prowler-profile]
|
||||
role_arn = arn:aws:iam::<account-id>:role/ProwlerScan
|
||||
source_profile = default
|
||||
```
|
||||
And set the environment variable in your `.env` file:
|
||||
This should be added to the `api`, `worker`, and `worker-beat` services.
|
||||
|
||||
```env
|
||||
AWS_PROFILE=prowler-profile
|
||||
```
|
||||
- Create or update your `~/.aws/config` and `~/.aws/credentials` files with the appropriate profiles and roles. For example:
|
||||
|
||||
- If you are scanning multiple AWS accounts, you may need to add multiple profiles to your AWS config. Note that this workaround is mainly for local testing; for production or multi-account setups, follow the [CloudFormation Template guide](https://github.com/prowler-cloud/prowler/issues/7745) and ensure the correct IAM roles and permissions are set up in each account.
|
||||
```ini
|
||||
[profile prowler-profile]
|
||||
role_arn = arn:aws:iam::<account-id>:role/ProwlerScan
|
||||
source_profile = default
|
||||
```
|
||||
|
||||
And set the environment variable in your `.env` file:
|
||||
|
||||
```env
|
||||
AWS_PROFILE=prowler-profile
|
||||
```
|
||||
|
||||
- If you are scanning multiple AWS accounts, you may need to add multiple profiles to your AWS config. Note that this workaround is mainly for local testing; for production or multi-account setups, follow the [CloudFormation Template guide](https://github.com/prowler-cloud/prowler/issues/7745) and ensure the correct IAM roles and permissions are set up in each account.
|
||||
|
||||
### Scans complete but reports are missing or compliance data is empty (`Too many open files` error)
|
||||
|
||||
When running Prowler App via Docker Compose, you may encounter situations where scans complete successfully but reports are not available for download, compliance data shows as empty, or you see 404 errors when trying to access scan reports. Checking the `worker` container logs may reveal errors like `[Errno 24] Too many open files`.
|
||||
|
||||
This issue occurs because the default file descriptor limits in Docker containers are too low for Prowler's operations.
|
||||
|
||||
**Solution:**
|
||||
|
||||
Add `ulimits` configuration to the `worker` and `worker-beat` services in your `docker-compose.yaml`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
worker:
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 65536
|
||||
hard: 65536
|
||||
# ... rest of service configuration
|
||||
|
||||
worker-beat:
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 65536
|
||||
hard: 65536
|
||||
# ... rest of service configuration
|
||||
```
|
||||
|
||||
After making these changes, restart your Docker Compose stack:
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
<Note>
|
||||
We are evaluating adding these values to the default `docker-compose.yml` to avoid this issue in future releases.
|
||||
</Note>
|
||||
|
||||
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 42 KiB |
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'Dashboard'
|
||||
title: "Dashboard"
|
||||
---
|
||||
|
||||
Prowler allows you to run your own local dashboards using the csv outputs provided by Prowler
|
||||
@@ -34,26 +34,30 @@ The overview page provides a full impression of your findings obtained from Prow
|
||||
|
||||
This page allows for multiple functions:
|
||||
|
||||
* Apply filters:
|
||||
- Apply filters:
|
||||
|
||||
* Assesment Date
|
||||
* Account
|
||||
* Region
|
||||
* Severity
|
||||
* Service
|
||||
* Status
|
||||
- Assesment Date
|
||||
- Account
|
||||
- Region
|
||||
- Severity
|
||||
- Service
|
||||
- Provider
|
||||
- Status
|
||||
- Category
|
||||
|
||||
* See which files has been scanned to generate the dashboard by placing your mouse on the `?` icon:
|
||||
- See which files has been scanned to generate the dashboard by placing your mouse on the `?` icon:
|
||||
|
||||
<img src="/images/cli/dashboard/dashboard-files-scanned.png" />
|
||||
{" "}
|
||||
<img src="/images/cli/dashboard/dashboard-files-scanned.png" />
|
||||
|
||||
* Download the `Top Findings by Severity` table using the button `DOWNLOAD THIS TABLE AS CSV` or `DOWNLOAD THIS TABLE AS XLSX`
|
||||
- Download the `Top Findings by Severity` table using the button `DOWNLOAD THIS TABLE AS CSV` or `DOWNLOAD THIS TABLE AS XLSX`
|
||||
|
||||
* Click the provider cards to filter by provider.
|
||||
- Click the provider cards to filter by provider.
|
||||
|
||||
* On the dropdowns under `Top Findings by Severity` you can apply multiple sorts to see the information, also you will get a detailed view of each finding using the dropdowns:
|
||||
- On the dropdowns under `Top Findings by Severity` you can apply multiple sorts to see the information, also you will get a detailed view of each finding using the dropdowns:
|
||||
|
||||
<img src="/images/cli/dashboard/dropdown.png" />
|
||||
{" "}
|
||||
<img src="/images/cli/dashboard/dropdown.png" />
|
||||
|
||||
## Compliance Page
|
||||
|
||||
@@ -110,17 +114,16 @@ To change the path, modify the values `folder_path_overview` or `folder_path_com
|
||||
<Note>
|
||||
If you have any issue related with dashboards, check that the output path where the dashboard is getting the outputs is correct.
|
||||
|
||||
|
||||
</Note>
|
||||
## Output Support
|
||||
|
||||
Prowler dashboard supports the detailed outputs:
|
||||
|
||||
| Provider| V3| V4| COMPLIANCE-V3| COMPLIANCE-V4
|
||||
|----------|----------|----------|----------|----------
|
||||
| AWS| ✅| ✅| ✅| ✅
|
||||
| Azure| ❌| ✅| ❌| ✅
|
||||
| Kubernetes| ❌| ✅| ❌| ✅
|
||||
| GCP| ❌| ✅| ❌| ✅
|
||||
| M365| ❌| ✅| ❌| ✅
|
||||
| GitHub| ❌| ✅| ❌| ✅
|
||||
| Provider | V3 | V4 | COMPLIANCE-V3 | COMPLIANCE-V4 |
|
||||
| ---------- | --- | --- | ------------- | ------------- |
|
||||
| AWS | ✅ | ✅ | ✅ | ✅ |
|
||||
| Azure | ❌ | ✅ | ❌ | ✅ |
|
||||
| Kubernetes | ❌ | ✅ | ❌ | ✅ |
|
||||
| GCP | ❌ | ✅ | ❌ | ✅ |
|
||||
| M365 | ❌ | ✅ | ❌ | ✅ |
|
||||
| GitHub | ❌ | ✅ | ❌ | ✅ |
|
||||
|
||||
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 42 KiB |
@@ -2,26 +2,111 @@
|
||||
title: 'Getting Started With Alibaba Cloud on Prowler'
|
||||
---
|
||||
|
||||
## Prowler CLI
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
||||
### Configure Alibaba Cloud Credentials
|
||||
Prowler supports Alibaba Cloud both from the CLI and from Prowler Cloud. This guide walks you through the requirements, how to connect the provider in the UI, and how to run scans from the command line.
|
||||
|
||||
Prowler requires Alibaba Cloud credentials to perform security checks. Authentication is available through the following methods (in order of priority):
|
||||
## Prerequisites
|
||||
|
||||
1. **Credentials URI** (Recommended for centralized credential services)
|
||||
2. **OIDC Role Authentication** (Recommended for ACK/Kubernetes)
|
||||
3. **ECS RAM Role** (Recommended for ECS instances)
|
||||
4. **RAM Role Assumption** (Recommended for cross-account access)
|
||||
5. **STS Temporary Credentials**
|
||||
6. **Permanent Access Keys**
|
||||
7. **Default Credential Chain**
|
||||
Before you begin, make sure you have:
|
||||
|
||||
1. An **Alibaba Cloud Account ID** (visible in the Alibaba Cloud Console under your profile).
|
||||
2. **Credentials** with appropriate permissions:
|
||||
- **RAM User with Access Keys**: For static credential authentication.
|
||||
- **RAM Role**: For cross-account access using role assumption (recommended).
|
||||
3. The required permissions for Prowler to audit your resources. See the [Alibaba Cloud Authentication](/user-guide/providers/alibabacloud/authentication) guide for the full list of required permissions.
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Prowler Cloud" icon="cloud" href="#prowler-cloud">
|
||||
Onboard Alibaba Cloud using Prowler Cloud
|
||||
</Card>
|
||||
<Card title="Prowler CLI" icon="terminal" href="#prowler-cli">
|
||||
Onboard Alibaba Cloud using Prowler CLI
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Prowler Cloud
|
||||
|
||||
<VersionBadge version="5.18.0" />
|
||||
|
||||
### Step 1: Get Your Alibaba Cloud Account ID
|
||||
|
||||
1. Log in to the [Alibaba Cloud Console](https://home.console.alibabacloud.com/)
|
||||
2. Click on your profile avatar in the top-right corner
|
||||
3. Locate and copy your Account ID
|
||||
|
||||

|
||||
|
||||
### Step 2: Access Prowler Cloud or Prowler App
|
||||
|
||||
1. Navigate to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler App](/user-guide/tutorials/prowler-app)
|
||||
2. Go to "Configuration" > "Cloud Providers"
|
||||
|
||||

|
||||
|
||||
3. Click "Add Cloud Provider"
|
||||
|
||||

|
||||
|
||||
4. Select "Alibaba Cloud"
|
||||
|
||||

|
||||
|
||||
5. Enter your Alibaba Cloud Account ID and optionally provide a friendly alias
|
||||
|
||||

|
||||
|
||||
### Step 3: Choose and Provide Authentication
|
||||
|
||||
After the Account ID is in place, select the authentication method that matches your Alibaba Cloud setup:
|
||||
|
||||

|
||||
|
||||
#### RAM Role Assumption (Recommended)
|
||||
|
||||
Use this method for secure cross-account access. For detailed instructions on how to create the RAM role, see the [Authentication guide](/user-guide/providers/alibabacloud/authentication#ram-role-assumption-recommended-for-cross-account).
|
||||
|
||||
1. Enter the **Role ARN** (format: `acs:ram::<account-id>:role/<role-name>`)
|
||||
2. Enter the **Access Key ID** and **Access Key Secret** of the RAM user that will assume the role
|
||||
|
||||

|
||||
|
||||
<Info>
|
||||
The RAM user whose credentials you provide must have permission to assume the target role. For more details, see the [Alibaba Cloud AssumeRole API documentation](https://www.alibabacloud.com/help/en/ram/developer-reference/api-sts-2015-04-01-assumerole).
|
||||
</Info>
|
||||
|
||||
#### Credentials (Static Access Keys)
|
||||
|
||||
Use static credentials for quick scans (not recommended for production). For detailed setup, see the [Authentication guide](/user-guide/providers/alibabacloud/authentication#permanent-access-keys).
|
||||
|
||||
1. Enter the **Access Key ID** and **Access Key Secret**
|
||||
|
||||

|
||||
|
||||
<Warning>
|
||||
Prowler does not accept credentials through command-line arguments. Provide credentials through environment variables or the Alibaba Cloud credential chain.
|
||||
|
||||
Static access keys are long-lived credentials. For production environments, consider using RAM Role Assumption instead.
|
||||
</Warning>
|
||||
|
||||
#### Option 1: Environment Variables (Permanent Credentials)
|
||||
### Step 4: Launch the Scan
|
||||
|
||||
1. Click "Next" to review your configuration
|
||||
2. Click "Launch Scan" to start auditing your Alibaba Cloud account
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Prowler CLI
|
||||
|
||||
<VersionBadge version="5.15.0" />
|
||||
|
||||
You can also run Alibaba Cloud assessments directly from the CLI. Both command-line flags and environment variables are supported.
|
||||
|
||||
### Step 1: Select an Authentication Method
|
||||
|
||||
Choose one of the following authentication methods. For the complete list and detailed configuration, see the [Authentication guide](/user-guide/providers/alibabacloud/authentication).
|
||||
|
||||
#### Environment Variables
|
||||
|
||||
```bash
|
||||
export ALIBABA_CLOUD_ACCESS_KEY_ID="your-access-key-id"
|
||||
@@ -29,104 +114,49 @@ export ALIBABA_CLOUD_ACCESS_KEY_SECRET="your-access-key-secret"
|
||||
prowler alibabacloud
|
||||
```
|
||||
|
||||
#### Option 2: Environment Variables (STS Temporary Credentials)
|
||||
|
||||
```bash
|
||||
export ALIBABA_CLOUD_ACCESS_KEY_ID="your-sts-access-key-id"
|
||||
export ALIBABA_CLOUD_ACCESS_KEY_SECRET="your-sts-access-key-secret"
|
||||
export ALIBABA_CLOUD_SECURITY_TOKEN="your-sts-security-token"
|
||||
prowler alibabacloud
|
||||
```
|
||||
|
||||
#### Option 3: RAM Role Assumption (Environment Variables)
|
||||
#### RAM Role Assumption
|
||||
|
||||
```bash
|
||||
export ALIBABA_CLOUD_ACCESS_KEY_ID="your-access-key-id"
|
||||
export ALIBABA_CLOUD_ACCESS_KEY_SECRET="your-access-key-secret"
|
||||
export ALIBABA_CLOUD_ROLE_ARN="acs:ram::123456789012:role/ProwlerAuditRole"
|
||||
export ALIBABA_CLOUD_ROLE_SESSION_NAME="ProwlerAssessmentSession" # Optional
|
||||
prowler alibabacloud
|
||||
```
|
||||
|
||||
#### Option 4: RAM Role Assumption (CLI + Environment Variables)
|
||||
#### ECS RAM Role (for ECS instances)
|
||||
|
||||
```bash
|
||||
# Set credentials via environment variables
|
||||
export ALIBABA_CLOUD_ACCESS_KEY_ID="your-access-key-id"
|
||||
export ALIBABA_CLOUD_ACCESS_KEY_SECRET="your-access-key-secret"
|
||||
# Specify role via CLI argument
|
||||
prowler alibabacloud --role-arn acs:ram::123456789012:role/ProwlerAuditRole --role-session-name ProwlerAssessmentSession
|
||||
```
|
||||
|
||||
#### Option 5: ECS Instance Metadata (ECS RAM Role)
|
||||
|
||||
```bash
|
||||
# When running on an ECS instance with an attached RAM role
|
||||
prowler alibabacloud --ecs-ram-role RoleName
|
||||
|
||||
# Or using environment variable
|
||||
export ALIBABA_CLOUD_ECS_METADATA="RoleName"
|
||||
prowler alibabacloud
|
||||
```
|
||||
|
||||
#### Option 6: OIDC Role Authentication (for ACK/Kubernetes)
|
||||
### Step 2: Run the First Scan
|
||||
|
||||
```bash
|
||||
# For applications running in ACK (Alibaba Container Service for Kubernetes) with RRSA enabled
|
||||
export ALIBABA_CLOUD_ROLE_ARN="acs:ram::123456789012:role/YourRole"
|
||||
export ALIBABA_CLOUD_OIDC_PROVIDER_ARN="acs:ram::123456789012:oidc-provider/ack-rrsa-provider"
|
||||
export ALIBABA_CLOUD_OIDC_TOKEN_FILE="/var/run/secrets/tokens/oidc-token"
|
||||
export ALIBABA_CLOUD_ROLE_SESSION_NAME="ProwlerOIDCSession" # Optional
|
||||
prowler alibabacloud
|
||||
|
||||
# Or using CLI argument
|
||||
prowler alibabacloud --oidc-role-arn acs:ram::123456789012:role/YourRole
|
||||
```
|
||||
|
||||
#### Option 7: Credentials URI (External Credential Service)
|
||||
|
||||
```bash
|
||||
# Retrieve credentials from an external URI endpoint
|
||||
export ALIBABA_CLOUD_CREDENTIALS_URI="http://localhost:8080/credentials"
|
||||
prowler alibabacloud
|
||||
|
||||
# Or using CLI argument
|
||||
prowler alibabacloud --credentials-uri http://localhost:8080/credentials
|
||||
```
|
||||
|
||||
#### Option 8: Default Credential Chain
|
||||
|
||||
The SDK automatically checks credentials in the following order:
|
||||
1. Environment variables (`ALIBABA_CLOUD_*` or `ALIYUN_*`)
|
||||
2. OIDC authentication (if OIDC environment variables are set)
|
||||
3. Configuration file (`~/.aliyun/config.json`)
|
||||
4. ECS instance metadata (if running on ECS)
|
||||
5. Credentials URI (if `ALIBABA_CLOUD_CREDENTIALS_URI` is set)
|
||||
#### Scan all regions
|
||||
|
||||
```bash
|
||||
prowler alibabacloud
|
||||
```
|
||||
|
||||
### Specify Regions
|
||||
|
||||
To run checks only in specific regions:
|
||||
#### Scan specific regions
|
||||
|
||||
```bash
|
||||
prowler alibabacloud --regions cn-hangzhou cn-shanghai
|
||||
```
|
||||
|
||||
### Run Specific Checks
|
||||
|
||||
To run specific checks:
|
||||
#### Run specific checks
|
||||
|
||||
```bash
|
||||
prowler alibabacloud --checks ram_no_root_access_key ram_user_mfa_enabled_console_access
|
||||
```
|
||||
|
||||
### Run Compliance Framework
|
||||
|
||||
To run a specific compliance framework:
|
||||
#### Run a compliance framework
|
||||
|
||||
```bash
|
||||
prowler alibabacloud --compliance cis_2.0_alibabacloud
|
||||
```
|
||||
|
||||
### Additional Tips
|
||||
|
||||
- Combine flags (for example, `--checks` or `--services`) just like with other providers.
|
||||
- Use `--output-modes` to export findings in JSON, CSV, ASFF, etc.
|
||||
- For more authentication options (OIDC, Credentials URI, STS), see the [Authentication guide](/user-guide/providers/alibabacloud/authentication).
|
||||
|
||||
@@ -69,7 +69,19 @@ If your IAM Role is configured with Multi-Factor Authentication (MFA), use `--mf
|
||||
|
||||
## Creating a Role for One or Multiple Accounts
|
||||
|
||||
To create an IAM role that can be assumed in one or multiple AWS accounts, use either a CloudFormation Stack or StackSet and adapt the provided [template](https://github.com/prowler-cloud/prowler/blob/master/permissions/create_role_to_assume_cfn.yaml).
|
||||
To create an IAM role that can be assumed in one or multiple AWS accounts, use either a CloudFormation Stack or StackSet with the provided [template](https://github.com/prowler-cloud/prowler/blob/master/permissions/templates/cloudformation/prowler-scan-role.yml).
|
||||
|
||||
The template requires the following parameters:
|
||||
|
||||
- **ExternalId:** A unique identifier to prevent the [confused deputy problem](https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html)
|
||||
- **AccountId:** *(Optional)* AWS Account ID that will assume the role (default: Prowler Cloud account)
|
||||
- **IAMPrincipal:** *(Optional)* The IAM principal allowed to assume the role (default: `role/prowler*`)
|
||||
|
||||
When running Prowler CLI, include the External ID using the `-I/--external-id` flag:
|
||||
|
||||
```sh
|
||||
prowler aws -R arn:aws:iam::<account_id>:role/ProwlerScan -I <external_id>
|
||||
```
|
||||
|
||||
<Note>
|
||||
**Session Duration Considerations**: Depending on the number of checks performed and the size of your infrastructure, Prowler may require more than 1 hour to complete. Use the `-T <seconds>` option to allow up to 12 hours (43,200 seconds). If you need more than 1 hour, modify the _“Maximum CLI/API session duration”_ setting for the role. Learn more [here](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html#id_roles_use_view-role-max-session).
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
AWSTemplateFormatVersion: '2010-09-09'
|
||||
#
|
||||
# You can invoke CloudFormation and pass the principal ARN from a command line like this:
|
||||
# aws cloudformation create-stack \
|
||||
# --capabilities CAPABILITY_IAM --capabilities CAPABILITY_NAMED_IAM \
|
||||
# --template-body "file://create_role_to_assume_cfn.yaml" \
|
||||
# --stack-name "ProwlerScanRole" \
|
||||
# --parameters "ParameterKey=AuthorisedARN,ParameterValue=arn:aws:iam::123456789012:root"
|
||||
#
|
||||
Description: |
|
||||
This template creates an AWS IAM Role with an inline policy and two AWS managed policies
|
||||
attached. It sets the trust policy on that IAM Role to permit a named ARN in another AWS
|
||||
account to assume that role. The role name and the ARN of the trusted user can all be passed
|
||||
to the CloudFormation stack as parameters. Then you can run Prowler to perform a security
|
||||
assessment with a command like:
|
||||
prowler --role ProwlerScanRole.ARN
|
||||
Parameters:
|
||||
AuthorisedARN:
|
||||
Description: |
|
||||
ARN of user who is authorised to assume the role that is created by this template.
|
||||
E.g., arn:aws:iam::123456789012:root
|
||||
Type: String
|
||||
ProwlerRoleName:
|
||||
Description: |
|
||||
Name of the IAM role that will have these policies attached. Default: ProwlerScanRole
|
||||
Type: String
|
||||
Default: 'ProwlerScanRole'
|
||||
|
||||
Resources:
|
||||
ProwlerScanRole:
|
||||
Type: AWS::IAM::Role
|
||||
Properties:
|
||||
AssumeRolePolicyDocument:
|
||||
Version: '2012-10-17'
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Principal:
|
||||
AWS: !Sub ${AuthorisedARN}
|
||||
Action: 'sts:AssumeRole'
|
||||
## In case MFA is required uncomment lines below and read https://github.com/prowler-cloud/prowler#run-prowler-with-mfa-protected-credentials
|
||||
# Condition:
|
||||
# Bool:
|
||||
# 'aws:MultiFactorAuthPresent': true
|
||||
# This is 12h that is maximum allowed, Minimum is 3600 = 1h
|
||||
# to take advantage of this use -T like in './prowler --role ProwlerScanRole.ARN -T 43200'
|
||||
MaxSessionDuration: 43200
|
||||
ManagedPolicyArns:
|
||||
- 'arn:aws:iam::aws:policy/SecurityAudit'
|
||||
- 'arn:aws:iam::aws:policy/job-function/ViewOnlyAccess'
|
||||
RoleName: !Sub ${ProwlerRoleName}
|
||||
Policies:
|
||||
- PolicyName: ProwlerScanRoleAdditionalViewPrivileges
|
||||
PolicyDocument:
|
||||
Version : '2012-10-17'
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- 'account:Get*'
|
||||
- 'appstream:Describe*'
|
||||
- 'appstream:List*'
|
||||
- 'backup:List*'
|
||||
- 'bedrock:List*'
|
||||
- 'bedrock:Get*'
|
||||
- 'cloudtrail:GetInsightSelectors'
|
||||
- 'codeartifact:List*'
|
||||
- 'codebuild:BatchGet*'
|
||||
- 'codebuild:ListReportGroups'
|
||||
- 'cognito-idp:GetUserPoolMfaConfig'
|
||||
- 'dlm:Get*'
|
||||
- 'drs:Describe*'
|
||||
- 'ds:Get*'
|
||||
- 'ds:Describe*'
|
||||
- 'ds:List*'
|
||||
- 'dynamodb:GetResourcePolicy'
|
||||
- 'ec2:GetEbsEncryptionByDefault'
|
||||
- 'ec2:GetSnapshotBlockPublicAccessState'
|
||||
- 'ec2:GetInstanceMetadataDefaults'
|
||||
- 'ecr:Describe*'
|
||||
- 'ecr:GetRegistryScanningConfiguration'
|
||||
- 'elasticfilesystem:DescribeBackupPolicy'
|
||||
- 'glue:GetConnections'
|
||||
- 'glue:GetSecurityConfiguration*'
|
||||
- 'glue:SearchTables'
|
||||
- 'lambda:GetFunction*'
|
||||
- 'logs:FilterLogEvents'
|
||||
- 'lightsail:GetRelationalDatabases'
|
||||
- 'macie2:GetMacieSession'
|
||||
- 'macie2:GetAutomatedDiscoveryConfiguration'
|
||||
- 's3:GetAccountPublicAccessBlock'
|
||||
- 'shield:DescribeProtection'
|
||||
- 'shield:GetSubscriptionState'
|
||||
- 'securityhub:BatchImportFindings'
|
||||
- 'securityhub:GetFindings'
|
||||
- 'servicecatalog:Describe*'
|
||||
- 'servicecatalog:List*'
|
||||
- 'ssm:GetDocument'
|
||||
- 'ssm-incidents:List*'
|
||||
- 'states:ListTagsForResource'
|
||||
- 'support:Describe*'
|
||||
- 'tag:GetTagKeys'
|
||||
- 'wellarchitected:List*'
|
||||
Resource: '*'
|
||||
- PolicyName: ProwlerScanRoleAdditionalViewPrivilegesApiGateway
|
||||
PolicyDocument:
|
||||
Version : '2012-10-17'
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- 'apigateway:GET'
|
||||
Resource: 'arn:aws:apigateway:*::/restapis/*'
|
||||
@@ -7,9 +7,14 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
### Added
|
||||
- Add Prowler ThreatScore for the Alibaba Cloud provider [(#9511)](https://github.com/prowler-cloud/prowler/pull/9511)
|
||||
- `compute_instance_group_multiple_zones` check for GCP provider [(#9566)](https://github.com/prowler-cloud/prowler/pull/9566)
|
||||
- `compute_instance_group_autohealing_enabled` check for GCP provider [(#9690)](https://github.com/prowler-cloud/prowler/pull/9690)
|
||||
- Support AWS European Sovereign Cloud [(#9649)](https://github.com/prowler-cloud/prowler/pull/9649)
|
||||
- `compute_instance_disk_auto_delete_disabled` check for GCP provider [(#9604)](https://github.com/prowler-cloud/prowler/pull/9604)
|
||||
- Bedrock service pagination [(#9606)](https://github.com/prowler-cloud/prowler/pull/9606)
|
||||
- `ResourceGroup` field to all check metadata for resource classification [(#9656)](https://github.com/prowler-cloud/prowler/pull/9656)
|
||||
- `compute_instance_group_load_balancer_attached` check for GCP provider [(#9695)](https://github.com/prowler-cloud/prowler/pull/9695)
|
||||
- `compute_instance_single_network_interface` check for GCP provider [(#9702)](https://github.com/prowler-cloud/prowler/pull/9702)
|
||||
- `compute_image_not_publicly_shared` check for GCP provider [(#9718)](https://github.com/prowler-cloud/prowler/pull/9718)
|
||||
|
||||
### Changed
|
||||
- Update AWS Step Functions service metadata to new format [(#9432)](https://github.com/prowler-cloud/prowler/pull/9432)
|
||||
@@ -18,6 +23,20 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
- Update AWS Shield service metadata to new format [(#9427)](https://github.com/prowler-cloud/prowler/pull/9427)
|
||||
- Update AWS Secrets Manager service metadata to new format [(#9408)](https://github.com/prowler-cloud/prowler/pull/9408)
|
||||
- Improve SageMaker service tag retrieval with parallel execution [(#9609)](https://github.com/prowler-cloud/prowler/pull/9609)
|
||||
- Update AWS Redshift service metadata to new format [(#9385)](https://github.com/prowler-cloud/prowler/pull/9385)
|
||||
- Update AWS Storage Gateway service metadata to new format [(#9433)](https://github.com/prowler-cloud/prowler/pull/9433)
|
||||
- Update AWS Well-Architected service metadata to new format [(#9482)](https://github.com/prowler-cloud/prowler/pull/9482)
|
||||
- Update AWS SSM service metadata to new format [(#9430)](https://github.com/prowler-cloud/prowler/pull/9430)
|
||||
- Update AWS Organizations service metadata to new format [(#9384)](https://github.com/prowler-cloud/prowler/pull/9384)
|
||||
- Update AWS Resource Explorer v2 service metadata to new format [(#9386)](https://github.com/prowler-cloud/prowler/pull/9386)
|
||||
- Update AWS SageMaker service metadata to new format [(#9407)](https://github.com/prowler-cloud/prowler/pull/9407)
|
||||
- Update AWS Security Hub service metadata to new format [(#9409)](https://github.com/prowler-cloud/prowler/pull/9409)
|
||||
- Update AWS SES service metadata to new format [(#9411)](https://github.com/prowler-cloud/prowler/pull/9411)
|
||||
- Update AWS SSM Incidents service metadata to new format [(#9431)](https://github.com/prowler-cloud/prowler/pull/9431)
|
||||
- Update AWS WorkSpaces service metadata to new format [(#9483)](https://github.com/prowler-cloud/prowler/pull/9483)
|
||||
- Update AWS OpenSearch service metadata to new format [(#9383)](https://github.com/prowler-cloud/prowler/pull/9383)
|
||||
- Update AWS VPC service metadata to new format [(#9479)](https://github.com/prowler-cloud/prowler/pull/9479)
|
||||
- Update AWS Transfer service metadata to new format [(#9434)](https://github.com/prowler-cloud/prowler/pull/9434)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -112,6 +112,7 @@ class CheckMetadata(BaseModel):
|
||||
ResourceIdTemplate: str
|
||||
Severity: Severity
|
||||
ResourceType: str
|
||||
ResourceGroup: str = Field(default="")
|
||||
Description: str
|
||||
Risk: str
|
||||
RelatedUrl: str
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
"""GitHub Integration Package."""
|
||||
|
||||
from prowler.lib.outputs.github.github import GitHub, GitHubConnection
|
||||
|
||||
__all__ = ["GitHub", "GitHubConnection"]
|
||||
@@ -0,0 +1,35 @@
|
||||
"""GitHub Integration Exceptions Package."""
|
||||
|
||||
from prowler.lib.outputs.github.exceptions.exceptions import (
|
||||
GitHubAuthenticationError,
|
||||
GitHubBaseException,
|
||||
GitHubCreateIssueError,
|
||||
GitHubCreateIssueResponseError,
|
||||
GitHubGetLabelsError,
|
||||
GitHubGetLabelsResponseError,
|
||||
GitHubGetRepositoriesError,
|
||||
GitHubGetRepositoriesResponseError,
|
||||
GitHubInvalidParameterError,
|
||||
GitHubInvalidRepositoryError,
|
||||
GitHubNoRepositoriesError,
|
||||
GitHubSendFindingsResponseError,
|
||||
GitHubTestConnectionError,
|
||||
GitHubTokenError,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"GitHubAuthenticationError",
|
||||
"GitHubBaseException",
|
||||
"GitHubCreateIssueError",
|
||||
"GitHubCreateIssueResponseError",
|
||||
"GitHubGetLabelsError",
|
||||
"GitHubGetLabelsResponseError",
|
||||
"GitHubGetRepositoriesError",
|
||||
"GitHubGetRepositoriesResponseError",
|
||||
"GitHubInvalidParameterError",
|
||||
"GitHubInvalidRepositoryError",
|
||||
"GitHubNoRepositoriesError",
|
||||
"GitHubSendFindingsResponseError",
|
||||
"GitHubTestConnectionError",
|
||||
"GitHubTokenError",
|
||||
]
|
||||
@@ -0,0 +1,57 @@
|
||||
"""GitHub Integration Exceptions."""
|
||||
|
||||
|
||||
class GitHubBaseException(Exception):
|
||||
"""Base exception for all GitHub integration errors."""
|
||||
|
||||
|
||||
class GitHubAuthenticationError(GitHubBaseException):
|
||||
"""Exception raised when GitHub authentication fails."""
|
||||
|
||||
|
||||
class GitHubTokenError(GitHubBaseException):
|
||||
"""Exception raised when GitHub token is invalid or missing."""
|
||||
|
||||
|
||||
class GitHubGetRepositoriesError(GitHubBaseException):
|
||||
"""Exception raised when fetching repositories fails."""
|
||||
|
||||
|
||||
class GitHubGetRepositoriesResponseError(GitHubBaseException):
|
||||
"""Exception raised when the response from GitHub repositories API is invalid."""
|
||||
|
||||
|
||||
class GitHubNoRepositoriesError(GitHubBaseException):
|
||||
"""Exception raised when no repositories are found."""
|
||||
|
||||
|
||||
class GitHubInvalidRepositoryError(GitHubBaseException):
|
||||
"""Exception raised when an invalid repository is specified."""
|
||||
|
||||
|
||||
class GitHubCreateIssueError(GitHubBaseException):
|
||||
"""Exception raised when creating a GitHub issue fails."""
|
||||
|
||||
|
||||
class GitHubCreateIssueResponseError(GitHubBaseException):
|
||||
"""Exception raised when the response from GitHub create issue API is invalid."""
|
||||
|
||||
|
||||
class GitHubTestConnectionError(GitHubBaseException):
|
||||
"""Exception raised when testing the connection to GitHub fails."""
|
||||
|
||||
|
||||
class GitHubInvalidParameterError(GitHubBaseException):
|
||||
"""Exception raised when an invalid parameter is provided."""
|
||||
|
||||
|
||||
class GitHubSendFindingsResponseError(GitHubBaseException):
|
||||
"""Exception raised when sending findings to GitHub fails."""
|
||||
|
||||
|
||||
class GitHubGetLabelsError(GitHubBaseException):
|
||||
"""Exception raised when fetching repository labels fails."""
|
||||
|
||||
|
||||
class GitHubGetLabelsResponseError(GitHubBaseException):
|
||||
"""Exception raised when the response from GitHub labels API is invalid."""
|
||||
@@ -0,0 +1,626 @@
|
||||
"""GitHub Integration Module."""
|
||||
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, List
|
||||
|
||||
import requests
|
||||
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.lib.outputs.github.exceptions.exceptions import (
|
||||
GitHubAuthenticationError,
|
||||
GitHubGetLabelsError,
|
||||
GitHubGetLabelsResponseError,
|
||||
GitHubGetRepositoriesError,
|
||||
GitHubGetRepositoriesResponseError,
|
||||
GitHubInvalidParameterError,
|
||||
GitHubInvalidRepositoryError,
|
||||
GitHubNoRepositoriesError,
|
||||
GitHubTestConnectionError,
|
||||
)
|
||||
from prowler.providers.common.models import Connection
|
||||
|
||||
|
||||
@dataclass
|
||||
class GitHubConnection(Connection):
|
||||
"""
|
||||
Represents a GitHub connection object.
|
||||
Attributes:
|
||||
repositories (dict): Dictionary of repositories in GitHub.
|
||||
"""
|
||||
|
||||
repositories: dict = None
|
||||
|
||||
|
||||
class GitHub:
|
||||
"""
|
||||
GitHub class to interact with the GitHub API
|
||||
|
||||
This integration supports creating GitHub Issues from Prowler findings.
|
||||
It uses Personal Access Token (PAT) authentication.
|
||||
|
||||
Attributes:
|
||||
- _token: The Personal Access Token
|
||||
- _owner: The repository owner (user or organization)
|
||||
- _api_url: The GitHub API base URL (defaults to https://api.github.com)
|
||||
|
||||
Methods:
|
||||
- __init__: Initialize the GitHub object
|
||||
- test_connection: Test the connection to GitHub and return a Connection object
|
||||
- get_repositories: Get the accessible repositories from GitHub
|
||||
- get_repository_labels: Get the available labels for a repository
|
||||
- send_finding: Send a finding to GitHub and create an issue
|
||||
|
||||
Raises:
|
||||
- GitHubAuthenticationError: Failed to authenticate
|
||||
- GitHubTokenError: Token is invalid or missing
|
||||
- GitHubNoRepositoriesError: No repositories found
|
||||
- GitHubGetRepositoriesError: Failed to get repositories
|
||||
- GitHubGetRepositoriesResponseError: Failed to get repositories, response code did not match 200
|
||||
- GitHubInvalidRepositoryError: The repository is invalid
|
||||
- GitHubCreateIssueError: Failed to create an issue
|
||||
- GitHubCreateIssueResponseError: Failed to create an issue, response code did not match 201
|
||||
- GitHubTestConnectionError: Failed to test the connection
|
||||
- GitHubInvalidParameterError: Invalid parameters provided
|
||||
- GitHubGetLabelsError: Failed to get labels
|
||||
- GitHubGetLabelsResponseError: Failed to get labels, response code did not match 200
|
||||
|
||||
Usage:
|
||||
github = GitHub(
|
||||
token="ghp_xxxxxxxxxxxx",
|
||||
owner="myorg"
|
||||
)
|
||||
github.send_finding(
|
||||
check_id="aws_ec2_instance_public_ip",
|
||||
severity="high",
|
||||
repository="myorg/myrepo",
|
||||
...
|
||||
)
|
||||
"""
|
||||
|
||||
_token: str = None
|
||||
_owner: str = None
|
||||
_api_url: str = "https://api.github.com"
|
||||
HEADER_TEMPLATE = {
|
||||
"Accept": "application/vnd.github+json",
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
token: str = None,
|
||||
owner: str = None,
|
||||
api_url: str = None,
|
||||
):
|
||||
"""
|
||||
Initialize the GitHub client.
|
||||
|
||||
Args:
|
||||
token: GitHub Personal Access Token
|
||||
owner: Repository owner (user or organization)
|
||||
api_url: GitHub API base URL (defaults to https://api.github.com for GitHub.com,
|
||||
use https://github.example.com/api/v3 for GitHub Enterprise)
|
||||
"""
|
||||
if not token:
|
||||
raise GitHubInvalidParameterError(
|
||||
message="GitHub token is required",
|
||||
file=os.path.basename(__file__),
|
||||
)
|
||||
|
||||
self._token = token
|
||||
self._owner = owner
|
||||
if api_url:
|
||||
self._api_url = api_url.rstrip("/")
|
||||
|
||||
# Test authentication
|
||||
try:
|
||||
self._authenticate()
|
||||
except Exception as e:
|
||||
raise GitHubAuthenticationError(
|
||||
message=f"Failed to authenticate with GitHub: {str(e)}",
|
||||
file=os.path.basename(__file__),
|
||||
)
|
||||
|
||||
def _get_headers(self) -> Dict:
|
||||
"""Get the headers for GitHub API requests."""
|
||||
headers = self.HEADER_TEMPLATE.copy()
|
||||
headers["Authorization"] = f"Bearer {self._token}"
|
||||
return headers
|
||||
|
||||
def _authenticate(self) -> bool:
|
||||
"""
|
||||
Authenticate with GitHub by testing the token.
|
||||
|
||||
Returns:
|
||||
True if authentication successful
|
||||
|
||||
Raises:
|
||||
GitHubAuthenticationError: If authentication fails
|
||||
"""
|
||||
try:
|
||||
response = requests.get(
|
||||
f"{self._api_url}/user",
|
||||
headers=self._get_headers(),
|
||||
timeout=10,
|
||||
)
|
||||
if response.status_code == 200:
|
||||
return True
|
||||
elif response.status_code == 401:
|
||||
raise GitHubAuthenticationError(
|
||||
message="Invalid or expired GitHub token",
|
||||
file=os.path.basename(__file__),
|
||||
)
|
||||
else:
|
||||
raise GitHubAuthenticationError(
|
||||
message=f"GitHub authentication failed with status {response.status_code}",
|
||||
file=os.path.basename(__file__),
|
||||
)
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise GitHubAuthenticationError(
|
||||
message=f"Failed to connect to GitHub: {str(e)}",
|
||||
file=os.path.basename(__file__),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def test_connection(
|
||||
token: str = None,
|
||||
owner: str = None,
|
||||
api_url: str = None,
|
||||
raise_on_exception: bool = True,
|
||||
) -> GitHubConnection:
|
||||
"""
|
||||
Test the connection to GitHub.
|
||||
|
||||
Args:
|
||||
token: GitHub Personal Access Token
|
||||
owner: Repository owner (optional)
|
||||
api_url: GitHub API base URL (optional)
|
||||
raise_on_exception: Whether to raise exceptions or return error in Connection object
|
||||
|
||||
Returns:
|
||||
GitHubConnection object with connection status and repositories
|
||||
"""
|
||||
try:
|
||||
github = GitHub(token=token, owner=owner, api_url=api_url)
|
||||
repositories = github.get_repositories()
|
||||
return GitHubConnection(
|
||||
is_connected=True,
|
||||
error=None,
|
||||
repositories=repositories,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"GitHub connection test failed: {str(e)}")
|
||||
if raise_on_exception:
|
||||
raise GitHubTestConnectionError(
|
||||
message=f"Failed to test GitHub connection: {str(e)}",
|
||||
file=os.path.basename(__file__),
|
||||
)
|
||||
return GitHubConnection(
|
||||
is_connected=False,
|
||||
error=str(e),
|
||||
repositories={},
|
||||
)
|
||||
|
||||
def get_repositories(self) -> Dict[str, str]:
|
||||
"""
|
||||
Get accessible repositories from GitHub.
|
||||
|
||||
Returns:
|
||||
Dictionary with repository full names as keys and names as values
|
||||
Example: {"owner/repo1": "repo1", "owner/repo2": "repo2"}
|
||||
|
||||
Raises:
|
||||
GitHubGetRepositoriesError: If getting repositories fails
|
||||
GitHubGetRepositoriesResponseError: If response is invalid
|
||||
GitHubNoRepositoriesError: If no repositories found
|
||||
"""
|
||||
try:
|
||||
repositories = {}
|
||||
page = 1
|
||||
per_page = 100
|
||||
|
||||
while True:
|
||||
# Get repositories for the authenticated user
|
||||
response = requests.get(
|
||||
f"{self._api_url}/user/repos",
|
||||
headers=self._get_headers(),
|
||||
params={
|
||||
"per_page": per_page,
|
||||
"page": page,
|
||||
"sort": "updated",
|
||||
"affiliation": "owner,collaborator,organization_member",
|
||||
},
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
raise GitHubGetRepositoriesResponseError(
|
||||
message=f"Failed to get repositories: {response.status_code} - {response.text}",
|
||||
file=os.path.basename(__file__),
|
||||
)
|
||||
|
||||
repos = response.json()
|
||||
if not repos:
|
||||
break
|
||||
|
||||
for repo in repos:
|
||||
full_name = repo.get("full_name")
|
||||
name = repo.get("name")
|
||||
if full_name and name:
|
||||
repositories[full_name] = name
|
||||
|
||||
# Check if there are more pages
|
||||
if len(repos) < per_page:
|
||||
break
|
||||
page += 1
|
||||
|
||||
if not repositories:
|
||||
raise GitHubNoRepositoriesError(
|
||||
message="No repositories found for the authenticated user",
|
||||
file=os.path.basename(__file__),
|
||||
)
|
||||
|
||||
return repositories
|
||||
|
||||
except GitHubNoRepositoriesError:
|
||||
raise
|
||||
except GitHubGetRepositoriesResponseError:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise GitHubGetRepositoriesError(
|
||||
message=f"Failed to get repositories: {str(e)}",
|
||||
file=os.path.basename(__file__),
|
||||
)
|
||||
|
||||
def get_repository_labels(self, repository: str) -> List[str]:
|
||||
"""
|
||||
Get available labels for a repository.
|
||||
|
||||
Args:
|
||||
repository: Repository full name (e.g., "owner/repo")
|
||||
|
||||
Returns:
|
||||
List of label names
|
||||
|
||||
Raises:
|
||||
GitHubGetLabelsError: If getting labels fails
|
||||
GitHubGetLabelsResponseError: If response is invalid
|
||||
"""
|
||||
try:
|
||||
response = requests.get(
|
||||
f"{self._api_url}/repos/{repository}/labels",
|
||||
headers=self._get_headers(),
|
||||
params={"per_page": 100},
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
raise GitHubGetLabelsResponseError(
|
||||
message=f"Failed to get labels: {response.status_code} - {response.text}",
|
||||
file=os.path.basename(__file__),
|
||||
)
|
||||
|
||||
labels = response.json()
|
||||
return [label.get("name") for label in labels if label.get("name")]
|
||||
|
||||
except GitHubGetLabelsResponseError:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise GitHubGetLabelsError(
|
||||
message=f"Failed to get repository labels: {str(e)}",
|
||||
file=os.path.basename(__file__),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _get_severity_label(severity: str) -> str:
|
||||
"""Get a severity label with color indicator."""
|
||||
severity_lower = severity.lower()
|
||||
emoji_map = {
|
||||
"critical": "🔴",
|
||||
"high": "🟠",
|
||||
"medium": "🟡",
|
||||
"low": "🟢",
|
||||
"informational": "🔵",
|
||||
}
|
||||
emoji = emoji_map.get(severity_lower, "⚪")
|
||||
return f"{emoji} {severity.upper()}"
|
||||
|
||||
@staticmethod
|
||||
def _get_status_label(status: str) -> str:
|
||||
"""Get a status label with indicator."""
|
||||
status_lower = status.lower()
|
||||
if "fail" in status_lower:
|
||||
return "❌ FAIL"
|
||||
elif "pass" in status_lower:
|
||||
return "✅ PASS"
|
||||
else:
|
||||
return f"ℹ️ {status.upper()}"
|
||||
|
||||
def _build_issue_body(
|
||||
self,
|
||||
check_id: str = "",
|
||||
check_title: str = "",
|
||||
severity: str = "",
|
||||
status: str = "",
|
||||
status_extended: str = "",
|
||||
provider: str = "",
|
||||
region: str = "",
|
||||
resource_uid: str = "",
|
||||
resource_name: str = "",
|
||||
risk: str = "",
|
||||
recommendation_text: str = "",
|
||||
recommendation_url: str = "",
|
||||
remediation_code_native_iac: str = "",
|
||||
remediation_code_terraform: str = "",
|
||||
remediation_code_cli: str = "",
|
||||
remediation_code_other: str = "",
|
||||
resource_tags: dict = None,
|
||||
compliance: dict = None,
|
||||
finding_url: str = "",
|
||||
tenant_info: str = "",
|
||||
) -> str:
|
||||
"""
|
||||
Build the markdown body for the GitHub issue.
|
||||
|
||||
GitHub natively supports markdown, so we can use standard markdown formatting.
|
||||
"""
|
||||
body_parts = []
|
||||
|
||||
# Header with severity and status
|
||||
body_parts.append("## Prowler Security Finding\n")
|
||||
|
||||
# Metadata table
|
||||
body_parts.append("### Finding Details\n")
|
||||
body_parts.append("| Field | Value |")
|
||||
body_parts.append("|-------|-------|")
|
||||
|
||||
if check_id:
|
||||
body_parts.append(f"| **Check ID** | `{check_id}` |")
|
||||
if check_title:
|
||||
body_parts.append(f"| **Check Title** | {check_title} |")
|
||||
if severity:
|
||||
body_parts.append(
|
||||
f"| **Severity** | {self._get_severity_label(severity)} |"
|
||||
)
|
||||
if status:
|
||||
body_parts.append(f"| **Status** | {self._get_status_label(status)} |")
|
||||
if status_extended:
|
||||
body_parts.append(f"| **Status Details** | {status_extended} |")
|
||||
if provider:
|
||||
body_parts.append(f"| **Provider** | {provider.upper()} |")
|
||||
if region:
|
||||
body_parts.append(f"| **Region** | {region} |")
|
||||
if resource_uid:
|
||||
body_parts.append(f"| **Resource UID** | `{resource_uid}` |")
|
||||
if resource_name:
|
||||
body_parts.append(f"| **Resource Name** | {resource_name} |")
|
||||
if tenant_info:
|
||||
body_parts.append(f"| **Tenant** | {tenant_info} |")
|
||||
|
||||
body_parts.append("")
|
||||
|
||||
# Risk description
|
||||
if risk:
|
||||
body_parts.append("### Risk\n")
|
||||
body_parts.append(risk)
|
||||
body_parts.append("")
|
||||
|
||||
# Recommendation
|
||||
if recommendation_text or recommendation_url:
|
||||
body_parts.append("### Recommendation\n")
|
||||
if recommendation_text:
|
||||
body_parts.append(recommendation_text)
|
||||
if recommendation_url:
|
||||
body_parts.append(f"\n[View Recommendation]({recommendation_url})")
|
||||
body_parts.append("")
|
||||
|
||||
# Remediation code
|
||||
if any(
|
||||
[
|
||||
remediation_code_native_iac,
|
||||
remediation_code_terraform,
|
||||
remediation_code_cli,
|
||||
remediation_code_other,
|
||||
]
|
||||
):
|
||||
body_parts.append("### Remediation\n")
|
||||
|
||||
if remediation_code_cli:
|
||||
body_parts.append("#### CLI")
|
||||
body_parts.append("```bash")
|
||||
body_parts.append(remediation_code_cli.strip())
|
||||
body_parts.append("```\n")
|
||||
|
||||
if remediation_code_terraform:
|
||||
body_parts.append("#### Terraform")
|
||||
body_parts.append("```hcl")
|
||||
body_parts.append(remediation_code_terraform.strip())
|
||||
body_parts.append("```\n")
|
||||
|
||||
if remediation_code_native_iac:
|
||||
body_parts.append("#### Native IaC")
|
||||
body_parts.append("```yaml")
|
||||
body_parts.append(remediation_code_native_iac.strip())
|
||||
body_parts.append("```\n")
|
||||
|
||||
if remediation_code_other:
|
||||
body_parts.append("#### Other")
|
||||
body_parts.append("```")
|
||||
body_parts.append(remediation_code_other.strip())
|
||||
body_parts.append("```\n")
|
||||
|
||||
# Resource tags
|
||||
if resource_tags:
|
||||
body_parts.append("### Resource Tags\n")
|
||||
for key, value in resource_tags.items():
|
||||
body_parts.append(f"- **{key}**: {value}")
|
||||
body_parts.append("")
|
||||
|
||||
# Compliance
|
||||
if compliance:
|
||||
body_parts.append("### Compliance Frameworks\n")
|
||||
for framework, requirements in compliance.items():
|
||||
if requirements:
|
||||
body_parts.append(f"- **{framework}**: {', '.join(requirements)}")
|
||||
body_parts.append("")
|
||||
|
||||
# Finding URL
|
||||
if finding_url:
|
||||
body_parts.append(f"[View Finding in Prowler]({finding_url})\n")
|
||||
|
||||
# Footer
|
||||
body_parts.append("---")
|
||||
body_parts.append("*This issue was automatically created by Prowler*")
|
||||
|
||||
return "\n".join(body_parts)
|
||||
|
||||
def send_finding(
|
||||
self,
|
||||
check_id: str = "",
|
||||
check_title: str = "",
|
||||
severity: str = "",
|
||||
status: str = "",
|
||||
status_extended: str = "",
|
||||
provider: str = "",
|
||||
region: str = "",
|
||||
resource_uid: str = "",
|
||||
resource_name: str = "",
|
||||
risk: str = "",
|
||||
recommendation_text: str = "",
|
||||
recommendation_url: str = "",
|
||||
remediation_code_native_iac: str = "",
|
||||
remediation_code_terraform: str = "",
|
||||
remediation_code_cli: str = "",
|
||||
remediation_code_other: str = "",
|
||||
resource_tags: dict = None,
|
||||
compliance: dict = None,
|
||||
repository: str = "",
|
||||
issue_labels: list = None,
|
||||
finding_url: str = "",
|
||||
tenant_info: str = "",
|
||||
) -> bool:
|
||||
"""
|
||||
Send a finding to GitHub as an issue.
|
||||
|
||||
Args:
|
||||
check_id: The check ID
|
||||
check_title: The check title
|
||||
severity: The severity level
|
||||
status: The status
|
||||
status_extended: Extended status information
|
||||
provider: The cloud provider
|
||||
region: The region
|
||||
resource_uid: The resource UID
|
||||
resource_name: The resource name
|
||||
risk: Risk description
|
||||
recommendation_text: Recommendation text
|
||||
recommendation_url: Recommendation URL
|
||||
remediation_code_native_iac: Native IaC remediation code
|
||||
remediation_code_terraform: Terraform remediation code
|
||||
remediation_code_cli: CLI remediation code
|
||||
remediation_code_other: Other remediation code
|
||||
resource_tags: Resource tags dictionary
|
||||
compliance: Compliance frameworks dictionary
|
||||
repository: Repository full name (e.g., "owner/repo")
|
||||
issue_labels: List of label names to apply
|
||||
finding_url: URL to the finding in Prowler
|
||||
tenant_info: Tenant information
|
||||
|
||||
Returns:
|
||||
True if the issue was created successfully, False otherwise
|
||||
|
||||
Raises:
|
||||
GitHubInvalidRepositoryError: If repository is invalid
|
||||
GitHubCreateIssueError: If issue creation fails
|
||||
"""
|
||||
try:
|
||||
if not repository:
|
||||
raise GitHubInvalidParameterError(
|
||||
message="Repository is required",
|
||||
file=os.path.basename(__file__),
|
||||
)
|
||||
|
||||
# Validate repository exists
|
||||
repositories = self.get_repositories()
|
||||
if repository not in repositories:
|
||||
raise GitHubInvalidRepositoryError(
|
||||
message=f"Repository '{repository}' not found or not accessible",
|
||||
file=os.path.basename(__file__),
|
||||
)
|
||||
|
||||
# Build issue title
|
||||
title_parts = ["[Prowler]"]
|
||||
if severity:
|
||||
title_parts.append(severity.upper())
|
||||
if check_id:
|
||||
title_parts.append(check_id)
|
||||
if resource_uid:
|
||||
title_parts.append(resource_uid)
|
||||
|
||||
title = " - ".join(title_parts[1:])
|
||||
title = f"{title_parts[0]} {title}"
|
||||
|
||||
# Build issue body
|
||||
body = self._build_issue_body(
|
||||
check_id=check_id,
|
||||
check_title=check_title,
|
||||
severity=severity,
|
||||
status=status,
|
||||
status_extended=status_extended,
|
||||
provider=provider,
|
||||
region=region,
|
||||
resource_uid=resource_uid,
|
||||
resource_name=resource_name,
|
||||
risk=risk,
|
||||
recommendation_text=recommendation_text,
|
||||
recommendation_url=recommendation_url,
|
||||
remediation_code_native_iac=remediation_code_native_iac,
|
||||
remediation_code_terraform=remediation_code_terraform,
|
||||
remediation_code_cli=remediation_code_cli,
|
||||
remediation_code_other=remediation_code_other,
|
||||
resource_tags=resource_tags or {},
|
||||
compliance=compliance or {},
|
||||
finding_url=finding_url,
|
||||
tenant_info=tenant_info,
|
||||
)
|
||||
|
||||
# Build payload
|
||||
payload = {
|
||||
"title": title,
|
||||
"body": body,
|
||||
}
|
||||
|
||||
if issue_labels:
|
||||
payload["labels"] = issue_labels
|
||||
|
||||
# Create issue
|
||||
response = requests.post(
|
||||
f"{self._api_url}/repos/{repository}/issues",
|
||||
headers=self._get_headers(),
|
||||
json=payload,
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
if response.status_code != 201:
|
||||
try:
|
||||
response_json = response.json()
|
||||
error_message = response_json.get("message", response.text)
|
||||
except (ValueError, requests.exceptions.JSONDecodeError):
|
||||
error_message = response.text
|
||||
|
||||
logger.error(
|
||||
f"Failed to create GitHub issue: {response.status_code} - {error_message}"
|
||||
)
|
||||
return False
|
||||
|
||||
response_json = response.json()
|
||||
issue_url = response_json.get("html_url", "")
|
||||
logger.info(f"GitHub issue created successfully: {issue_url}")
|
||||
return True
|
||||
|
||||
except GitHubInvalidRepositoryError as e:
|
||||
logger.error(f"Invalid repository: {str(e)}")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send finding to GitHub: {str(e)}")
|
||||
return False
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:actiontrail::account-id:trail",
|
||||
"Severity": "critical",
|
||||
"ResourceType": "AlibabaCloudActionTrail",
|
||||
"ResourceGroup": "monitoring",
|
||||
"Description": "**ActionTrail** is a web service that records API calls for your account and delivers log files to you.\n\nThe recorded information includes the identity of the API caller, the time of the API call, the source IP address of the API caller, the request parameters, and the response elements returned by the Alibaba Cloud service. ActionTrail provides a history of API calls for an account, including API calls made via the Management Console, SDKs, and command line tools.",
|
||||
"Risk": "The API call history produced by ActionTrail enables **security analysis**, **resource change tracking**, and **compliance auditing**.\n\nEnsuring that a **multi-region trail** exists will detect unexpected activities occurring in otherwise unused regions. Global Service Logging should be enabled by default to capture events generated on Alibaba Cloud global services, ensuring the recording of management operations performed on all resources in an Alibaba Cloud account.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"ResourceIdTemplate": "acs:oss::account-id:bucket-name",
|
||||
"Severity": "critical",
|
||||
"ResourceType": "AlibabaCloudOSSBucket",
|
||||
"ResourceGroup": "storage",
|
||||
"Description": "**ActionTrail** logs a record of every API call made in your Alibaba Cloud account. These log files are stored in an **OSS bucket**.\n\nIt is recommended that the **Access Control List (ACL)** of the OSS bucket, which ActionTrail logs to, prevents public access to the ActionTrail logs.",
|
||||
"Risk": "Allowing **public access** to ActionTrail log content may aid an adversary in identifying weaknesses in the affected account's use or configuration.\n\nExposed audit logs can reveal sensitive information about your infrastructure, API usage patterns, and security configurations.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AlibabaCloudKubernetesCluster",
|
||||
"ResourceGroup": "container",
|
||||
"Description": "The monitoring service in **Kubernetes Engine clusters** depends on the Alibaba Cloud **CloudMonitor** agent to access additional system resources and application services in virtual machine instances.\n\nThe monitor can access metrics about CPU utilization, disk traffic metrics, network traffic, and disk IO information, which help monitor signals and build operations in your Kubernetes Engine clusters.",
|
||||
"Risk": "Without **CloudMonitor** enabled, you lack visibility into system metrics and custom metrics. System metrics measure the cluster's infrastructure, such as CPU or memory usage.\n\nWith CloudMonitor, a monitor controller is created that periodically connects to each node and collects metrics about its Pods and containers, then sends the metrics to CloudMonitor server.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AlibabaCloudKubernetesCluster",
|
||||
"ResourceGroup": "container",
|
||||
"Description": "**Kubernetes Engine's cluster check** feature helps you verify the system nodes and components healthy status.\n\nWhen you trigger the checking, the process validates the health state of each node in your cluster and also the cluster configuration (`kubelet`, `docker daemon`, `kernel`, and network `iptables` configuration). If there are consecutive health check failures, the diagnose reports to admin for further repair.",
|
||||
"Risk": "Kubernetes Engine uses the node's health status to determine if a node needs to be repaired. A cluster health check includes: cloud resource healthy status including **VPC/VSwitch**, **SLB**, and every **ECS node** status in the cluster; the `kubelet`, `docker daemon`, `kernel`, `iptables` configurations on every node.\n\nWithout regular cluster checks, potential issues may go undetected and could lead to **cluster instability** or **security vulnerabilities**.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AlibabaCloudKubernetesCluster",
|
||||
"ResourceGroup": "container",
|
||||
"Description": "**Kubernetes Engine's cluster check** feature helps you verify the system nodes and components healthy status.\n\nWhen you trigger the checking, the process validates the health state of each node in your cluster and also the cluster configuration (`kubelet`, `docker daemon`, `kernel`, and network `iptables` configuration). If there are consecutive health check failures, the diagnose reports to admin for further repair.",
|
||||
"Risk": "Kubernetes Engine uses the node's health status to determine if a node needs to be repaired. A cluster health check includes: cloud resource healthy status including **VPC/VSwitch**, **SLB**, and every **ECS node** status in the cluster; the `kubelet`, `docker daemon`, `kernel`, `iptables` configurations on every node.\n\nWithout regular cluster checks, potential issues may go undetected and could lead to **cluster instability** or **security vulnerabilities**.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}",
|
||||
"Severity": "high",
|
||||
"ResourceType": "AlibabaCloudKubernetesCluster",
|
||||
"ResourceGroup": "container",
|
||||
"Description": "**Dashboard** is a web-based Kubernetes user interface that can be used to deploy containerized applications to a Kubernetes cluster, troubleshoot your containerized application, and manage the cluster itself.\n\nYou should disable the **Kubernetes Web UI (Dashboard)** when running on Kubernetes Engine. The Dashboard is backed by a highly privileged Kubernetes Service Account. It is recommended to use the **ACK User Console** instead to avoid privilege escalation via a compromised dashboard.",
|
||||
"Risk": "The **Kubernetes Dashboard** is backed by a highly privileged Service Account. If the Dashboard is compromised, it could allow an attacker to gain **full control** over the cluster and potentially **escalate privileges**.\n\nAttackers who gain access to the Dashboard can deploy malicious workloads, exfiltrate secrets, and compromise the entire cluster.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AlibabaCloudKubernetesCluster",
|
||||
"ResourceGroup": "container",
|
||||
"Description": "Alibaba Cloud **ENI (Elastic Network Interface)** supports assigning ranges of internal IP addresses as aliases to a single virtual machine's ENI network interfaces.\n\nWith **ENI multiple IP mode**, Kubernetes Engine clusters can allocate IP addresses from a CIDR block known to **Terway** network plugin. This makes your cluster more scalable and allows better interaction with other Alibaba Cloud products.",
|
||||
"Risk": "Without **ENI multiple IP mode** (provided by Terway), pods share the node's network interface in a less scalable way.\n\nUsing ENI multiple IPs allows pod IPs to be reserved within the network ahead of time, preventing conflict with other compute resources, and allows firewall controls for Pods to be applied separately from their nodes.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}",
|
||||
"Severity": "high",
|
||||
"ResourceType": "AlibabaCloudKubernetesCluster",
|
||||
"ResourceGroup": "container",
|
||||
"Description": "**Log Service** is a complete real-time data logging service on Alibaba Cloud supporting collection, shipping, search, storage, and analysis for logs.\n\nLog Service can automatically collect, process, and store your container and audit logs in a dedicated, persistent datastore. Container logs are collected from your containers, audit logs from the `kube-apiserver` or deployed ingress, and events about cluster activity such as the deletion of Pods or Secrets.",
|
||||
"Risk": "Without **Log Service** enabled, you lose visibility into container and system logs. The per-node logging agent collects: `kube-apiserver` audit logs, ingress visiting logs, and standard output/error logs from containerized processes.\n\nLack of logging makes **incident investigation**, **compliance auditing**, and **security monitoring** significantly more difficult.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AlibabaCloudKubernetesCluster",
|
||||
"ResourceGroup": "container",
|
||||
"Description": "A **Network Policy** is a specification of how groups of pods are allowed to communicate with each other and other network endpoints.\n\n`NetworkPolicy` resources use labels to select pods and define rules which specify what traffic is allowed. By default, pods are non-isolated and accept traffic from any source. Pods become isolated by having a NetworkPolicy that selects them.",
|
||||
"Risk": "Without **Network Policies**, all pods in a Kubernetes cluster can communicate with each other freely. This open communication model allows an attacker who compromises a single pod to potentially move **laterally** within the cluster and access sensitive services or data.\n\nNetwork Policies are essential for implementing **defense in depth** and **least privilege** networking.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AlibabaCloudKubernetesCluster",
|
||||
"ResourceGroup": "container",
|
||||
"Description": "A **private cluster** is a cluster that makes your master inaccessible from the public internet.\n\nIn a private cluster, nodes do not have public IP addresses, so your workloads run in an environment that is isolated from the internet. Nodes and masters communicate with each other privately using **VPC peering**.",
|
||||
"Risk": "Exposing the **API server endpoint** to the public internet increases the attack surface of your cluster. Attackers can attempt to probe for vulnerabilities, perform **brute force attacks**, or exploit misconfigurations if the API server is publicly accessible.\n\nUsing a private cluster significantly reduces network security risks.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}",
|
||||
"Severity": "high",
|
||||
"ResourceType": "AlibabaCloudKubernetesCluster",
|
||||
"ResourceGroup": "container",
|
||||
"Description": "In Kubernetes, authorizers interact by granting a permission if any authorizer grants the permission. The legacy authorizer in Kubernetes Engine grants broad, statically defined permissions.\n\nTo ensure that **RBAC** limits permissions correctly, you must disable the legacy authorizer. RBAC has significant security advantages, helps ensure that users only have access to specific cluster resources within their own namespace, and is now stable in Kubernetes.",
|
||||
"Risk": "In Kubernetes, **RBAC** is used to grant permissions to resources at the cluster and namespace level. RBAC allows you to define roles with rules containing a set of permissions.\n\nWithout RBAC, legacy authorization mechanisms like **ABAC** grant **overly broad permissions**, increasing the risk of unauthorized access and privilege escalation.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"ResourceIdTemplate": "acs:ecs:region:account-id:disk/{disk-id}",
|
||||
"Severity": "high",
|
||||
"ResourceType": "AlibabaCloudECSDisk",
|
||||
"ResourceGroup": "storage",
|
||||
"Description": "**ECS cloud disk encryption** protects your data at rest. The cloud disk data encryption feature automatically encrypts data when data is transferred from ECS instances to disks, and decrypts data when read from disks.\n\nEnsure that disks are encrypted when they are created with the creation of VM instances.",
|
||||
"Risk": "**Unencrypted disks** attached to ECS instances pose a security risk as they may contain sensitive data that could be accessed if the disk is compromised or accessed by unauthorized parties.\n\nData at rest without encryption is vulnerable to **unauthorized access** if storage media is lost, stolen, or improperly decommissioned.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"ResourceIdTemplate": "acs:ecs:region:account-id:instance/{instance-id}",
|
||||
"Severity": "high",
|
||||
"ResourceType": "AlibabaCloudECSInstance",
|
||||
"ResourceGroup": "compute",
|
||||
"Description": "Installing **endpoint protection systems** (like **Security Center** for Alibaba Cloud) provides real-time protection capability that helps identify and remove viruses, spyware, and other malicious software.\n\nConfigurable alerts notify when known malicious software attempts to install itself or run on ECS instances.",
|
||||
"Risk": "ECS instances without **endpoint protection** are vulnerable to **malware**, **viruses**, and other security threats.\n\nEndpoint protection provides real-time monitoring and protection capabilities essential for detecting and preventing security incidents.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:ecs:region:account-id:instance/{instance-id}",
|
||||
"Severity": "high",
|
||||
"ResourceType": "AlibabaCloudECSInstance",
|
||||
"ResourceGroup": "compute",
|
||||
"Description": "Windows and Linux virtual machines should be kept updated to address specific bugs or flaws, improve OS or application's general stability, and fix **security vulnerabilities**.\n\nThe Alibaba Cloud **Security Center** checks for the latest updates in Linux and Windows systems.",
|
||||
"Risk": "**Unpatched systems** are vulnerable to known security exploits and may be compromised by attackers.\n\nKeeping systems updated with the latest patches is critical for maintaining security and preventing **exploitation of known vulnerabilities**.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"ResourceIdTemplate": "acs:ecs:region:account-id:instance/{instance-id}",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AlibabaCloudECSInstance",
|
||||
"ResourceGroup": "compute",
|
||||
"Description": "In order to prevent use of **legacy networks**, ECS instances should not have a legacy network configured.\n\nLegacy networks have a single network IPv4 prefix range and a single gateway IP address for the whole network. With legacy networks, you cannot create subnetworks or switch from legacy to auto or custom subnet networks.",
|
||||
"Risk": "**Legacy networks** can have an impact on high network traffic ECS instances and are subject to a **single point of failure**.\n\nThey also lack the security isolation and network segmentation capabilities provided by **VPCs**.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:ecs:region:account-id:security-group/{security-group-id}",
|
||||
"Severity": "high",
|
||||
"ResourceType": "AlibabaCloudECSSecurityGroup",
|
||||
"ResourceGroup": "network",
|
||||
"Description": "**Security groups** provide stateful filtering of ingress/egress network traffic to Alibaba Cloud resources.\n\nIt is recommended that no security group allows unrestricted ingress access to port **3389 (RDP)**.",
|
||||
"Risk": "Removing unfettered connectivity to remote console services, such as **RDP**, reduces a server's exposure to risk.\n\nUnrestricted RDP access from the internet (`0.0.0.0/0`) exposes systems to **brute force attacks**, **credential stuffing**, and **exploitation of RDP vulnerabilities**.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:ecs:region:account-id:security-group/{security-group-id}",
|
||||
"Severity": "high",
|
||||
"ResourceType": "AlibabaCloudECSSecurityGroup",
|
||||
"ResourceGroup": "network",
|
||||
"Description": "**Security groups** provide stateful filtering of ingress/egress network traffic to Alibaba Cloud resources.\n\nIt is recommended that no security group allows unrestricted ingress access to port **22 (SSH)**.",
|
||||
"Risk": "Removing unfettered connectivity to remote console services, such as **SSH**, reduces a server's exposure to risk.\n\nUnrestricted SSH access from the internet (`0.0.0.0/0`) exposes systems to **brute force attacks**, **credential stuffing**, and **exploitation of SSH vulnerabilities**.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"ResourceIdTemplate": "acs:ecs:region:account-id:disk/{disk-id}",
|
||||
"Severity": "high",
|
||||
"ResourceType": "AlibabaCloudECSDisk",
|
||||
"ResourceGroup": "storage",
|
||||
"Description": "**Cloud disk encryption** protects your data at rest. The cloud disk data encryption feature automatically encrypts data when data is transferred from ECS instances to disks, and decrypts data when read from disks.",
|
||||
"Risk": "**Unencrypted unattached disks** pose a security risk as they may contain sensitive data that could be accessed if the disk is compromised or accessed by unauthorized parties.\n\nUnattached disks are especially vulnerable as they may be forgotten or not monitored, increasing the risk of **unauthorized access**.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:oss::account-id:bucket-name",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AlibabaCloudOSSBucket",
|
||||
"ResourceGroup": "storage",
|
||||
"Description": "**OSS Bucket Access Logging** generates a log that contains access records for each request made to your OSS bucket.\n\nAn access log record contains details about the request, such as the request type, the resources specified in the request, and the time and date the request was processed. It is recommended that bucket access logging be enabled on OSS buckets.",
|
||||
"Risk": "By enabling **OSS bucket logging** on target OSS buckets, it is possible to capture all events which may affect objects within target buckets.\n\nConfiguring logs to be placed in a separate bucket allows access to log information useful in **security** and **incident response** workflows.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:oss::account-id:bucket-name",
|
||||
"Severity": "critical",
|
||||
"ResourceType": "AlibabaCloudOSSBucket",
|
||||
"ResourceGroup": "storage",
|
||||
"Description": "A bucket is a container used to store objects in **Object Storage Service (OSS)**. All objects in OSS are stored in buckets.\n\nIt is recommended that the access policy on OSS buckets does not allow **anonymous** and/or **public access**.",
|
||||
"Risk": "Allowing **anonymous** and/or **public access** grants permissions to anyone to access bucket content. Such access might not be desired if you are storing any sensitive data.\n\nPublic buckets can lead to **data breaches**, **unauthorized data access**, and **compliance violations**.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"ResourceIdTemplate": "acs:oss::account-id:bucket-name",
|
||||
"Severity": "high",
|
||||
"ResourceType": "AlibabaCloudOSSBucket",
|
||||
"ResourceGroup": "storage",
|
||||
"Description": "Enable **data encryption in transit**. The secure transfer enhances the security of OSS buckets by only allowing requests to the storage account via a secure connection.\n\nFor example, when calling REST APIs to access storage accounts, the connection must use **HTTPS**. Any requests using HTTP will be rejected.",
|
||||
"Risk": "Without **secure transfer enforcement**, OSS buckets may accept HTTP requests, which are not encrypted in transit.\n\nThis exposes data to potential **interception** and **man-in-the-middle attacks**, compromising data confidentiality and integrity.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:ram::account-id:root",
|
||||
"Severity": "critical",
|
||||
"ResourceType": "AlibabaCloudRAMAccessKey",
|
||||
"ResourceGroup": "IAM",
|
||||
"Description": "Ensure no **root account access key** exists. Access keys provide programmatic access to a given Alibaba Cloud account.\n\nIt is recommended that all access keys associated with the root account be removed.",
|
||||
"Risk": "The **root account** is the most privileged user in an Alibaba Cloud account. Access Keys provide programmatic access to a given Alibaba Cloud account.\n\nRemoving access keys associated with the root account limits vectors by which the account can be compromised and encourages the creation and use of **role-based accounts** that are least privileged.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:ram::account-id:password-policy",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AlibabaCloudRAMPasswordPolicy",
|
||||
"ResourceGroup": "IAM",
|
||||
"Description": "**RAM password policies** can be used to ensure password complexity.\n\nIt is recommended that the password policy require at least one **lowercase letter**.",
|
||||
"Risk": "Enhancing complexity of a password policy increases account resiliency against **brute force logon attempts**.\n\nWeak passwords without character variety are more susceptible to dictionary attacks and automated password cracking tools.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:ram::account-id:password-policy",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AlibabaCloudRAMPasswordPolicy",
|
||||
"ResourceGroup": "IAM",
|
||||
"Description": "**RAM password policies** can temporarily block logon after several incorrect logon attempts within an hour.\n\nIt is recommended that the password policy is set to temporarily block logon after **5 incorrect logon attempts** within an hour.",
|
||||
"Risk": "Temporarily blocking logon for incorrect password input increases account resiliency against **brute force logon attempts**.\n\nThis control helps prevent automated password guessing attacks from succeeding.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:ram::account-id:password-policy",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AlibabaCloudRAMPasswordPolicy",
|
||||
"ResourceGroup": "IAM",
|
||||
"Description": "**RAM password policies** can require passwords to be expired after a given number of days.\n\nIt is recommended that the password policy expire passwords after **365 days** or greater.",
|
||||
"Risk": "Too frequent password changes are more harmful than beneficial. They offer no containment benefits and enforce bad habits, since they encourage users to choose variants of older passwords.\n\nThe CIS now recommends an **annual password reset** as a balanced approach.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:ram::account-id:password-policy",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AlibabaCloudRAMPasswordPolicy",
|
||||
"ResourceGroup": "IAM",
|
||||
"Description": "**RAM password policies** can be used to ensure password complexity.\n\nIt is recommended that the password policy require a minimum of **14 or greater characters** for any password.",
|
||||
"Risk": "Enhancing complexity of a password policy increases account resiliency against **brute force logon attempts**.\n\nLonger passwords provide exponentially more security against automated password cracking.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:ram::account-id:password-policy",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AlibabaCloudRAMPasswordPolicy",
|
||||
"ResourceGroup": "IAM",
|
||||
"Description": "**RAM password policies** can be used to ensure password complexity.\n\nIt is recommended that the password policy require at least one **number**.",
|
||||
"Risk": "Enhancing complexity of a password policy increases account resiliency against **brute force logon attempts**.\n\nWeak passwords without numeric characters are more susceptible to dictionary attacks.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:ram::account-id:password-policy",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AlibabaCloudRAMPasswordPolicy",
|
||||
"ResourceGroup": "IAM",
|
||||
"Description": "It is recommended that the **password policy** prevent the reuse of passwords.\n\nThis ensures users cannot cycle back to previously compromised passwords.",
|
||||
"Risk": "Preventing **password reuse** increases account resiliency against brute force logon attempts.\n\nIf a password is compromised and later reused, attackers with knowledge of old credentials can regain access.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:ram::account-id:password-policy",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AlibabaCloudRAMPasswordPolicy",
|
||||
"ResourceGroup": "IAM",
|
||||
"Description": "**RAM password policies** can be used to ensure password complexity.\n\nIt is recommended that the password policy require at least one **symbol**.",
|
||||
"Risk": "Enhancing complexity of a password policy increases account resiliency against **brute force logon attempts**.\n\nSpecial characters significantly increase the keyspace that attackers must search.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:ram::account-id:password-policy",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AlibabaCloudRAMPasswordPolicy",
|
||||
"ResourceGroup": "IAM",
|
||||
"Description": "**RAM password policies** can be used to ensure password complexity.\n\nIt is recommended that the password policy require at least one **uppercase letter**.",
|
||||
"Risk": "Enhancing complexity of a password policy increases account resiliency against **brute force logon attempts**.\n\nWeak passwords without case variety are more susceptible to dictionary attacks.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:ram::account-id:user/{user-name}",
|
||||
"Severity": "low",
|
||||
"ResourceType": "AlibabaCloudRAMUser",
|
||||
"ResourceGroup": "IAM",
|
||||
"Description": "By default, **RAM users**, groups, and roles have no access to Alibaba Cloud resources. RAM policies are the means by which privileges are granted to users, groups, or roles.\n\nIt is recommended that RAM policies be applied directly to **groups and roles** but not users.",
|
||||
"Risk": "Assigning privileges at the **group or role level** reduces the complexity of access management as the number of users grows.\n\nReducing access management complexity may in turn reduce opportunity for a principal to inadvertently receive or retain **excessive privileges**.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:ram::account-id:policy/{policy-name}",
|
||||
"Severity": "critical",
|
||||
"ResourceType": "AlibabaCloudRAMPolicy",
|
||||
"ResourceGroup": "IAM",
|
||||
"Description": "**RAM policies** represent permissions that can be granted to users, groups, or roles. It is recommended to grant **least privilege**—that is, granting only the permissions required to perform tasks.\n\nDetermine what users need to do and then create policies with permissions that only fit those tasks, instead of allowing full administrative privileges.",
|
||||
"Risk": "It is more secure to start with a minimum set of permissions and grant additional permissions as necessary. Providing **full administrative privileges** exposes your resources to potentially unwanted actions.\n\nRAM policies with `\"Effect\": \"Allow\"`, `\"Action\": \"*\"`, and `\"Resource\": \"*\"` should be prohibited.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:ram::account-id:user/{user-name}/accesskey/{access-key-id}",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AlibabaCloudRAMAccessKey",
|
||||
"ResourceGroup": "IAM",
|
||||
"Description": "An **access key** consists of an access key ID and a secret, which are used to sign programmatic requests that you make to Alibaba Cloud.\n\nRAM users need their own access keys to make programmatic calls from SDKs, CLIs, or direct API calls. It is recommended that all access keys be **regularly rotated**.",
|
||||
"Risk": "Access keys might be compromised by leaving them in code, configuration files, on-premise and cloud storages, and then stolen by attackers.\n\n**Rotating access keys** reduces the window of opportunity for a compromised access key to be used.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:ram::account-id:user/{user-name}",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AlibabaCloudRAMUser",
|
||||
"ResourceGroup": "IAM",
|
||||
"Description": "Alibaba Cloud **RAM users** can log on to the Alibaba Cloud console by using their username and password.\n\nIf a user has not logged on for **90 days or longer**, it is recommended to disable the console access of the user.",
|
||||
"Risk": "Disabling users from having unnecessary logon privileges will reduce the opportunity that an **abandoned user** or a user with **compromised password** to be exploited.\n\nInactive accounts are common targets for attackers attempting account takeover.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:ram::account-id:user/{user-name}",
|
||||
"Severity": "high",
|
||||
"ResourceType": "AlibabaCloudRAMUser",
|
||||
"ResourceGroup": "IAM",
|
||||
"Description": "**Multi-Factor Authentication (MFA)** adds an extra layer of protection on top of a username and password.\n\nWith MFA enabled, when a user logs on to Alibaba Cloud, they will be prompted for their username and password followed by an authentication code from their virtual MFA device. It is recommended that MFA be enabled for all users that have a console password.",
|
||||
"Risk": "**MFA** requires users to verify their identities by entering two authentication factors. When MFA is enabled, an attacker faces at least two different authentication mechanisms.\n\nThe additional security makes it significantly harder for an attacker to gain access even if passwords are compromised.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:rds:region:account-id:dbinstance/{dbinstance-id}",
|
||||
"Severity": "critical",
|
||||
"ResourceType": "AlibabaCloudRDSDBInstance",
|
||||
"ResourceGroup": "database",
|
||||
"Description": "Database Server should accept connections only from trusted **Network(s)/IP(s)** and restrict access from the world.\n\nTo minimize attack surface on a Database server Instance, only trusted/known and required IPs should be whitelisted. Authorized network should not have IPs/networks configured to `0.0.0.0` or `/0` which would allow access from anywhere in the world.",
|
||||
"Risk": "Allowing **public access** (`0.0.0.0/0`) to the database significantly increases the risk of **brute-force attacks**, **unauthorized access**, and **data exfiltration**.\n\nDatabases exposed to the internet are prime targets for attackers.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:rds:region:account-id:dbinstance/{dbinstance-id}",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AlibabaCloudRDSDBInstance",
|
||||
"ResourceGroup": "database",
|
||||
"Description": "Enable `log_connections` on **PostgreSQL Servers**. Enabling `log_connections` helps PostgreSQL Database log attempted connections to the server, as well as successful completion of client authentication.\n\nLog data can be used to identify, troubleshoot, and repair configuration errors and suboptimal performance.",
|
||||
"Risk": "Without **connection logging**, unauthorized access attempts might go unnoticed, and troubleshooting connection issues becomes more difficult.\n\nThis data is essential for **security monitoring** and **incident investigation**.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:rds:region:account-id:dbinstance/{dbinstance-id}",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AlibabaCloudRDSDBInstance",
|
||||
"ResourceGroup": "database",
|
||||
"Description": "Enable `log_disconnections` on **PostgreSQL Servers**. Enabling `log_disconnections` helps PostgreSQL Database log session terminations of the server, as well as duration of the session.\n\nLog data can be used to identify, troubleshoot, and repair configuration errors and suboptimal performance.",
|
||||
"Risk": "Without **disconnection logging**, it's harder to track session durations and identify abnormal disconnection patterns that might indicate **attacks** or **stability issues**.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"ResourceIdTemplate": "acs:rds:region:account-id:dbinstance/{dbinstance-id}",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AlibabaCloudRDSDBInstance",
|
||||
"ResourceGroup": "database",
|
||||
"Description": "Enable `log_duration` on **PostgreSQL Servers**. Enabling `log_duration` helps PostgreSQL Database log the duration of each completed SQL statement which in turn generates query and error logs.\n\nQuery and error logs can be used to identify, troubleshoot, and repair configuration errors and sub-optimal performance.",
|
||||
"Risk": "Without **duration logging**, it's difficult to identify **slow queries**, **performance bottlenecks**, and potential **DoS attempts**.\n\nThis information is critical for database performance tuning and security monitoring.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"ResourceIdTemplate": "acs:rds:region:account-id:dbinstance/{dbinstance-id}",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AlibabaCloudRDSDBInstance",
|
||||
"ResourceGroup": "database",
|
||||
"Description": "Enable **SQL auditing** on all RDS instances (except SQL Server 2012/2016/2017 and MariaDB TX). Auditing tracks database events and writes them to an audit log.\n\nIt helps to maintain **regulatory compliance**, understand database activity, and gain insight into discrepancies and anomalies that could indicate business concerns or suspected security violations.",
|
||||
"Risk": "Without **SQL auditing**, it's difficult to detect **unauthorized access**, **data breaches**, or **malicious activity** within the database.\n\nIt also hinders **forensic investigations** and compliance reporting.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"ResourceIdTemplate": "acs:rds:region:account-id:dbinstance/{dbinstance-id}",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AlibabaCloudRDSDBInstance",
|
||||
"ResourceGroup": "database",
|
||||
"Description": "Database **SQL Audit Retention** should be configured to be greater than or equal to the configured period (default: **6 months / 180 days**).\n\nAudit Logs can be used to check for anomalies and give insight into suspected breaches or misuse of information and access.",
|
||||
"Risk": "**Short retention periods** for audit logs can result in the loss of critical forensic data needed for **incident investigation** and **compliance auditing**.\n\nMany regulations require minimum retention periods for audit data.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:rds:region:account-id:dbinstance/{dbinstance-id}",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AlibabaCloudRDSDBInstance",
|
||||
"ResourceGroup": "database",
|
||||
"Description": "It is recommended to enforce all incoming connections to SQL database instances to use **SSL**.\n\nSQL database connections if successfully intercepted (MITM) can reveal sensitive data like credentials, database queries, and query outputs. For security, it is recommended to always use SSL encryption when connecting to your instance.",
|
||||
"Risk": "If **SSL is not enabled**, data in transit (including credentials and query results) can be intercepted by attackers performing **Man-in-the-Middle (MITM) attacks**.\n\nThis compromises data confidentiality and integrity.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:rds:region:account-id:dbinstance/{dbinstance-id}",
|
||||
"Severity": "high",
|
||||
"ResourceType": "AlibabaCloudRDSDBInstance",
|
||||
"ResourceGroup": "database",
|
||||
"Description": "Enable **Transparent Data Encryption (TDE)** on every RDS instance. RDS Database TDE helps protect against the threat of malicious activity by performing real-time encryption and decryption of the database, associated backups, and log files at rest.\n\nNo changes to the application are required.",
|
||||
"Risk": "**Data at rest** that is not encrypted is vulnerable to unauthorized access if the underlying storage media or backups are compromised.\n\nTDE protects against physical theft and unauthorized access to storage systems.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"ResourceIdTemplate": "acs:rds:region:account-id:dbinstance/{dbinstance-id}",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AlibabaCloudRDSDBInstance",
|
||||
"ResourceGroup": "database",
|
||||
"Description": "**TDE with BYOK** support provides increased transparency and control, increased security with an HSM-backed KMS service, and promotion of separation of duties.\n\nBased on business needs or criticality of data, it is recommended that the TDE protector is encrypted by a key that is managed by the data owner (**BYOK**).",
|
||||
"Risk": "Using **service-managed keys** means the cloud provider manages the encryption keys. **BYOK (Bring Your Own Key)** gives you full control over the key lifecycle and permissions.\n\nThis ensures that even the cloud provider cannot access your data without your explicit permission.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"ResourceIdTemplate": "acs:sas::account-id:security-center",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AlibabaCloudSecurityCenter",
|
||||
"ResourceGroup": "security",
|
||||
"Description": "The **Advanced or Enterprise Edition** enables threat detection for network and endpoints, providing **malware detection**, **webshell detection**, and **anomaly detection** in Security Center.",
|
||||
"Risk": "Using **Basic or Free Edition** of Security Center may not provide comprehensive protection against cloud threats.\n\n**Advanced or Enterprise Edition** allows for full protection to defend against cloud threats.",
|
||||
"RelatedUrl": "",
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"ResourceIdTemplate": "acs:sas:region:account-id:machine/{machine-id}",
|
||||
"Severity": "high",
|
||||
"ResourceType": "AlibabaCloudSecurityCenterMachine",
|
||||
"ResourceGroup": "security",
|
||||
"Description": "The endpoint protection of **Security Center** requires an agent to be installed on the endpoint to work. Such an agent-based approach allows the security center to provide comprehensive endpoint intrusion detection and protection capabilities.\n\nThis includes remote logon detection, **webshell detection** and removal, **anomaly detection** (detection of abnormal process behaviors and network connections), and detection of changes in key files and suspicious accounts.",
|
||||
"Risk": "Assets without **Security Center agent** installed are not protected by endpoint intrusion detection and protection capabilities, leaving them vulnerable to security threats.\n\nUnprotected assets become blind spots in your security monitoring.",
|
||||
"RelatedUrl": "",
|
||||
|
||||