Compare commits

..

38 Commits

Author SHA1 Message Date
Toni de la Fuente 55327704dd feat(ui): add GitHub integration UI components
Add complete frontend implementation for GitHub integration following
the same pattern as Jira integration.

Components:
- GitHubIntegrationForm: Form for creating/editing GitHub integrations
- GitHubIntegrationsManager: Manager component for listing and managing integrations
- GitHubIntegrationCard: Card component for main integrations page
- GitHub integration page at /integrations/github

Features:
- Personal Access Token input with validation
- Optional repository owner filter
- Connection testing and repository discovery
- Enable/disable integration toggle
- Edit credentials functionality
- Delete integration with confirmation
- Pagination support
- Integration status display with last checked timestamp

Server Actions:
- getGitHubIntegrations(): Fetch enabled GitHub integrations
- sendFindingToGitHub(): Send finding to GitHub as issue
- pollGitHubDispatchTask(): Poll async task completion

Types & Schemas:
- githubIntegrationFormSchema: Zod schema for creation
- editGitHubIntegrationFormSchema: Zod schema for editing
- GitHubCredentialsPayload: TypeScript interface for credentials
- GitHubDispatchRequest/Response: API types for dispatching

Integration Page:
- Created /integrations/github page with features list
- Added GitHub card to main integrations overview
- Exported GitHub components from integrations index

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-07 21:44:45 +01:00
Toni de la Fuente a8c244849f feat(api): add GitHub integration for sending findings as issues
Add complete GitHub integration to Prowler that allows sending security
findings as GitHub Issues, working similarly to the existing Jira integration.

Features:
- GitHub API client with Personal Access Token (PAT) authentication
- Support for creating issues from Prowler findings
- Automatic repository discovery and validation
- Label support for issue categorization
- Rich markdown-formatted issues with detailed finding information
- Full API integration with CRUD operations
- Async task processing for bulk operations
- Connection testing and validation

Implementation:
- Add GitHub API client in prowler/lib/outputs/github/
- Add GitHub to Integration model choices
- Create serializers and validators for GitHub credentials and configuration
- Implement IntegrationGitHubViewSet for API endpoints
- Add async tasks and job processing for sending findings
- Add URL routing for /integrations/{id}/github/dispatches endpoint

API Changes:
- New integration type: "github"
- Credentials: token (required), owner (optional)
- Configuration: repositories dict, owner string
- Dispatch endpoint accepts repository and optional labels

Issue Format:
- Title: [Prowler] SEVERITY - CHECK_ID - RESOURCE_UID
- Body includes: finding details, risk description, recommendations,
  remediation code (CLI/Terraform/IaC), resource tags, compliance info

Security:
- GitHub PAT encrypted with Fernet before storage
- Repository access validated before dispatch
- All API calls use HTTPS
- Comprehensive error handling and logging

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-07 21:38:16 +01:00
Adrián Peña df8d82345d fix(api): update dependencies to patch security vulnerabilities (#9730) 2026-01-07 18:10:58 +01:00
lydiavilchez 3e4458c8f3 feat(gcp): add check to detect VMs with multiple network interfaces (#9702) 2026-01-07 17:04:53 +01:00
lydiavilchez e12e0dc1aa feat(gcp): add check to ensure Compute Engine disk images are not publicly shared (#9718) 2026-01-07 15:05:36 +01:00
Rubén De la Torre Vico beb2daa30d chore(aws): enhance metadata for transfer service (#9434)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-01-07 14:59:16 +01:00
Rubén De la Torre Vico 14b60b8bee chore(aws): enhance metadata for vpc service (#9479)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-01-07 14:36:27 +01:00
Pedro Martín cab9b008d1 docs(alibabacloud): provider documentation (#9721) 2026-01-07 11:45:57 +01:00
Rubén De la Torre Vico ced0b8def4 chore(aws): enhance metadata for opensearch service (#9383)
Co-authored-by: HugoPBrito <hugopbrit@gmail.com>
2026-01-07 10:31:41 +01:00
Alan Buscaglia f31e230537 fix(ui): extend Risk Plot gradient to cover full chart area (#9720) 2026-01-05 15:34:17 +01:00
Andoni Alonso c6cc82c527 docs(aws): update CloudFormation template reference in role-assumption docs (#9719) 2026-01-05 14:44:51 +01:00
dependabot[bot] 5cc3cdc466 build(deps): bump @langchain/core from 1.1.4 to 1.1.8 in /ui (#9687)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-05 13:12:25 +01:00
Pedro Martín b7f83da012 feat(troubleshooting): add info about too many open files error (#9703) 2026-01-05 11:51:19 +01:00
mchennai 4169611a6a test(s3_bucket_server_access_logging_enabled): Add multi-bucket test (#9716)
Co-authored-by: pedrooot <pedromarting3@gmail.com>
2026-01-05 11:34:57 +01:00
Daniel Barranquero 9ad2e1ef98 chore(docs): fix troubleshooting link in readme (#9700) 2025-12-30 14:36:54 +01:00
lydiavilchez 78ce4d8d9b feat(gcp): add check to ensure Managed Instance Groups have autohealing enabled (#9690)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2025-12-30 12:40:47 +01:00
Alan Buscaglia 49585ac6c7 feat(ui): add gradient to Risk Plot and refactor ScatterPlot as reusable component (#9664) 2025-12-29 16:35:41 +01:00
César Arroba 0c3c6aea0e chore: include ExternalId on CFN template (#9697) 2025-12-29 15:19:40 +01:00
lydiavilchez 144d59de45 feat(gcp): add check to ensure Managed Instance Groups are attached to load balancers (#9695)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2025-12-29 14:16:11 +01:00
Rubén De la Torre Vico e3027190de chore(aws): enhance metadata for workspaces service (#9483)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2025-12-26 13:31:55 +01:00
Rubén De la Torre Vico 9f4b5e01cf chore(aws): enhance metadata for ssmincidents service (#9431)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2025-12-26 13:11:01 +01:00
Rubén De la Torre Vico 8acdf8e65b chore(aws): enhance metadata for ses service (#9411)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2025-12-26 13:03:58 +01:00
Rubén De la Torre Vico 35c727c7e4 chore(aws): enhance metadata for securityhub service (#9409)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2025-12-26 12:57:49 +01:00
Rubén De la Torre Vico 18fa788268 chore(aws): enhance metadata for sagemaker service (#9407)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2025-12-26 12:46:02 +01:00
mchennai b6e04f507c fix(metadata): Remediation URL for s3_bucket_server_access_logging_enabled (#9693) 2025-12-26 12:31:24 +01:00
Rubén De la Torre Vico 85c90cac31 chore(aws): enhance metadata for resourceexplorer2 service (#9386)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2025-12-26 12:16:56 +01:00
Rubén De la Torre Vico 4ed27e1aaa chore(aws): enhance metadata for organizations service (#9384)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2025-12-26 12:08:30 +01:00
Rubén De la Torre Vico 53b5030f00 chore(aws): enhance metadata for ssm service (#9430)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2025-12-26 11:06:08 +01:00
Rubén De la Torre Vico 627d6da699 chore(aws): enhance metadata for wellarchitected service (#9482)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2025-12-24 12:44:47 +01:00
Rubén De la Torre Vico 352f136a0f chore(aws): enhance metadata for storagegateway service (#9433)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2025-12-24 12:36:14 +01:00
Rubén De la Torre Vico ab4d7e0c19 chore(aws): enhance metadata for redshift service (#9385)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2025-12-24 12:10:55 +01:00
Ryan Nolette 47532cf498 feat: add category filter to all Prowler dashboards (#9137)
Co-authored-by: pedrooot <pedromarting3@gmail.com>
2025-12-24 11:23:10 +01:00
Alejandro Bailo afb8701450 test: fix providers page model according new components (#9691) 2025-12-24 11:07:22 +01:00
César Arroba 942177ae59 chore(github): fix sdk container build pipeline (#9689) 2025-12-24 10:03:28 +01:00
César Arroba 750182cd6d chore(github): fix container build pipelines (#9688) 2025-12-24 10:00:01 +01:00
Adrián Peña 9bfa1e740c feat(checks): add ResourceGroup field to all check metadata for resource classification (#9656) 2025-12-24 09:13:14 +01:00
Pepe Fagoaga e58e939f55 chore(api): update lock for SDK (#9673) 2025-12-23 16:56:40 +01:00
Pepe Fagoaga d7f0b5b190 chore(labeler): add missing entries for OCI and AlibabaCloud (#9665) 2025-12-23 15:02:11 +01:00
1229 changed files with 9411 additions and 3721 deletions
+7
View File
@@ -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:
+2 -2
View File
@@ -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
+7
View File
@@ -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
+1835 -2122
View File
File diff suppressed because it is too large Load Diff
+5 -2
View File
@@ -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"
+20
View File
@@ -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")
+1
View File
@@ -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")
+29 -2
View File
@@ -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",
+74 -1
View File
@@ -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.
+4
View File
@@ -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
View File
@@ -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"],
+77 -2
View File
@@ -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,
+18
View File
@@ -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",
+25
View File
@@ -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"},
),
],
)
+3 -1
View File
@@ -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(
[
+57
View File
@@ -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,
+60
View File
@@ -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 Audiences Expertise
### Understand Your Audiences 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).
+26
View File
@@ -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.
+1
View File
@@ -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",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 555 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 555 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 812 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 860 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 42 KiB

+67 -26
View File
@@ -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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 42 KiB

+26 -23
View File
@@ -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 | ❌ | ✅ | ❌ | ✅ |
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

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
![Get Account ID](/images/providers/alibaba-account-id.png)
### 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"
![Cloud Providers Page](/images/prowler-app/cloud-providers-page.png)
3. Click "Add Cloud Provider"
![Add a Cloud Provider](/images/prowler-app/add-cloud-provider.png)
4. Select "Alibaba Cloud"
![Select Alibaba Cloud](/images/providers/select-alibaba-cloud.png)
5. Enter your Alibaba Cloud Account ID and optionally provide a friendly alias
![Add Account ID](/images/providers/add-alibaba-account-id.png)
### Step 3: Choose and Provide Authentication
After the Account ID is in place, select the authentication method that matches your Alibaba Cloud setup:
![Select Auth Method](/images/providers/select-auth-method-alibaba.png)
#### 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
![Input the Role ARN](/images/providers/alibaba-get-role-arn.png)
<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**
![Filled Credentials Page](/images/providers/alibaba-credentials-form.png)
<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
![Launch Scan](/images/providers/launch-scan-alibaba.png)
---
## 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).
-110
View File
@@ -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/*'
+19
View File
@@ -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)
---
+1
View File
@@ -112,6 +112,7 @@ class CheckMetadata(BaseModel):
ResourceIdTemplate: str
Severity: Severity
ResourceType: str
ResourceGroup: str = Field(default="")
Description: str
Risk: str
RelatedUrl: str
+5
View File
@@ -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."""
+626
View File
@@ -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": "",

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