Compare commits

..

39 Commits

Author SHA1 Message Date
Pedro Miguel Díaz Peña 503b5bfe15 feat(skills): add prowler-changelog-review skill
Add skill to review changelog completeness between two versions
across all Prowler components (API, UI, SDK, MCP Server). The skill
extracts PR numbers from git log, cross-references with CHANGELOG.md
entries, checks for no-changelog labels, detects revert pairs, and
reports missing entries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 15:18:26 +01:00
Andoni Alonso 3afa98084f chore(ci): update Josema user for labeling purposes (#10041) 2026-02-12 11:46:14 +01:00
Alejandro Bailo b0ee914825 chore(ui): improve changelog wording for v1.18.2 bug fixes (#10044) 2026-02-12 11:30:56 +01:00
Andoni Alonso eabe488437 feat(aws): update privilege escalation check with pathfinding.cloud patterns (#9922) 2026-02-12 09:39:39 +01:00
Alejandro Bailo 8104382cc1 fix(ui): reapply filter transition opacity overlay on filter changes (#10036) 2026-02-11 22:13:33 +01:00
Alejandro Bailo 592c7bac81 fix(ui): move default muted filter from middleware to client-side hook (#10034) 2026-02-11 20:58:58 +01:00
Alejandro Bailo 3aefde14aa revert: re-integrate signalFilterChange into useUrlFilters (#10028) (#10032) 2026-02-11 20:21:58 +01:00
Alejandro Bailo 02f3e77eaf fix(ui): re-integrate signalFilterChange into useUrlFilters and always reset page on filter change (#10028) 2026-02-11 20:06:26 +01:00
Alejandro Bailo bcd7b2d723 fix(ui): remove useTransition and shared context from useUrlFilters (#10025) 2026-02-11 18:57:48 +01:00
Alejandro Bailo 86946f3a84 fix(ui): fix findings filter silent reverts by replacing useRelatedFilters effect with pure derivation (#10021)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 17:57:38 +01:00
Andoni Alonso fce1e4f3d2 feat(m365): add defender_safe_attachments_policy_enabled security check (#9833)
Co-authored-by: HugoPBrito <hugopbrit@gmail.com>
2026-02-11 15:42:11 +01:00
Andoni Alonso 5d490fa185 feat(m365): add defender_atp_safe_attachments_and_docs_configured security check (#9837)
Co-authored-by: HugoPBrito <hugopbrit@gmail.com>
2026-02-11 15:21:06 +01:00
Alejandro Bailo ea847d8824 fix(ui): use local transitions for filter navigation to prevent silent reverts (#10017) 2026-02-11 14:41:03 +01:00
Andoni Alonso c5f7e80b20 feat(m365): add defender_safelinks_policy_enabled security check (#9832)
Co-authored-by: HugoPBrito <hugopbrit@gmail.com>
2026-02-11 13:03:32 +01:00
Alejandro Bailo f5345a3982 fix(ui): fix filter navigation and pagination bugs in findings and scans pages (#10013) 2026-02-11 11:18:29 +01:00
Adrián Peña b539514d8d docs: restructure SAML SSO guide for Okta App Catalog (#10012) 2026-02-11 11:15:59 +01:00
Hugo Pereira Brito 9acef41f96 fix(sdk): mute HPACK library logs to prevent token leakage (#10010) 2026-02-11 10:59:15 +01:00
Pedro Martín c40adce2ff feat(oraclecloud): add CIS 3.1 compliance framework (#9971) 2026-02-11 10:39:16 +01:00
Adrián Peña 378c2ff7f6 fix(saml): prevent SAML role mapping from removing last manage-account user (#10007) 2026-02-10 15:57:34 +01:00
Alejandro Bailo d54095abde feat(ui): add expandable row support to DataTable (#9940) 2026-02-10 15:51:55 +01:00
Alejandro Bailo a12cb5b6d6 feat(ui): add TreeView component for hierarchical data (#9911) 2026-02-10 15:26:07 +01:00
Andoni Alonso dde42b6a84 fix(github): combine --repository and --organization flags for scan scoping (#10001) 2026-02-10 14:34:59 +01:00
Prowler Bot 3316ec8d23 feat(aws): Update regions for AWS services (#9989)
Co-authored-by: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
2026-02-10 12:02:09 +01:00
Alejandro Bailo 71220b2696 fix(ui): replace HeroUI dropdowns with Radix ActionDropdown to fix overlay conflict (#9996) 2026-02-10 10:28:03 +01:00
Utwo dd730eec94 feat(app): Helm chart for deploying prowler in k8s (#9835)
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-02-09 16:43:12 +01:00
Alejandro Bailo afe2e0a09e fix(ui): guard against unknown provider types in ProviderTypeSelector (#9991) 2026-02-09 15:18:50 +01:00
Alejandro Bailo 507d163a50 docs(ui): mark changelog v1.18.1 as released with Prowler v5.18.1 (#9993) 2026-02-09 13:16:44 +01:00
Josema Camacho 530fef5106 chore(attack-pahts): Internet node is now created while Attack Paths scan (#9992) 2026-02-09 12:17:51 +01:00
Josema Camacho 5cbbceb3be chore(attack-pahts): improve attack paths queries attribution (#9983) 2026-02-09 11:07:12 +01:00
Daniel Barranquero fa189e7eb9 docs(openstack): add provider to introduction table (#9990) 2026-02-09 10:33:10 +01:00
Pedro Martín fb966213cc test(e2e): add e2e tests for alibabacloud provider (#9729) 2026-02-09 10:25:26 +01:00
Rubén De la Torre Vico 097a60ebc9 chore(azure): enhance metadata for monitor service (#9622)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-02-09 10:12:57 +01:00
Pedro Martín db03556ef6 chore(readme): update content (#9972) 2026-02-09 09:09:46 +01:00
Josema Camacho ecc8eaf366 feat(skills): create new Attack Packs queries in openCypher (#9975) 2026-02-06 11:57:33 +01:00
Alan Buscaglia 619d1ffc62 chore(ci): remove legacy E2E workflow superseded by optimized v2 (#9977) 2026-02-06 11:20:10 +01:00
Alan Buscaglia 9e20cb2e5a fix(ui): optimize scans page polling to avoid redundant API calls (#9974)
Co-authored-by: pedrooot <pedromarting3@gmail.com>
2026-02-06 10:49:15 +01:00
Prowler Bot cb76e77851 chore(api): Bump version to v1.20.0 (#9968)
Co-authored-by: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
2026-02-05 22:18:33 +01:00
Prowler Bot a24f818547 chore(release): Bump version to v5.19.0 (#9964)
Co-authored-by: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
2026-02-05 22:17:38 +01:00
Prowler Bot e07687ce67 docs: Update version to v5.18.0 (#9965)
Co-authored-by: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
2026-02-05 22:16:42 +01:00
140 changed files with 9660 additions and 1396 deletions
+1 -1
View File
@@ -55,7 +55,7 @@ jobs:
"danibarranqueroo"
"HugoPBrito"
"jfagoagas"
"josemazo"
"josema-xyz"
"lydiavilchez"
"mmuller88"
"MrCloudSec"
+4
View File
@@ -65,6 +65,10 @@ jobs:
E2E_OCI_KEY_CONTENT: ${{ secrets.E2E_OCI_KEY_CONTENT }}
E2E_OCI_REGION: ${{ secrets.E2E_OCI_REGION }}
E2E_NEW_USER_PASSWORD: ${{ secrets.E2E_NEW_USER_PASSWORD }}
E2E_ALIBABACLOUD_ACCOUNT_ID: ${{ secrets.E2E_ALIBABACLOUD_ACCOUNT_ID }}
E2E_ALIBABACLOUD_ACCESS_KEY_ID: ${{ secrets.E2E_ALIBABACLOUD_ACCESS_KEY_ID }}
E2E_ALIBABACLOUD_ACCESS_KEY_SECRET: ${{ secrets.E2E_ALIBABACLOUD_ACCESS_KEY_SECRET }}
E2E_ALIBABACLOUD_ROLE_ARN: ${{ secrets.E2E_ALIBABACLOUD_ROLE_ARN }}
# Pass E2E paths from impact analysis
E2E_TEST_PATHS: ${{ needs.impact-analysis.outputs.ui-e2e }}
RUN_ALL_TESTS: ${{ needs.impact-analysis.outputs.run-all }}
+6
View File
@@ -40,10 +40,12 @@ Use these skills for detailed patterns on-demand:
| `prowler-compliance-review` | Review compliance framework PRs | [SKILL.md](skills/prowler-compliance-review/SKILL.md) |
| `prowler-provider` | Add new cloud providers | [SKILL.md](skills/prowler-provider/SKILL.md) |
| `prowler-changelog` | Changelog entries (keepachangelog.com) | [SKILL.md](skills/prowler-changelog/SKILL.md) |
| `prowler-changelog-review` | Review changelog completeness between versions | [SKILL.md](skills/prowler-changelog-review/SKILL.md) |
| `prowler-ci` | CI checks and PR gates (GitHub Actions) | [SKILL.md](skills/prowler-ci/SKILL.md) |
| `prowler-commit` | Professional commits (conventional-commits) | [SKILL.md](skills/prowler-commit/SKILL.md) |
| `prowler-pr` | Pull request conventions | [SKILL.md](skills/prowler-pr/SKILL.md) |
| `prowler-docs` | Documentation style guide | [SKILL.md](skills/prowler-docs/SKILL.md) |
| `prowler-attack-paths-query` | Create Attack Paths openCypher queries | [SKILL.md](skills/prowler-attack-paths-query/SKILL.md) |
| `skill-creator` | Create new AI agent skills | [SKILL.md](skills/skill-creator/SKILL.md) |
### Auto-invoke Skills
@@ -56,6 +58,7 @@ When performing these actions, ALWAYS invoke the corresponding skill FIRST:
| Adding DRF pagination or permissions | `django-drf` |
| Adding new providers | `prowler-provider` |
| Adding services to existing providers | `prowler-provider` |
| Adding privilege escalation detection queries | `prowler-attack-paths-query` |
| After creating/modifying a skill | `skill-sync` |
| App Router / Server Actions | `nextjs-15` |
| Building AI chat features | `ai-sdk-5` |
@@ -63,6 +66,7 @@ When performing these actions, ALWAYS invoke the corresponding skill FIRST:
| Create PR that requires changelog entry | `prowler-changelog` |
| Create a PR with gh pr create | `prowler-pr` |
| Creating API endpoints | `jsonapi` |
| Creating Attack Paths queries | `prowler-attack-paths-query` |
| Creating ViewSets, serializers, or filters in api/ | `django-drf` |
| Creating Zod schemas | `zod-4` |
| Creating a git commit | `prowler-commit` |
@@ -82,6 +86,7 @@ When performing these actions, ALWAYS invoke the corresponding skill FIRST:
| Modifying API responses | `jsonapi` |
| Regenerate AGENTS.md Auto-invoke tables (sync.sh) | `skill-sync` |
| Review PR requirements: template, title conventions, changelog gate | `prowler-pr` |
| Review changelog completeness between versions | `prowler-changelog-review` |
| Review changelog format and conventions | `prowler-changelog` |
| Reviewing JSON:API compliance | `jsonapi` |
| Reviewing compliance framework PRs | `prowler-compliance-review` |
@@ -92,6 +97,7 @@ When performing these actions, ALWAYS invoke the corresponding skill FIRST:
| Understand changelog gate and no-changelog label behavior | `prowler-ci` |
| Understand review ownership with CODEOWNERS | `prowler-pr` |
| Update CHANGELOG.md in any component | `prowler-changelog` |
| Updating existing Attack Paths queries | `prowler-attack-paths-query` |
| Updating existing checks and metadata | `prowler-sdk-check` |
| Using Zustand stores | `zustand-5` |
| Working on MCP server tools | `prowler-mcp` |
+5 -4
View File
@@ -104,18 +104,19 @@ Every AWS provider scan will enqueue an Attack Paths ingestion job automatically
| Provider | Checks | Services | [Compliance Frameworks](https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/compliance/) | [Categories](https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/misc/#categories) | Support | Interface |
|---|---|---|---|---|---|---|
| AWS | 584 | 84 | 40 | 17 | Official | UI, API, CLI |
| Azure | 169 | 22 | 16 | 12 | Official | UI, API, CLI |
| AWS | 585 | 84 | 40 | 17 | Official | UI, API, CLI |
| Azure | 169 | 22 | 17 | 13 | Official | UI, API, CLI |
| GCP | 100 | 17 | 14 | 7 | Official | UI, API, CLI |
| Kubernetes | 84 | 7 | 7 | 9 | Official | UI, API, CLI |
| GitHub | 20 | 2 | 1 | 2 | Official | UI, API, CLI |
| M365 | 71 | 7 | 4 | 3 | Official | UI, API, CLI |
| M365 | 72 | 7 | 4 | 4 | Official | UI, API, CLI |
| OCI | 52 | 14 | 1 | 12 | Official | UI, API, CLI |
| Alibaba Cloud | 64 | 9 | 2 | 9 | Official | UI, API, CLI |
| Cloudflare | 23 | 2 | 0 | 5 | Official | CLI |
| Cloudflare | 29 | 3 | 0 | 5 | Official | CLI |
| IaC | [See `trivy` docs.](https://trivy.dev/latest/docs/coverage/iac/) | N/A | N/A | N/A | Official | UI, API, CLI |
| MongoDB Atlas | 10 | 3 | 0 | 3 | Official | UI, API, CLI |
| LLM | [See `promptfoo` docs.](https://www.promptfoo.dev/docs/red-team/plugins/) | N/A | N/A | N/A | Official | CLI |
| OpenStack | 1 | 1 | 0 | 2 | Official | CLI |
| NHN | 6 | 2 | 1 | 0 | Unofficial | CLI |
> [!Note]
+4
View File
@@ -3,6 +3,7 @@
> **Skills Reference**: For detailed patterns, use these skills:
> - [`prowler-api`](../skills/prowler-api/SKILL.md) - Models, Serializers, Views, RLS patterns
> - [`prowler-test-api`](../skills/prowler-test-api/SKILL.md) - Testing patterns (pytest-django)
> - [`prowler-attack-paths-query`](../skills/prowler-attack-paths-query/SKILL.md) - Attack Paths openCypher queries
> - [`django-drf`](../skills/django-drf/SKILL.md) - Generic DRF patterns
> - [`jsonapi`](../skills/jsonapi/SKILL.md) - Strict JSON:API v1.1 spec compliance
> - [`pytest`](../skills/pytest/SKILL.md) - Generic pytest patterns
@@ -15,9 +16,11 @@ When performing these actions, ALWAYS invoke the corresponding skill FIRST:
|--------|-------|
| Add changelog entry for a PR or feature | `prowler-changelog` |
| Adding DRF pagination or permissions | `django-drf` |
| Adding privilege escalation detection queries | `prowler-attack-paths-query` |
| Committing changes | `prowler-commit` |
| Create PR that requires changelog entry | `prowler-changelog` |
| Creating API endpoints | `jsonapi` |
| Creating Attack Paths queries | `prowler-attack-paths-query` |
| Creating ViewSets, serializers, or filters in api/ | `django-drf` |
| Creating a git commit | `prowler-commit` |
| Creating/modifying models, views, serializers | `prowler-api` |
@@ -27,6 +30,7 @@ When performing these actions, ALWAYS invoke the corresponding skill FIRST:
| Reviewing JSON:API compliance | `jsonapi` |
| Testing RLS tenant isolation | `prowler-test-api` |
| Update CHANGELOG.md in any component | `prowler-changelog` |
| Updating existing Attack Paths queries | `prowler-attack-paths-query` |
| Writing Prowler API tests | `prowler-test-api` |
| Writing Python tests with pytest | `pytest` |
+9
View File
@@ -2,6 +2,15 @@
All notable changes to the **Prowler API** are documented in this file.
## [1.20.0] (Prowler UNRELEASED)
### 🔄 Changed
- Attack Paths: Queries definition now has short description and attribution [(#9983)](https://github.com/prowler-cloud/prowler/pull/9983)
- Attack Paths: Internet node is created while scan [(#9992)](https://github.com/prowler-cloud/prowler/pull/9992)
---
## [1.19.2] (Prowler v5.18.2)
### 🐞 Fixed
+13 -368
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -24,7 +24,7 @@ dependencies = [
"drf-spectacular-jsonapi==0.5.1",
"gunicorn==23.0.0",
"lxml==5.3.2",
"prowler @ git+https://github.com/prowler-cloud/prowler.git@v5.18",
"prowler @ git+https://github.com/prowler-cloud/prowler.git@master",
"psycopg2-binary==2.9.9",
"pytest-celery[redis] (>=1.0.1,<2.0.0)",
"sentry-sdk[django] (>=2.20.0,<3.0.0)",
@@ -49,7 +49,7 @@ name = "prowler-api"
package-mode = false
# Needed for the SDK compatibility
requires-python = ">=3.11,<3.13"
version = "1.19.2"
version = "1.20.0"
[project.scripts]
celery = "src.backend.config.settings.celery"
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,14 @@
from dataclasses import dataclass, field
@dataclass
class AttackPathsQueryAttribution:
"""Source attribution for an Attack Path query."""
text: str
link: str
@dataclass
class AttackPathsQueryParameterDefinition:
"""
@@ -23,7 +31,9 @@ class AttackPathsQueryDefinition:
id: str
name: str
short_description: str
description: str
provider: str
cypher: str
attribution: AttackPathsQueryAttribution | None = None
parameters: list[AttackPathsQueryParameterDefinition] = field(default_factory=list)
+35 -3
View File
@@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: Prowler API
version: 1.19.2
version: 1.20.0
description: |-
Prowler API specification.
@@ -616,7 +616,7 @@ paths:
operationId: attack_paths_scans_queries_retrieve
description: Retrieve the catalog of Attack Paths queries available for this
Attack Paths scan.
summary: List attack paths queries
summary: List Attack Paths queries
parameters:
- in: query
name: fields[attack-paths-scans]
@@ -714,7 +714,7 @@ paths:
description: Bad request (e.g., Unknown Attack Paths query for the selected
provider)
'404':
description: No attack paths found for the given query and parameters
description: No Attack Paths found for the given query and parameters
'500':
description: Attack Paths query execution failed due to a database error
/api/v1/compliance-overviews:
@@ -12438,6 +12438,8 @@ components:
type: string
name:
type: string
short_description:
type: string
description:
type: string
provider:
@@ -12446,12 +12448,42 @@ components:
type: array
items:
$ref: '#/components/schemas/AttackPathsQueryParameter'
attribution:
allOf:
- $ref: '#/components/schemas/AttackPathsQueryAttribution'
nullable: true
required:
- id
- name
- short_description
- description
- provider
- parameters
AttackPathsQueryAttribution:
type: object
required:
- type
- id
additionalProperties: false
properties:
type:
type: string
description: The [type](https://jsonapi.org/format/#document-resource-object-identification)
member is used to describe resource objects that share common attributes
and relationships.
enum:
- attack-paths-query-attributions
id: {}
attributes:
type: object
properties:
text:
type: string
link:
type: string
required:
- text
- link
AttackPathsQueryParameter:
type: object
required:
@@ -83,6 +83,7 @@ def test_execute_attack_paths_query_serializes_graph(
definition = attack_paths_query_definition_factory(
id="aws-rds",
name="RDS",
short_description="Short desc",
description="",
cypher="MATCH (n) RETURN n",
parameters=[],
@@ -143,6 +144,7 @@ def test_execute_attack_paths_query_wraps_graph_errors(
definition = attack_paths_query_definition_factory(
id="aws-rds",
name="RDS",
short_description="Short desc",
description="",
cypher="MATCH (n) RETURN n",
parameters=[],
+3
View File
@@ -3830,6 +3830,7 @@ class TestAttackPathsScanViewSet:
AttackPathsQueryDefinition(
id="aws-rds",
name="RDS inventory",
short_description="List account RDS assets.",
description="List account RDS assets",
provider=provider.provider,
cypher="MATCH (n) RETURN n",
@@ -3892,6 +3893,7 @@ class TestAttackPathsScanViewSet:
query_definition = AttackPathsQueryDefinition(
id="aws-rds",
name="RDS inventory",
short_description="List account RDS assets.",
description="List account RDS assets",
provider=provider.provider,
cypher="MATCH (n) RETURN n",
@@ -4049,6 +4051,7 @@ class TestAttackPathsScanViewSet:
query_definition = AttackPathsQueryDefinition(
id="aws-empty",
name="empty",
short_description="",
description="",
provider=provider.provider,
cypher="MATCH (n) RETURN n",
+10
View File
@@ -1176,6 +1176,14 @@ class AttackPathsScanSerializer(RLSSerializer):
return provider.uid if provider else None
class AttackPathsQueryAttributionSerializer(BaseSerializerV1):
text = serializers.CharField()
link = serializers.CharField()
class JSONAPIMeta:
resource_name = "attack-paths-query-attributions"
class AttackPathsQueryParameterSerializer(BaseSerializerV1):
name = serializers.CharField()
label = serializers.CharField()
@@ -1190,7 +1198,9 @@ class AttackPathsQueryParameterSerializer(BaseSerializerV1):
class AttackPathsQuerySerializer(BaseSerializerV1):
id = serializers.CharField()
name = serializers.CharField()
short_description = serializers.CharField()
description = serializers.CharField()
attribution = AttackPathsQueryAttributionSerializer(allow_null=True, required=False)
provider = serializers.CharField()
parameters = AttackPathsQueryParameterSerializer(many=True)
+1 -1
View File
@@ -392,7 +392,7 @@ class SchemaView(SpectacularAPIView):
def get(self, request, *args, **kwargs):
spectacular_settings.TITLE = "Prowler API"
spectacular_settings.VERSION = "1.19.2"
spectacular_settings.VERSION = "1.20.0"
spectacular_settings.DESCRIPTION = (
"Prowler API specification.\n\nThis file is auto-generated."
)
+1
View File
@@ -1663,6 +1663,7 @@ def attack_paths_query_definition_factory():
definition_payload = {
"id": "aws-test",
"name": "Attack Paths Test Query",
"short_description": "Synthetic short description for tests.",
"description": "Synthetic Attack Paths definition for tests.",
"provider": "aws",
"cypher": "RETURN 1",
@@ -12,8 +12,10 @@ BATCH_SIZE = env.int("ATTACK_PATHS_BATCH_SIZE", 1000)
# Neo4j internal labels (Prowler-specific, not provider-specific)
# - `ProwlerFinding`: Label for finding nodes created by Prowler and linked to cloud resources.
# - `ProviderResource`: Added to ALL synced nodes for provider isolation and drop/query ops.
# - `Internet`: Singleton node representing external internet access for exposed-resource queries.
PROWLER_FINDING_LABEL = "ProwlerFinding"
PROVIDER_RESOURCE_LABEL = "ProviderResource"
INTERNET_NODE_LABEL = "Internet"
@dataclass(frozen=True)
@@ -6,6 +6,7 @@ from cartography.client.core.tx import run_write_query
from celery.utils.log import get_task_logger
from tasks.jobs.attack_paths.config import (
INTERNET_NODE_LABEL,
PROWLER_FINDING_LABEL,
PROVIDER_RESOURCE_LABEL,
)
@@ -30,6 +31,8 @@ FINDINGS_INDEX_STATEMENTS = [
f"CREATE INDEX prowler_finding_provider_uid IF NOT EXISTS FOR (n:{PROWLER_FINDING_LABEL}) ON (n.provider_uid);",
f"CREATE INDEX prowler_finding_lastupdated IF NOT EXISTS FOR (n:{PROWLER_FINDING_LABEL}) ON (n.lastupdated);",
f"CREATE INDEX prowler_finding_status IF NOT EXISTS FOR (n:{PROWLER_FINDING_LABEL}) ON (n.status);",
# Internet node index for MERGE lookups
f"CREATE INDEX internet_id IF NOT EXISTS FOR (n:{INTERNET_NODE_LABEL}) ON (n.id);",
]
# Indexes for provider resource sync operations
@@ -0,0 +1,67 @@
"""
Internet node enrichment for Attack Paths graph.
Creates a real Internet node and CAN_ACCESS relationships to
internet-exposed resources (EC2Instance, LoadBalancer, LoadBalancerV2)
in the temporary scan database before sync.
"""
import neo4j
from cartography.config import Config as CartographyConfig
from celery.utils.log import get_task_logger
from api.models import Provider
from prowler.config import config as ProwlerConfig
from tasks.jobs.attack_paths.config import get_root_node_label
from tasks.jobs.attack_paths.queries import (
CREATE_CAN_ACCESS_RELATIONSHIPS_TEMPLATE,
CREATE_INTERNET_NODE,
render_cypher_template,
)
logger = get_task_logger(__name__)
def analysis(
neo4j_session: neo4j.Session,
prowler_api_provider: Provider,
config: CartographyConfig,
) -> int:
"""
Create Internet node and CAN_ACCESS relationships to exposed resources.
Args:
neo4j_session: Active Neo4j session (temp database).
prowler_api_provider: The Prowler API provider instance.
config: Cartography configuration with update_tag.
Returns:
Number of CAN_ACCESS relationships created.
"""
provider_uid = str(prowler_api_provider.uid)
parameters = {
"provider_uid": provider_uid,
"last_updated": config.update_tag,
"prowler_version": ProwlerConfig.prowler_version,
}
logger.info(f"Creating Internet node for provider {provider_uid}")
neo4j_session.run(CREATE_INTERNET_NODE, parameters)
query = render_cypher_template(
CREATE_CAN_ACCESS_RELATIONSHIPS_TEMPLATE,
{"__ROOT_LABEL__": get_root_node_label(prowler_api_provider.provider)},
)
logger.info(
f"Creating CAN_ACCESS relationships from Internet to exposed resources for {provider_uid}"
)
result = neo4j_session.run(query, parameters)
relationships_merged = result.single().get("relationships_merged", 0)
logger.info(
f"Created {relationships_merged} CAN_ACCESS relationships for provider {provider_uid}"
)
return relationships_merged
@@ -1,5 +1,6 @@
# Cypher query templates for Attack Paths operations
from tasks.jobs.attack_paths.config import (
INTERNET_NODE_LABEL,
PROWLER_FINDING_LABEL,
PROVIDER_RESOURCE_LABEL,
)
@@ -91,6 +92,37 @@ CLEANUP_FINDINGS_TEMPLATE = f"""
RETURN COUNT(finding) AS deleted_findings_count
"""
# Internet queries (used by internet.py)
# ---------------------------------------
CREATE_INTERNET_NODE = f"""
MERGE (internet:{INTERNET_NODE_LABEL} {{id: 'Internet'}})
ON CREATE SET
internet.name = 'Internet',
internet.firstseen = timestamp(),
internet.lastupdated = $last_updated,
internet._module_name = 'cartography:prowler',
internet._module_version = $prowler_version
ON MATCH SET
internet.lastupdated = $last_updated
"""
CREATE_CAN_ACCESS_RELATIONSHIPS_TEMPLATE = f"""
MATCH (account:__ROOT_LABEL__ {{id: $provider_uid}})-->(resource)
WHERE resource.exposed_internet = true
WITH resource
MATCH (internet:{INTERNET_NODE_LABEL} {{id: 'Internet'}})
MERGE (internet)-[r:CAN_ACCESS]->(resource)
ON CREATE SET
r.firstseen = timestamp(),
r.lastupdated = $last_updated,
r._module_name = 'cartography:prowler',
r._module_version = $prowler_version
ON MATCH SET
r.lastupdated = $last_updated
RETURN COUNT(r) AS relationships_merged
"""
# Sync queries (used by sync.py)
# -------------------------------
@@ -16,7 +16,7 @@ from api.models import (
StateChoices,
)
from api.utils import initialize_prowler_provider
from tasks.jobs.attack_paths import db_utils, findings, sync, utils
from tasks.jobs.attack_paths import db_utils, findings, internet, sync, utils
from tasks.jobs.attack_paths.config import get_cartography_ingestion_function
# Without this Celery goes crazy with Cartography logging
@@ -135,7 +135,15 @@ def run(tenant_id: str, scan_id: str, task_id: str) -> dict[str, Any]:
cartography_analysis.run(tmp_neo4j_session, tmp_cartography_config)
db_utils.update_attack_paths_scan_progress(attack_paths_scan, 96)
# Adding Prowler nodes and relationships
# Creating Internet node and CAN_ACCESS relationships
logger.info(
f"Creating Internet graph for AWS account {prowler_api_provider.uid}"
)
internet.analysis(
tmp_neo4j_session, prowler_api_provider, tmp_cartography_config
)
# Adding Prowler Finding nodes and relationships
logger.info(
f"Syncing Prowler analysis for AWS account {prowler_api_provider.uid}"
)
@@ -4,6 +4,7 @@ from unittest.mock import MagicMock, call, patch
import pytest
from tasks.jobs.attack_paths import findings as findings_module
from tasks.jobs.attack_paths import internet as internet_module
from tasks.jobs.attack_paths.scan import run as attack_paths_run
from api.models import (
@@ -37,6 +38,7 @@ class TestAttackPathsRun:
@patch("tasks.jobs.attack_paths.scan.sync.sync_graph")
@patch("tasks.jobs.attack_paths.scan.graph_database.drop_subgraph")
@patch("tasks.jobs.attack_paths.scan.sync.create_sync_indexes")
@patch("tasks.jobs.attack_paths.scan.internet.analysis")
@patch("tasks.jobs.attack_paths.scan.findings.analysis")
@patch("tasks.jobs.attack_paths.scan.findings.create_findings_indexes")
@patch("tasks.jobs.attack_paths.scan.cartography_ontology.run")
@@ -67,6 +69,7 @@ class TestAttackPathsRun:
mock_cartography_ontology,
mock_findings_indexes,
mock_findings_analysis,
mock_internet_analysis,
mock_sync_indexes,
mock_drop_subgraph,
mock_sync,
@@ -139,6 +142,7 @@ class TestAttackPathsRun:
# These use tmp_cartography_config (neo4j_database="db-scan-id")
mock_cartography_analysis.assert_called_once()
mock_cartography_ontology.assert_called_once()
mock_internet_analysis.assert_called_once()
mock_findings_analysis.assert_called_once()
mock_drop_subgraph.assert_called_once_with(
database="tenant-db",
@@ -207,6 +211,7 @@ class TestAttackPathsRun:
patch("tasks.jobs.attack_paths.scan.cartography_create_indexes.run"),
patch("tasks.jobs.attack_paths.scan.cartography_analysis.run"),
patch("tasks.jobs.attack_paths.scan.findings.create_findings_indexes"),
patch("tasks.jobs.attack_paths.scan.internet.analysis"),
patch("tasks.jobs.attack_paths.scan.findings.analysis"),
patch(
"tasks.jobs.attack_paths.scan.db_utils.retrieve_attack_paths_scan",
@@ -757,3 +762,45 @@ class TestAttackPathsFindingsHelpers:
findings_module.load_findings(mock_session, empty_gen(), provider, config)
mock_session.run.assert_not_called()
class TestInternetAnalysis:
def _make_provider_and_config(self):
provider = MagicMock()
provider.provider = "aws"
provider.uid = "123456789012"
config = SimpleNamespace(update_tag=1234567890)
return provider, config
def test_analysis_creates_node_and_relationships(self):
"""Verify both Cypher statements are executed and relationship count returned."""
mock_session = MagicMock()
mock_result = MagicMock()
mock_result.single.return_value = {"relationships_merged": 3}
mock_session.run.side_effect = [None, mock_result]
provider, config = self._make_provider_and_config()
with patch(
"tasks.jobs.attack_paths.internet.get_root_node_label",
return_value="AWSAccount",
):
result = internet_module.analysis(mock_session, provider, config)
assert mock_session.run.call_count == 2
assert result == 3
def test_analysis_zero_exposed_resources(self):
"""When no resources are exposed, zero relationships are created."""
mock_session = MagicMock()
mock_result = MagicMock()
mock_result.single.return_value = {"relationships_merged": 0}
mock_session.run.side_effect = [None, mock_result]
provider, config = self._make_provider_and_config()
with patch(
"tasks.jobs.attack_paths.internet.get_root_node_label",
return_value="AWSAccount",
):
result = internet_module.analysis(mock_session, provider, config)
assert result == 0
+24
View File
@@ -0,0 +1,24 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
examples
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
+12
View File
@@ -0,0 +1,12 @@
dependencies:
- name: postgresql
repository: oci://registry-1.docker.io/bitnamicharts
version: 18.2.0
- name: valkey
repository: https://valkey.io/valkey-helm/
version: 0.9.3
- name: neo4j
repository: https://helm.neo4j.com/neo4j
version: 2025.12.1
digest: sha256:da19233c6832727345fcdb314d683d30aa347d349f270023f3a67149bffb009b
generated: "2026-01-26T12:00:06.798702+02:00"
+33
View File
@@ -0,0 +1,33 @@
apiVersion: v2
name: prowler
description: Prowler is an Open Cloud Security tool for AWS, Azure, GCP and Kubernetes. It helps for continuous monitoring, security assessments and audits, incident response, compliance, hardening and forensics readiness.
type: application
version: 0.0.1
appVersion: "5.17.0"
home: https://prowler.com
icon: https://cdn.prod.website-files.com/68c4ec3f9fb7b154fbcb6e36/68c5e0fea5d0059b9e05834b_Link.png
keywords:
- security
- aws
- azure
- gcp
- kubernetes
maintainers:
- name: Mihai
email: mihai.legat@gmail.com
dependencies:
# https://artifacthub.io/packages/helm/bitnami/postgresql
- name: postgresql
version: 18.2.0
repository: oci://registry-1.docker.io/bitnamicharts
condition: postgresql.enabled
# https://valkey.io/valkey-helm/
- name: valkey
version: 0.9.3
repository: https://valkey.io/valkey-helm/
condition: valkey.enabled
# https://helm.neo4j.com/neo4j
- name: neo4j
version: 2025.12.1
repository: https://helm.neo4j.com/neo4j
condition: neo4j.enabled
+143
View File
@@ -0,0 +1,143 @@
<!--
This README is the one shown on Artifact Hub.
Images should use absolute URLs.
-->
# Prowler App Helm Chart
![Version: 0.0.1](https://img.shields.io/badge/Version-0.0.1-informational?style=flat-square)
![AppVersion: 5.17.0](https://img.shields.io/badge/AppVersion-5.17.0-informational?style=flat-square)
Prowler is an Open Cloud Security tool for AWS, Azure, GCP and Kubernetes. It helps for continuous monitoring, security assessments and audits, incident response, compliance, hardening and forensics readiness. Includes CIS, NIST 800, NIST CSF, CISA, FedRAMP, PCI-DSS, GDPR, HIPAA, FFIEC, SOC2, GXP, Well-Architected Security, ENS and more.
## Architecture
The Prowler App consists of three main components:
- **Prowler UI**: A user-friendly web interface for running Prowler and viewing results, powered by Next.js.
- **Prowler API**: The backend API that executes Prowler scans and stores the results, built with Django REST Framework.
- **Prowler SDK**: A Python SDK that integrates with the Prowler CLI for advanced functionality.
The app leverages the following supporting infrastructure:
- **PostgreSQL**: Used for persistent storage of scan results.
- **Celery Workers**: Facilitate asynchronous execution of Prowler scans.
- **Valkey**: An in-memory database serving as a message broker for the Celery workers.
- **Neo4j**: Graph Database
- **Keda**: Kubernetes Event-driven Autoscaling (Keda) automatically scales the number of Celery worker pods based on the workload, ensuring efficient resource utilization and responsiveness.
## Setup
This guide walks you through installing Prowler App using Helm. For a minimal installation example, see the [minimal installation example](./examples/minimal-installation/).
### Prerequisites
- Kubernetes cluster (1.24+)
- Helm 3.x installed
- `kubectl` configured to access your cluster
- Access to the Prowler Helm chart repository (or local chart)
### Step 1: Create Required Secrets
Before installing the Helm chart, you must create a Kubernetes Secret containing the required authentication keys and secrets.
1. **Generate the required keys and secrets:**
```bash
# Generate Django token signing key (private key)
openssl genrsa -out private.pem 2048
# Generate Django token verifying key (public key)
openssl rsa -in private.pem -pubout -out public.pem
# Generate Django secrets encryption key
openssl rand -base64 32
# Generate Auth secret
openssl rand -base64 32
```
2. **Create the secret file:**
Create a file named `secrets.yaml` with the following structure:
```yaml
apiVersion: v1
kind: Secret
type: Opaque
metadata:
name: prowler-secret
stringData:
DJANGO_TOKEN_SIGNING_KEY: |
-----BEGIN PRIVATE KEY-----
[paste your private key here]
-----END PRIVATE KEY-----
DJANGO_TOKEN_VERIFYING_KEY: |
-----BEGIN PUBLIC KEY-----
[paste your public key here]
-----END PUBLIC KEY-----
DJANGO_SECRETS_ENCRYPTION_KEY: "[paste your encryption key here]"
AUTH_SECRET: "[paste your auth secret here]"
NEO4J_PASSWORD: "[prowler-password]"
NEO4J_AUTH: "neo4j/[prowler-password]"
```
> **Note:** You can use the [example secrets file](./examples/minimal-installation/secrets.yaml) as a template, but **always replace the placeholder values with your own secure keys** before applying.
3. **Apply the secret to your cluster:**
```bash
kubectl apply -f secrets.yaml
```
### Step 2: Configure Values
Create a `values.yaml` file to customize your installation. At minimum, you need to configure the UI access method.
**Option A: Using Ingress (Recommended for production)**
```yaml
ui:
ingress:
enabled: true
hosts:
- host: prowler.example.com
paths:
- path: /
pathType: ImplementationSpecific
```
**Option B: Using authUrl (For proxy setups)**
```yaml
ui:
authUrl: prowler.example.com
```
> **Note:** See the [minimal installation example](./examples/minimal-installation/values.yaml) for a complete reference.
### Step 3: Install the Chart
Install Prowler App using Helm:
```bash
helm dependency update
helm install prowler prowler/prowler-app -f values.yaml
```
### Using Existing PostgreSQL and Valkey Instances
By default, this Chart uses Bitnami's Charts to deploy [PostgreSQL](https://artifacthub.io/packages/helm/bitnami/postgresql), [Neo4j](https://helm.neo4j.com/neo4j) and [Valkey official helm chart](https://valkey.io/valkey-helm/). **Note:** This default setup is not production-ready.
To connect to existing PostgreSQL, Neo4j and Valkey instances:
1. Create a `Secret` containing the correct database and message broker credentials
2. Reference the secret in the [values.yaml](values.yaml) file api->secrets list
## Contributing
Feel free to contact the maintainer of this repository for any questions or concerns. Contributions are encouraged and appreciated.
@@ -0,0 +1,46 @@
# Minimal Installation Example
This example demonstrates a minimal installation of Prowler in a Kubernetes cluster.
## Installation
To install Prowler using this example:
1. First, create the required secret:
```bash
# Edit secret.yaml and set secure values before applying
kubectl apply -f secret.yaml
```
1. Install the chart using the base values file:
```bash
# Basic installation
helm install prowler prowler/prowler-app -f values.yaml
```
## Configuration
The example contains the following configuration files:
### `secret.yaml`
Contains all required secrets for the Prowler installation. **Must be applied before installing the Helm chart**. Make sure to replace all placeholder values with secure values before applying.
### `values.yaml`
```yaml
ui:
# Note: You should set either `authUrl` if you use prowler behind a proxy or enable `ingress`.
# Example with authUrl:
# authUrl: example.prowler.com
# Example with ingress:
ingress:
enabled: true
hosts:
- host: example.prowler.com
paths:
- path: /
pathType: ImplementationSpecific
```
Make sure to adjust the hostname in the values file to match your environment before installing.
@@ -0,0 +1,58 @@
apiVersion: v1
kind: Secret
type: Opaque
metadata:
name: prowler-secret
stringData:
# openssl genrsa -out private.pem 2048
DJANGO_TOKEN_SIGNING_KEY: |
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCIro0QiLAxw7rF
GO0NgAWJfkpYE5ysMGDCbId07HUrv+/SCoRjqKVzGJVIvmNP5oByzSehPgswW9v3
3dqe2r9sCS1JyMa+XO3qfZCR0uRDcPCwZjIyr0QQLpWAymdBa8baeHsU1/3Orjcb
Vrr+lNx4HQJOiSn094iXPReW/25hYeq/SXs79V2CR87PGdoZAhb8IllAxJgdfkeB
/iWohY/1vfRTmIuMweWGXk0aKzPsBdvE/DqG4HjiNVEPh18G3vid0YTZNmm7u8vO
Cue3x9NQWGHA4QtxNtLtxlHcOEryqZ9ChO2nC+ew0Xl/v706XFNyLFicjisIKNQo
qdkaMS33AgMBAAECggEAGdJIChCYoL4mYafk2MEPyrrWFq+V0J3PGcvhB0DInfxD
tT2RZzZsE0NYqIZ3Qpf8OjPxwa9z863W74u1Cn+u3B0bti29BieONteD4VijEO6c
OecEorijth7m1Y7nVN+kkI9kSTrI0yvsczi+WOwMfpCUZ/vXtlSxNEkxVLBqzPCo
9VxAFIjgWOj2rpw8nxPedves36PUrC5ghLqrOTe1jmw/Di0++47AXG+DsTXc00sc
5+oybopm3Kimsxrqbf9s8SZf2A8NiwqcbLj8OtP2j2g4TCEgZYLD5Zmt+JN/wN4B
WsQG/Hwp4KPPm9QTHEpuuoPFP1CZWZeq8gPcV4apYQKBgQC+TuXjJCYhZqNIttTZ
z/i3hkKUEKQLkzTZnXaDzL5wHyEMVqM2E/WkilO0C9ZZwh0ENPzkp+JsHf7LEhHy
wSHOti81VzUCjN/YpCBKlOlClqSiDlOonImrobLei8xgvmA0VmGtirCXZyyzZUoV
OyPr17WpK6G/M5piX59MvKQg0QKBgQC33NBoQFD8A6FjrTopYmWfK099k9uQh9NE
bvUYsNAPunSDslmc/0PPHQC7fRX5Ime2BinXAN1PYtB/Fsu3jv/+FCUM5hVil0Dd
KBvt13+RYSCJKlhcGP1EkWoIg1F2XXBOZKJrC8VQ+Vyl2t06UcWQqy5M9J4VZaqI
fruOLU/URwKBgE55GjJfZZnASPRi78IhD94dbra/ZeWf/dr+IzCV7LEvJOGBmCtk
b5Y5s+o6N1krwetKLj3bPHJ4q+fwu5XuLZKfbTgBjcpPbL5YbzhRzx22IIzye2y7
n8k2FBvQaaY62lC6jeyRk9/am4Qd8D5w9I77k9z+MOQ20yJda8KoxsUBAoGBAIQ9
5QPmppjsf4ry0C9t30uhWhYnX7fPiYviBpVQrwVxBVan076Q9xOjd6BicohzT4bj
XfqPW546o12VZsbKqqLzmEZzwpPb2EJ5E8V4xv8ojb86Xr03GArWUB55XQE2aY1o
4kz99VitUg7UoWPN5ryL8sxU8NLRAdwU0w+K1a0HAoGAZaU7O94u9IIPZ6Ohobs2
Vjf/eV0brCKgX61b4z/YhuJdZsyTujhBZUihZwqR696kiFKuzmHx1ghE2ITvnPVN
q0iHxRZzBCnRQ+mQlS0trzphaCP0NVy3osFeAD9mJfnOnSmkU0ua4F81mkvke1eN
6nnaoAdy2lmMr96/Tye2ty4=
-----END PRIVATE KEY-----
# openssl rsa -in private.pem -pubout -out public.pem
DJANGO_TOKEN_VERIFYING_KEY: |
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiK6NEIiwMcO6xRjtDYAF
iX5KWBOcrDBgwmyHdOx1K7/v0gqEY6ilcxiVSL5jT+aAcs0noT4LMFvb993antq/
bAktScjGvlzt6n2QkdLkQ3DwsGYyMq9EEC6VgMpnQWvG2nh7FNf9zq43G1a6/pTc
eB0CTokp9PeIlz0Xlv9uYWHqv0l7O/VdgkfOzxnaGQIW/CJZQMSYHX5Hgf4lqIWP
9b30U5iLjMHlhl5NGisz7AXbxPw6huB44jVRD4dfBt74ndGE2TZpu7vLzgrnt8fT
UFhhwOELcTbS7cZR3DhK8qmfQoTtpwvnsNF5f7+9OlxTcixYnI4rCCjUKKnZGjEt
9wIDAQAB
-----END PUBLIC KEY-----
# openssl rand -base64 32
DJANGO_SECRETS_ENCRYPTION_KEY: "qYAIWnRK52aBT5YQkBoMEw08j7j3+QIPZXS6+A8Su44="
# openssl rand -base64 32
AUTH_SECRET: "CM9w3Nco2P1RdHaYmD+fmy2nJmSofusdHd4g7Z4KDG4="
# Unfortunatelly, we need to duplicate the password in two different keys because the Neo4j Helm Chart expects the password in the NEO4J_AUTH key and the application expects it in the NEO4J_PASSWORD key.
NEO4J_PASSWORD: "prowler-password-fake"
NEO4J_AUTH: "neo4j/prowler-password-fake"
@@ -0,0 +1,11 @@
ui:
ingress:
enabled: true
hosts:
- host: 127.0.0.1.nip.io
paths:
- path: /
pathType: ImplementationSpecific
# or use authUrl if you use prowler behind a proxy
# authUrl: 127.0.0.1.nip.io
@@ -0,0 +1,134 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "prowler.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "prowler.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "prowler.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "prowler.labels" -}}
helm.sh/chart: {{ include "prowler.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Django environment variables for api, worker, and worker_beat.
*/}}
{{- define "prowler.django.env" -}}
- name: DJANGO_TOKEN_SIGNING_KEY
valueFrom:
secretKeyRef:
name: {{ .Values.djangoTokenSigningKey.secretKeyRef.name }}
key: {{ .Values.djangoTokenSigningKey.secretKeyRef.key }}
- name: DJANGO_TOKEN_VERIFYING_KEY
valueFrom:
secretKeyRef:
name: {{ .Values.djangoTokenVerifyingKey.secretKeyRef.name }}
key: {{ .Values.djangoTokenVerifyingKey.secretKeyRef.key }}
- name: DJANGO_SECRETS_ENCRYPTION_KEY
valueFrom:
secretKeyRef:
name: {{ .Values.djangoSecretsEncryptionKey.secretKeyRef.name }}
key: {{ .Values.djangoSecretsEncryptionKey.secretKeyRef.key }}
{{- end }}
{{/*
PostgreSQL environment variables for api, worker, and worker_beat.
Outputs nothing when postgresql.enabled is false.
*/}}
{{- define "prowler.postgresql.env" -}}
{{- if .Values.postgresql.enabled }}
{{- if .Values.postgresql.auth.username }}
- name: POSTGRES_USER
value: {{ .Values.postgresql.auth.username | quote }}
{{- end }}
- name: POSTGRES_PASSWORD
{{- if .Values.postgresql.auth.existingSecret }}
valueFrom:
secretKeyRef:
name: {{ .Values.postgresql.auth.existingSecret }}
key: {{ required "postgresql.auth.secretKeys.userPasswordKey is required when using an existing secret" .Values.postgresql.auth.secretKeys.userPasswordKey }}
{{- else if .Values.postgresql.auth.password }}
value: {{ .Values.postgresql.auth.password | quote }}
{{- else }}
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-postgresql
key: password
{{- end }}
- name: POSTGRES_DB
value: {{ .Values.postgresql.auth.database | quote }}
- name: POSTGRES_HOST
value: {{ .Release.Name }}-postgresql
- name: POSTGRES_PORT
value: "5432"
- name: POSTGRES_ADMIN_USER
value: postgres
- name: POSTGRES_ADMIN_PASSWORD
{{- if .Values.postgresql.auth.existingSecret }}
valueFrom:
secretKeyRef:
name: {{ .Values.postgresql.auth.existingSecret }}
key: {{ required "postgresql.auth.secretKeys.adminPasswordKey is required when using an existing secret" .Values.postgresql.auth.secretKeys.adminPasswordKey }}
{{- else if .Values.postgresql.auth.postgresPassword }}
value: {{ .Values.postgresql.auth.postgresPassword | quote }}
{{- else }}
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-postgresql
key: postgres-password
{{- end }}
{{- end }}
{{- end }}
{{/*
Neo4j environment variables for api, worker, and worker_beat.
Outputs nothing when neo4j.enabled is false.
*/}}
{{- define "prowler.neo4j.env" -}}
{{- if .Values.neo4j.enabled }}
- name: NEO4J_HOST
value: {{ .Release.Name }}
- name: NEO4J_PORT
value: "7687"
- name: NEO4J_USER
value: "neo4j"
- name: NEO4J_PASSWORD
valueFrom:
secretKeyRef:
name: {{ required "neo4j.neo4j.passwordFromSecret is required" .Values.neo4j.neo4j.passwordFromSecret }}
key: NEO4J_PASSWORD
{{- end }}
{{- end }}
@@ -0,0 +1,10 @@
{{/*
Create the name of the service account to use
*/}}
{{- define "prowler.api.serviceAccountName" -}}
{{- if .Values.api.serviceAccount.create }}
{{- default (printf "%s-%s" (include "prowler.fullname" .) "api") .Values.api.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.api.serviceAccount.name }}
{{- end }}
{{- end }}
@@ -0,0 +1,10 @@
kind: ConfigMap
apiVersion: v1
metadata:
name: {{ include "prowler.fullname" . }}-api
labels:
{{- include "prowler.labels" . | nindent 4 }}
data:
{{- range $key, $value := .Values.api.djangoConfig }}
{{ $key }}: {{ $value | quote }}
{{- end }}
@@ -0,0 +1,105 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "prowler.fullname" . }}-api
labels:
{{- include "prowler.labels" . | nindent 4 }}
spec:
{{- if not .Values.api.autoscaling.enabled }}
replicas: {{ .Values.api.replicaCount }}
{{- end }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "prowler.fullname" . }}-api
template:
metadata:
annotations:
secret-hash: "{{ printf "%s%s%s" (.Files.Get "templates/api/configmap.yaml" | sha256sum) (.Files.Get "templates/api/secret-valkey.yaml" | sha256sum) | sha256sum }}"
{{- with .Values.api.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "prowler.labels" . | nindent 8 }}
app.kubernetes.io/name: {{ include "prowler.fullname" . }}-api
{{- with .Values.api.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.api.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "prowler.api.serviceAccountName" . }}
{{- with .Values.api.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: api
{{- with .Values.api.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
image: "{{ .Values.api.image.repository }}:{{ .Values.api.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.api.image.pullPolicy }}
{{- with .Values.api.command }}
command:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.api.args }}
args:
{{- toYaml . | nindent 12 }}
{{- end }}
ports:
- name: http
containerPort: {{ .Values.api.service.port }}
protocol: TCP
envFrom:
- configMapRef:
name: {{ include "prowler.fullname" . }}-api
{{- if .Values.valkey.enabled }}
- secretRef:
name: {{ include "prowler.fullname" . }}-api-valkey
{{- end }}
{{- with .Values.api.secrets }}
{{- range $index, $secret := . }}
- secretRef:
name: {{ $secret }}
{{- end }}
{{- end }}
env:
{{- include "prowler.django.env" . | nindent 12 }}
{{- include "prowler.postgresql.env" . | nindent 12 }}
{{- include "prowler.neo4j.env" . | nindent 12 }}
{{- with .Values.api.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.api.readinessProbe }}
readinessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.api.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.api.volumeMounts }}
volumeMounts:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.api.volumes }}
volumes:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.api.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.api.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.api.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
@@ -0,0 +1,32 @@
{{- if .Values.api.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "prowler.fullname" . }}-api
labels:
{{- include "prowler.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "prowler.fullname" . }}-api
minReplicas: {{ .Values.api.autoscaling.minReplicas }}
maxReplicas: {{ .Values.api.autoscaling.maxReplicas }}
metrics:
{{- if .Values.api.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.api.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.api.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.api.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}
@@ -0,0 +1,43 @@
{{- if .Values.api.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "prowler.fullname" . }}-api
labels:
{{- include "prowler.labels" . | nindent 4 }}
{{- with .Values.api.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- with .Values.api.ingress.className }}
ingressClassName: {{ . }}
{{- end }}
{{- if .Values.api.ingress.tls }}
tls:
{{- range .Values.api.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.api.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
{{- with .pathType }}
pathType: {{ . }}
{{- end }}
backend:
service:
name: {{ include "prowler.fullname" $ }}-api
port:
number: {{ $.Values.api.service.port }}
{{- end }}
{{- end }}
{{- end }}
@@ -0,0 +1,29 @@
# https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/prowler-app/#step-44-kubernetes-credentials
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ include "prowler.fullname" . }}-api
labels:
{{- include "prowler.labels" . | nindent 4 }}
rules:
- apiGroups: [""]
resources: ["pods", "configmaps", "nodes", "namespaces"]
verbs: ["get", "list", "watch"]
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["clusterrolebindings", "rolebindings", "clusterroles", "roles"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ include "prowler.fullname" . }}-api
labels:
{{- include "prowler.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ include "prowler.fullname" . }}-api
subjects:
- kind: ServiceAccount
name: {{ include "prowler.api.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
@@ -0,0 +1,13 @@
{{- if .Values.valkey.enabled -}}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "prowler.fullname" . }}-api-valkey
labels:
{{- include "prowler.labels" . | nindent 4 }}
type: Opaque
stringData:
VALKEY_HOST: "{{ include "prowler.fullname" . }}-valkey"
VALKEY_PORT: "6379"
VALKEY_DB: "0"
{{- end -}}
@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "prowler.fullname" . }}-api
labels:
{{- include "prowler.labels" . | nindent 4 }}
spec:
type: {{ .Values.api.service.type }}
ports:
- port: {{ .Values.api.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
app.kubernetes.io/name: {{ include "prowler.fullname" . }}-api
@@ -0,0 +1,13 @@
{{- if .Values.api.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "prowler.api.serviceAccountName" . }}
labels:
{{- include "prowler.labels" . | nindent 4 }}
{{- with .Values.api.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
automountServiceAccountToken: {{ .Values.api.serviceAccount.automount }}
{{- end }}
@@ -0,0 +1,10 @@
{{/*
Create the name of the service account to use
*/}}
{{- define "prowler.ui.serviceAccountName" -}}
{{- if .Values.ui.serviceAccount.create }}
{{- default (printf "%s-%s" (include "prowler.fullname" .) "ui") .Values.ui.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.ui.serviceAccount.name }}
{{- end }}
{{- end }}
@@ -0,0 +1,18 @@
kind: ConfigMap
apiVersion: v1
metadata:
name: {{ include "prowler.fullname" . }}-ui
data:
PROWLER_UI_VERSION: "stable"
{{- if .Values.ui.ingress.enabled }}
{{- with (first .Values.ui.ingress.hosts) }}
AUTH_URL: "https://{{ .host }}"
{{- end }}
{{- else }}
AUTH_URL: {{ .Values.ui.authUrl | quote }}
{{- end }}
API_BASE_URL: "http://{{ include "prowler.fullname" . }}-api:{{ .Values.api.service.port }}/api/v1"
NEXT_PUBLIC_API_BASE_URL: "http://{{ include "prowler.fullname" . }}-api:{{ .Values.api.service.port }}/api/v1"
NEXT_PUBLIC_API_DOCS_URL: "http://{{ include "prowler.fullname" . }}-api:{{ .Values.api.service.port }}/api/v1/docs"
AUTH_TRUST_HOST: "true"
UI_PORT: {{ .Values.ui.service.port | quote }}
@@ -0,0 +1,95 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "prowler.fullname" . }}-ui
labels:
{{- include "prowler.labels" . | nindent 4 }}
spec:
{{- if not .Values.ui.autoscaling.enabled }}
replicas: {{ .Values.ui.replicaCount }}
{{- end }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "prowler.fullname" . }}-ui
template:
metadata:
annotations:
secret-hash: {{ .Files.Get "templates/ui/configmap.yaml" | sha256sum }}
{{- with .Values.ui.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "prowler.labels" . | nindent 8 }}
app.kubernetes.io/name: {{ include "prowler.fullname" . }}-ui
{{- with .Values.ui.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.ui.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "prowler.ui.serviceAccountName" . }}
{{- with .Values.ui.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: ui
{{- with .Values.ui.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
image: "{{ .Values.ui.image.repository }}:{{ .Values.ui.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.ui.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.ui.service.port }}
protocol: TCP
env:
- name: AUTH_SECRET
valueFrom:
secretKeyRef:
name: {{ .Values.ui.authSecret.secretKeyRef.name }}
key: {{ .Values.ui.authSecret.secretKeyRef.key }}
envFrom:
- configMapRef:
name: {{ include "prowler.fullname" . }}-ui
{{- with .Values.ui.secrets }}
{{- range $index, $secret := . }}
- secretRef:
name: {{ $secret }}
{{- end }}
{{- end }}
{{- with .Values.ui.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.ui.readinessProbe }}
readinessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.ui.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.ui.volumeMounts }}
volumeMounts:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.ui.volumes }}
volumes:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.ui.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.ui.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.ui.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
@@ -0,0 +1,32 @@
{{- if .Values.ui.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "prowler.fullname" . }}-ui
labels:
{{- include "prowler.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "prowler.fullname" . }}-ui
minReplicas: {{ .Values.ui.autoscaling.minReplicas }}
maxReplicas: {{ .Values.ui.autoscaling.maxReplicas }}
metrics:
{{- if .Values.ui.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.ui.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.ui.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.ui.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}
@@ -0,0 +1,43 @@
{{- if .Values.ui.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "prowler.fullname" . }}-ui
labels:
{{- include "prowler.labels" . | nindent 4 }}
{{- with .Values.ui.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- with .Values.ui.ingress.className }}
ingressClassName: {{ . }}
{{- end }}
{{- if .Values.ui.ingress.tls }}
tls:
{{- range .Values.ui.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ui.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
{{- with .pathType }}
pathType: {{ . }}
{{- end }}
backend:
service:
name: {{ include "prowler.fullname" $ }}-ui
port:
number: {{ $.Values.ui.service.port }}
{{- end }}
{{- end }}
{{- end }}
@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "prowler.fullname" . }}-ui
labels:
{{- include "prowler.labels" . | nindent 4 }}
spec:
type: {{ .Values.ui.service.type }}
ports:
- port: {{ .Values.ui.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
app.kubernetes.io/name: {{ include "prowler.fullname" . }}-ui
@@ -0,0 +1,13 @@
{{- if .Values.ui.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "prowler.ui.serviceAccountName" . }}
labels:
{{- include "prowler.labels" . | nindent 4 }}
{{- with .Values.ui.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
automountServiceAccountToken: {{ .Values.ui.serviceAccount.automount }}
{{- end }}
@@ -0,0 +1,10 @@
{{/*
Create the name of the service account to use
*/}}
{{- define "prowler.worker.serviceAccountName" -}}
{{- if .Values.worker.serviceAccount.create }}
{{- default (printf "%s-%s" (include "prowler.fullname" .) "worker") .Values.worker.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.worker.serviceAccount.name }}
{{- end }}
{{- end }}
@@ -0,0 +1,101 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "prowler.fullname" . }}-worker
labels:
{{- include "prowler.labels" . | nindent 4 }}
spec:
{{- if not .Values.worker.autoscaling.enabled }}
replicas: {{ .Values.worker.replicaCount }}
{{- end }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "prowler.fullname" . }}-worker
template:
metadata:
annotations:
secret-hash: "{{ printf "%s%s%s" (.Files.Get "templates/api/configmap.yaml" | sha256sum) (.Files.Get "templates/api/secret-valkey.yaml" | sha256sum) | sha256sum }}"
{{- with .Values.worker.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "prowler.labels" . | nindent 8 }}
app.kubernetes.io/name: {{ include "prowler.fullname" . }}-worker
{{- with .Values.worker.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.worker.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "prowler.worker.serviceAccountName" . }}
{{- with .Values.worker.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: worker
{{- with .Values.worker.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
image: "{{ .Values.worker.image.repository }}:{{ .Values.worker.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.worker.image.pullPolicy }}
{{- with .Values.worker.command }}
command:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.worker.args }}
args:
{{- toYaml . | nindent 12 }}
{{- end }}
envFrom:
- configMapRef:
name: {{ include "prowler.fullname" . }}-api
{{- if .Values.valkey.enabled }}
- secretRef:
name: {{ include "prowler.fullname" . }}-api-valkey
{{- end }}
{{- with .Values.api.secrets }}
{{- range $index, $secret := . }}
- secretRef:
name: {{ $secret }}
{{- end }}
{{- end }}
env:
{{- include "prowler.django.env" . | nindent 12 }}
{{- include "prowler.postgresql.env" . | nindent 12 }}
{{- include "prowler.neo4j.env" . | nindent 12 }}
{{- with .Values.worker.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.worker.readinessProbe }}
readinessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.worker.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.worker.volumeMounts }}
volumeMounts:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.worker.volumes }}
volumes:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.worker.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.worker.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.worker.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
@@ -0,0 +1,32 @@
{{- if .Values.worker.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "prowler.fullname" . }}-worker
labels:
{{- include "prowler.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "prowler.fullname" . }}-worker
minReplicas: {{ .Values.worker.autoscaling.minReplicas }}
maxReplicas: {{ .Values.worker.autoscaling.maxReplicas }}
metrics:
{{- if .Values.worker.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.worker.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.worker.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.worker.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}
@@ -0,0 +1,32 @@
{{- if .Values.worker.keda.enabled }}
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: {{ include "prowler.fullname" . }}-worker
namespace: {{ $.Release.Namespace }}
labels:
{{- include "prowler.labels" . | nindent 4 }}
spec:
scaleTargetRef:
name: {{ include "prowler.fullname" . }}-worker
envSourceContainerName: worker
kind: Deployment
minReplicaCount: {{ .Values.worker.keda.minReplicas }}
maxReplicaCount: {{ .Values.worker.keda.maxReplicas }}
pollingInterval: {{ .Values.worker.keda.pollingInterval }}
cooldownPeriod: {{ .Values.worker.keda.cooldownPeriod }}
triggers:
- type: {{ .Values.worker.keda.triggerType }}
metadata:
userName: "postgres"
passwordFromEnv: POSTGRES_ADMIN_PASSWORD
host: {{ .Release.Name }}-postgresql
port: {{ .Values.postgresql.port | quote }}
dbName: {{ .Values.postgresql.auth.database | quote }}
sslmode: disable
# Query for KEDA to count the number of scans that are in executing, available, or scheduled states,
# where the scheduled time is within the last 2 hours and is before NOW(). Used for scaling workers.
query: >-
SELECT COUNT(*) FROM scans WHERE ((state='executing' OR state='available' OR state='scheduled') and scheduled_at < NOW() and scheduled_at > NOW() - INTERVAL '2 hours')
targetQueryValue: "1"
{{- end }}
@@ -0,0 +1,13 @@
{{- if .Values.worker.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "prowler.worker.serviceAccountName" . }}
labels:
{{- include "prowler.labels" . | nindent 4 }}
{{- with .Values.worker.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
automountServiceAccountToken: {{ .Values.worker.serviceAccount.automount }}
{{- end }}
@@ -0,0 +1,10 @@
{{/*
Create the name of the service account to use
*/}}
{{- define "prowler.worker_beat.serviceAccountName" -}}
{{- if .Values.worker_beat.serviceAccount.create }}
{{- default (printf "%s-%s" (include "prowler.fullname" .) "worker-beat") .Values.worker_beat.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.worker_beat.serviceAccount.name }}
{{- end }}
{{- end }}
@@ -0,0 +1,99 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "prowler.fullname" . }}-worker-beat
labels:
{{- include "prowler.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.worker_beat.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "prowler.fullname" . }}-worker-beat
template:
metadata:
annotations:
secret-hash: "{{ printf "%s%s%s" (.Files.Get "templates/api/configmap.yaml" | sha256sum) (.Files.Get "templates/api/secret-valkey.yaml" | sha256sum) | sha256sum }}"
{{- with .Values.worker.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "prowler.labels" . | nindent 8 }}
app.kubernetes.io/name: {{ include "prowler.fullname" . }}-worker-beat
{{- with .Values.worker_beat.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.worker_beat.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "prowler.worker_beat.serviceAccountName" . }}
{{- with .Values.worker_beat.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: worker-beat
{{- with .Values.worker_beat.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
image: "{{ .Values.worker_beat.image.repository }}:{{ .Values.worker_beat.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.worker_beat.image.pullPolicy }}
{{- with .Values.worker_beat.command }}
command:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.worker_beat.args }}
args:
{{- toYaml . | nindent 12 }}
{{- end }}
envFrom:
- configMapRef:
name: {{ include "prowler.fullname" . }}-api
{{- if .Values.valkey.enabled }}
- secretRef:
name: {{ include "prowler.fullname" . }}-api-valkey
{{- end }}
{{- with .Values.api.secrets }}
{{- range $index, $secret := . }}
- secretRef:
name: {{ $secret }}
{{- end }}
{{- end }}
env:
{{- include "prowler.django.env" . | nindent 12 }}
{{- include "prowler.postgresql.env" . | nindent 12 }}
{{- include "prowler.neo4j.env" . | nindent 12 }}
{{- with .Values.worker_beat.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.worker_beat.readinessProbe }}
readinessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.worker_beat.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.worker_beat.volumeMounts }}
volumeMounts:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.worker_beat.volumes }}
volumes:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.worker_beat.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.worker_beat.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.worker_beat.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
@@ -0,0 +1,13 @@
{{- if .Values.worker_beat.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "prowler.worker_beat.serviceAccountName" . }}
labels:
{{- include "prowler.labels" . | nindent 4 }}
{{- with .Values.worker_beat.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
automountServiceAccountToken: {{ .Values.worker_beat.serviceAccount.automount }}
{{- end }}
+566
View File
@@ -0,0 +1,566 @@
# This is to override the chart name.
nameOverride: ""
fullnameOverride: ""
# Reference to the secret containing the API authentication secret.
# Used to inject the environment variable for the API container.
djangoTokenSigningKey:
secretKeyRef:
name: prowler-secret
key: DJANGO_TOKEN_SIGNING_KEY
djangoTokenVerifyingKey:
secretKeyRef:
name: prowler-secret
key: DJANGO_TOKEN_VERIFYING_KEY
djangoSecretsEncryptionKey:
secretKeyRef:
name: prowler-secret
key: DJANGO_SECRETS_ENCRYPTION_KEY
ui:
# This will set the replicaset count more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/
replicaCount: 1
# This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/
image:
repository: prowlercloud/prowler-ui
# This sets the pull policy for images.
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: ""
# This is for the secrets for pulling an image from a private repository more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
imagePullSecrets: []
# Reference to the secret containing the UI authentication secret.
# Used to inject the environment variable for the UI container.
# By default, expects a Secret named 'prowler-secret' with a key 'AUTH_SECRET'.
authSecret:
secretKeyRef:
name: prowler-secret
key: AUTH_SECRET
# Secret names to be used as env vars.
secrets: []
# - "prowler-ui-secret"
# This section builds out the service account more information can be found here: https://kubernetes.io/docs/concepts/security/service-accounts/
serviceAccount:
# Specifies whether a service account should be created
create: true
# Automatically mount a ServiceAccount's API credentials?
automount: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
# This is for setting Kubernetes Annotations to a Pod.
# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
podAnnotations: {}
# This is for setting Kubernetes Labels to a Pod.
# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
podLabels: {}
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
# This is for setting up a service more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/
service:
# This sets the service type more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types
type: ClusterIP
# This sets the ports more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports
port: 3000
# The URL of the UI. This is only set if ingress is disabled.
authUrl: ""
# This block is for setting up the ingress for more information can be found here: https://kubernetes.io/docs/concepts/services-networking/ingress/
ingress:
enabled: false
className: ""
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- host: chart-example.local
paths:
- path: /
pathType: ImplementationSpecific
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
# This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http
# This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 100
targetCPUUtilizationPercentage: 80
targetMemoryUtilizationPercentage: 80
# Additional volumes on the output Deployment definition.
volumes: []
# - name: foo
# secret:
# secretName: mysecret
# optional: false
# Additional volumeMounts on the output Deployment definition.
volumeMounts: []
# - name: foo
# mountPath: "/etc/foo"
# readOnly: true
nodeSelector: {}
tolerations: []
affinity: {}
api:
# This will set the replicaset count more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/
replicaCount: 1
# This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/
image:
repository: prowlercloud/prowler-api
# This sets the pull policy for images.
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: ""
# Shared with celery-worker and celery-beat
djangoConfig:
# API scan settings
# The path to the directory where scan output should be stored
DJANGO_TMP_OUTPUT_DIRECTORY: "/tmp/prowler_api_output"
# The maximum number of findings to process in a single batch
DJANGO_FINDINGS_BATCH_SIZE: "1000"
# Django settings
DJANGO_ALLOWED_HOSTS: "*"
DJANGO_BIND_ADDRESS: "0.0.0.0"
DJANGO_PORT: "8080"
DJANGO_DEBUG: "False"
DJANGO_SETTINGS_MODULE: "config.django.production"
# Select one of [ndjson|human_readable]
DJANGO_LOGGING_FORMATTER: "ndjson"
# Select one of [DEBUG|INFO|WARNING|ERROR|CRITICAL]
# Applies to both Django and Celery Workers
DJANGO_LOGGING_LEVEL: "INFO"
# Defaults to the maximum available based on CPU cores if not set.
DJANGO_WORKERS: "4"
# Token lifetime is in minutes
DJANGO_ACCESS_TOKEN_LIFETIME: "30"
# Token lifetime is in minutes
DJANGO_REFRESH_TOKEN_LIFETIME: "1440"
DJANGO_CACHE_MAX_AGE: "3600"
DJANGO_STALE_WHILE_REVALIDATE: "60"
DJANGO_MANAGE_DB_PARTITIONS: "True"
DJANGO_BROKER_VISIBILITY_TIMEOUT: "86400"
# Secret names to be used as env vars for api, worker, and worker_beat.
secrets: []
# - "prowler-api-keys"
command:
- /home/prowler/docker-entrypoint.sh
args:
- prod
# This is for the secrets for pulling an image from a private repository more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
imagePullSecrets: []
# This section builds out the service account more information can be found here: https://kubernetes.io/docs/concepts/security/service-accounts/
serviceAccount:
# Specifies whether a service account should be created
create: true
# Automatically mount a ServiceAccount's API credentials?
automount: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
# This is for setting Kubernetes Annotations to a Pod.
# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
podAnnotations: {}
# This is for setting Kubernetes Labels to a Pod.
# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
podLabels: {}
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
# This is for setting up a service more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/
service:
# This sets the service type more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types
type: ClusterIP
# This sets the ports more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports
port: 8080
# This block is for setting up the ingress for more information can be found here: https://kubernetes.io/docs/concepts/services-networking/ingress/
ingress:
enabled: false
className: ""
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- host: chart-example.local
paths:
- path: /
pathType: ImplementationSpecific
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
# 3m30s to setup DB
# startupProbe:
# httpGet:
# path: /api/v1/docs
# port: http
# This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
livenessProbe:
failureThreshold: 10
httpGet:
path: /api/v1/docs
port: http
periodSeconds: 20
readinessProbe:
failureThreshold: 10
httpGet:
path: /api/v1/docs
port: http
periodSeconds: 20
# This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 100
targetCPUUtilizationPercentage: 80
targetMemoryUtilizationPercentage: 80
# Additional volumes on the output Deployment definition.
volumes: []
# - name: foo
# secret:
# secretName: mysecret
# optional: false
# Additional volumeMounts on the output Deployment definition.
volumeMounts: []
# - name: foo
# mountPath: "/etc/foo"
# readOnly: true
nodeSelector: {}
tolerations: []
affinity: {}
worker:
# This will set the replicaset count more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/
replicaCount: 1
# This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/
image:
repository: prowlercloud/prowler-api
# This sets the pull policy for images.
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: ""
command:
- /home/prowler/docker-entrypoint.sh
args:
- worker
# This is for the secrets for pulling an image from a private repository more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
imagePullSecrets: []
# This section builds out the service account more information can be found here: https://kubernetes.io/docs/concepts/security/service-accounts/
serviceAccount:
# Specifies whether a service account should be created
create: true
# Automatically mount a ServiceAccount's API credentials?
automount: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
# This is for setting Kubernetes Annotations to a Pod.
# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
podAnnotations: {}
# This is for setting Kubernetes Labels to a Pod.
# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
podLabels: {}
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
# This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
livenessProbe: {}
readinessProbe: {}
# This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 10
targetCPUUtilizationPercentage: 80
targetMemoryUtilizationPercentage: 80
# Additional volumes on the output Deployment definition.
volumes: []
# - name: foo
# secret:
# secretName: mysecret
# optional: false
# Additional volumeMounts on the output Deployment definition.
volumeMounts: []
# - name: foo
# mountPath: "/etc/foo"
# readOnly: true
nodeSelector: {}
tolerations: []
affinity: {}
# KEDA ScaledObject configuration
keda:
# -- Set to `true` to enable KEDA for the worker pods
# Note: When both KEDA and HPA are enabled, the deployment will fail.
enabled: false
# -- The minimum number of replicas to use for the worker pods
minReplicas: 1
# -- The maximum number of replicas to use for the worker pods
maxReplicas: 2
# -- The polling interval in seconds for checking metrics
pollingInterval: 30
# -- The cooldown period in seconds for scaling
cooldownPeriod: 120
# -- The trigger type for scaling (cpu or memory)
triggerType: "postgresql"
# -- The target utilization percentage for the worker pods
value: "50"
worker_beat:
# This will set the replicaset count more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/
replicaCount: 1
# This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/
image:
repository: prowlercloud/prowler-api
# This sets the pull policy for images.
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: ""
command:
- ../docker-entrypoint.sh
args:
- beat
# This is for the secrets for pulling an image from a private repository more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
imagePullSecrets: []
# This section builds out the service account more information can be found here: https://kubernetes.io/docs/concepts/security/service-accounts/
serviceAccount:
# Specifies whether a service account should be created
create: true
# Automatically mount a ServiceAccount's API credentials?
automount: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
# This is for setting Kubernetes Annotations to a Pod.
# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
podAnnotations: {}
# This is for setting Kubernetes Labels to a Pod.
# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
podLabels: {}
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
# This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
livenessProbe: {}
readinessProbe: {}
# Additional volumes on the output Deployment definition.
volumes: []
# - name: foo
# secret:
# secretName: mysecret
# optional: false
# Additional volumeMounts on the output Deployment definition.
volumeMounts: []
# - name: foo
# mountPath: "/etc/foo"
# readOnly: true
nodeSelector: {}
tolerations: []
affinity: {}
postgresql:
# -- Enable PostgreSQL deployment (via Bitnami Helm Chart). If you want to use an external Postgres server (or a managed one), set this to false
# If enabled, it will create a Secret with the credentials.
# Otherwise, create a secret with the following and add it to the api deployment:
# - POSTGRES_HOST
# - POSTGRES_PORT
# - POSTGRES_ADMIN_USER - Existing user in charge of migrations, tables, permissions, RLS
# - POSTGRES_ADMIN_PASSWORD
# - POSTGRES_USER - Will be created by ADMIN_USER
# - POSTGRES_PASSWORD
# - POSTGRES_DB - Existing DB
enabled: true
image:
repository: "bitnami/postgresql"
auth:
database: prowler_db
username: prowler
valkey:
# If enabled, it will create a Secret with the following.
# Otherwise, create a secret with
# - VALKEY_HOST
# - VALKEY_PORT
# - VALKEY_DB
enabled: true
neo4j:
enabled: true
neo4j:
name: prowler-neo4j
edition: community
# The name of the secret containing the Neo4j password with the key NEO4J_PASSWORD
passwordFromSecret: prowler-secret
# Disable lookups during helm template rendering (required for ArgoCD)
disableLookups: true
volumes:
data:
mode: defaultStorageClass
services:
neo4j:
enabled: false
# Neo4j Configuration (yaml format)
config:
dbms_security_procedures_allowlist: "apoc.*"
dbms_security_procedures_unrestricted: "apoc.*"
apoc_config:
apoc.export.file.enabled: "true"
apoc.import.file.enabled: "true"
apoc.import.file.use_neo4j_config: "true"
@@ -0,0 +1,41 @@
import warnings
from dashboard.common_methods import get_section_containers_cis
warnings.filterwarnings("ignore")
def get_table(data):
"""
Generate CIS OCI Foundations Benchmark v3.1 compliance table.
Args:
data: DataFrame containing compliance check results with columns:
- REQUIREMENTS_ID: CIS requirement ID (e.g., "1.1", "2.1")
- REQUIREMENTS_DESCRIPTION: Description of the requirement
- REQUIREMENTS_ATTRIBUTES_SECTION: CIS section name
- CHECKID: Prowler check identifier
- STATUS: Check status (PASS/FAIL)
- REGION: OCI region
- ACCOUNTID: OCI tenancy OCID (renamed from TENANCYID)
- RESOURCEID: Resource OCID or identifier
Returns:
Section containers organized by CIS sections for dashboard display
"""
aux = data[
[
"REQUIREMENTS_ID",
"REQUIREMENTS_DESCRIPTION",
"REQUIREMENTS_ATTRIBUTES_SECTION",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
].copy()
return get_section_containers_cis(
aux, "REQUIREMENTS_ID", "REQUIREMENTS_ATTRIBUTES_SECTION"
)
+5
View File
@@ -284,6 +284,11 @@ def display_data(
# Rename the column LOCATION to REGION for Alibaba Cloud
if "alibabacloud" in analytics_input:
data = data.rename(columns={"LOCATION": "REGION"})
# Rename the column TENANCYID to ACCOUNTID for Oracle Cloud
if "oraclecloud" in analytics_input:
data.rename(columns={"TENANCYID": "ACCOUNTID"}, inplace=True)
# Filter the chosen level of the CIS
if is_level_1:
data = data[data["REQUIREMENTS_ATTRIBUTES_PROFILE"].str.contains("Level 1")]
+13
View File
@@ -259,6 +259,8 @@ else:
accounts.append(account + " - K8S")
if "alibabacloud" in list(data[data["ACCOUNT_UID"] == account]["PROVIDER"]):
accounts.append(account + " - ALIBABACLOUD")
if "oraclecloud" in list(data[data["ACCOUNT_UID"] == account]["PROVIDER"]):
accounts.append(account + " - OCI")
account_dropdown = create_account_dropdown(accounts)
@@ -306,6 +308,8 @@ else:
services.append(service + " - M365")
if "alibabacloud" in list(data[data["SERVICE_NAME"] == service]["PROVIDER"]):
services.append(service + " - ALIBABACLOUD")
if "oraclecloud" in list(data[data["SERVICE_NAME"] == service]["PROVIDER"]):
services.append(service + " - OCI")
services = ["All"] + services
services = [
@@ -767,6 +771,8 @@ def filter_data(
all_account_ids.append(account)
if "alibabacloud" in list(data[data["ACCOUNT_UID"] == account]["PROVIDER"]):
all_account_ids.append(account)
if "oraclecloud" in list(data[data["ACCOUNT_UID"] == account]["PROVIDER"]):
all_account_ids.append(account)
all_account_names = []
if "ACCOUNT_NAME" in filtered_data.columns:
@@ -793,6 +799,8 @@ def filter_data(
data[data["ACCOUNT_UID"] == item]["PROVIDER"]
):
cloud_accounts_options.append(item + " - ALIBABACLOUD")
if "oraclecloud" in list(data[data["ACCOUNT_UID"] == item]["PROVIDER"]):
cloud_accounts_options.append(item + " - OCI")
if "ACCOUNT_NAME" in filtered_data.columns:
if "azure" in list(data[data["ACCOUNT_NAME"] == item]["PROVIDER"]):
cloud_accounts_options.append(item + " - AZURE")
@@ -925,6 +933,10 @@ def filter_data(
filtered_data[filtered_data["SERVICE_NAME"] == item]["PROVIDER"]
):
service_filter_options.append(item + " - ALIBABACLOUD")
if "oraclecloud" in list(
filtered_data[filtered_data["SERVICE_NAME"] == item]["PROVIDER"]
):
service_filter_options.append(item + " - OCI")
# Filter Service
if service_values == ["All"]:
@@ -1124,6 +1136,7 @@ def filter_data(
config={"displayModeBar": False},
)
table = dcc.Graph(figure=fig, config={"displayModeBar": False})
table_row_options = []
else:
# Status Pie Chart
@@ -115,8 +115,8 @@ To update the environment file:
Edit the `.env` file and change version values:
```env
PROWLER_UI_VERSION="5.18.1"
PROWLER_API_VERSION="5.18.1"
PROWLER_UI_VERSION="5.18.0"
PROWLER_API_VERSION="5.18.0"
```
<Note>
Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

+1
View File
@@ -36,6 +36,7 @@ The supported providers right now are:
| [Cloudflare](/user-guide/providers/cloudflare/getting-started-cloudflare) | Official | Accounts | CLI |
| [Infra as Code](/user-guide/providers/iac/getting-started-iac) | Official | Repositories | UI, API, CLI |
| [MongoDB Atlas](/user-guide/providers/mongodbatlas/getting-started-mongodbatlas) | Official | Organizations | UI, API, CLI |
| [OpenStack](/user-guide/providers/openstack/getting-started-openstack) | Official | Projects | CLI |
| [LLM](/user-guide/providers/llm/getting-started-llm) | Official | Models | CLI |
| **NHN** | Unofficial | Tenants | CLI |
+62 -28
View File
@@ -79,6 +79,31 @@ Choose a Method:
</Info>
**Configure Attribute Mapping in the IdP**
For Prowler App to correctly identify and provision users, configure the IdP to send the following attributes in the SAML assertion:
| Attribute Name | Description | Required |
|----------------|---------------------------------------------------------------------------------------------------------|----------|
| `firstName` | The user's first name. | Yes |
| `lastName` | The user's last name. | Yes |
| `userType` | The Prowler role to be assigned to the user (e.g., `admin`, `auditor`). If a role with that name already exists, it will be used; if it does not exist, a new role with that name will be created without permissions. If `userType` is not defined, the user is assigned the `no_permissions` role. Role permissions can be edited in the [RBAC Management tab](/user-guide/tutorials/prowler-app-rbac). | No |
| `companyName` | The user's company name. This is automatically populated if the IdP sends an `organization` attribute. | No |
<Info>
**IdP Attribute Mapping**
Note that the attribute name is just an example and may be different depending on the IdP. For instance, if the IdP provides a `division` attribute, it can be mapped to `userType`.
![IdP configuration](/images/prowler-app/saml/saml_attribute_statements.png)
</Info>
<Warning>
**Dynamic Updates**
Prowler App updates these attributes each time a user logs in. Any changes made in the Identity Provider (IdP) will be reflected when the user logs in again.
</Warning>
</Tab>
<Tab title="Okta App Catalog">
Instead of creating a custom SAML integration, Okta administrators can configure Prowler Cloud directly from Okta's application catalog.
@@ -105,39 +130,48 @@ Choose a Method:
6. **Assign Users**: Navigate to the "Assignments" tab and assign the appropriate users or groups to the Prowler application by clicking "Assign" and selecting "Assign to People" or "Assign to Groups".
![Okta App Assignments](/images/prowler-app/saml/okta-app-assignments.png)
7. **Configure User Attributes in Okta**: Okta acts as the central source for user profile information. Prowler App maps the following Okta user profile attributes during each SAML login:
* **First name** (`firstName`): Maps to the user's first name in Prowler App.
* **Last name** (`lastName`): Maps to the user's last name in Prowler App.
![Okta User Profile — First Name and Last Name](/images/prowler-app/saml/okta-user-profile-name.png)
* **Organization** (`organization`): Maps to the company name displayed in Prowler App. This attribute is optional.
* **User type** (`userType`): Determines the Prowler role assigned to the user. This attribute is **case-sensitive** and must match the exact name of an existing role in Prowler App.
![Okta User Profile — User Type and Organization](/images/prowler-app/saml/okta-user-profile-attributes.png)
To modify these values, edit the user's profile directly in the Okta admin console under the "Profile" tab. Changes are reflected in Prowler App the next time the user logs in via SAML.
<Warning>
**User Type and Role Assignment**
The `userType` attribute controls which Prowler role is assigned to the user:
* If a role with the specified name already exists in Prowler App, the user automatically receives that role.
* If the role does not exist, Prowler App creates a new role with that exact name but without any permissions, preventing the user from performing any actions.
* If `userType` is not defined in the user's Okta profile, the user is assigned the `no_permissions` role.
In all cases where the resulting role has no permissions, a Prowler administrator (a user whose role includes the "Manage Account" permission) must configure the appropriate permissions through the [RBAC Management tab](/user-guide/tutorials/prowler-app-rbac).
This behavior is intentional: by defaulting to no permissions, Prowler App ensures that a misconfiguration in Okta cannot inadvertently grant elevated access.
**Example:** To assign the `IT` role to a user, set the `userType` value to `IT` in Okta. If a role named `IT` already exists in Prowler App, the user receives it automatically upon login. If it does not exist, Prowler App creates a new role called `IT` without permissions, and a Prowler administrator must configure the desired permissions for it.
</Warning>
With this step, the Okta app catalog configuration is complete. Users can now access Prowler Cloud using either [IdP-initiated](#idp-initiated-sso) or [SP-initiated SSO](#sp-initiated-sso) flows.
7. **Download Metadata XML**: Inside the "Sign On" section, go to the "Metadata URL" and download the metadata XML file.
8. **Download Metadata XML**: Inside the "Sign On" section, go to the "Metadata URL" and download the metadata XML file.
Jump to [Step 5: Upload IdP Metadata to Prowler](#step-5:-upload-idp-metadata-to-prowler).
Jump to [Step 4: Upload IdP Metadata to Prowler](#step-4:-upload-idp-metadata-to-prowler).
</Tab>
</Tabs>
#### Step 4: Configure Attribute Mapping in the IdP
For Prowler App to correctly identify and provision users, configure the IdP to send the following attributes in the SAML assertion:
| Attribute Name | Description | Required |
|----------------|---------------------------------------------------------------------------------------------------------|----------|
| `firstName` | The user's first name. | Yes |
| `lastName` | The user's last name. | Yes |
| `userType` | The Prowler role to be assigned to the user (e.g., `admin`, `auditor`). If a role with that name already exists, it will be used; otherwise, a new role called `no_permissions` will be created with minimal permissions. Role permissions can be edited in the [RBAC Management tab](/user-guide/tutorials/prowler-app-rbac). | No |
| `companyName` | The user's company name. This is automatically populated if the IdP sends an `organization` attribute. | No |
<Info>
**IdP Attribute Mapping**
Note that the attribute name is just an example and may be different depending on the IdP. For instance, if the IdP provides a `division` attribute, it can be mapped to `userType`.
![IdP configuration](/images/prowler-app/saml/saml_attribute_statements.png)
</Info>
<Warning>
**Dynamic Updates**
Prowler App updates these attributes each time a user logs in. Any changes made in the Identity Provider (IdP) will be reflected when the user logs in again.
</Warning>
#### Step 5: Upload IdP Metadata to Prowler
#### Step 4: Upload IdP Metadata to Prowler
Once the IdP is configured, it provides a **metadata XML file**. This file contains the IdP's configuration information, such as its public key and login URL.
@@ -151,7 +185,7 @@ To complete the Prowler App configuration:
![Configure Prowler with IdP Metadata](/images/prowler-app/saml/saml-step-3.png)
#### Step 6: Save and Verify Configuration
#### Step 5: Save and Verify Configuration
Click the "Save" button to complete the setup. The "SAML Integration" card will now display an "Active" status, indicating the configuration is complete and enabled.
+16 -1
View File
@@ -2,6 +2,18 @@
All notable changes to the **Prowler SDK** are documented in this file.
## [5.19.0] (Prowler UNRELEASED)
### 🚀 Added
- `defender_safe_attachments_policy_enabled` check for M365 provider [(#9833)](https://github.com/prowler-cloud/prowler/pull/9833)
- `defender_safelinks_policy_enabled` check for M365 provider [(#9832)](https://github.com/prowler-cloud/prowler/pull/9832)
- AI Skills: Added a skill for creating new Attack Paths queries in openCypher, compatible with Neo4j and Neptune [(#9975)](https://github.com/prowler-cloud/prowler/pull/9975)
### 🔄 Changed
- Update Azure Monitor service metadata to new format [(#9622)](https://github.com/prowler-cloud/prowler/pull/9622)
## [5.18.2] (Prowler UNRELEASED)
### 🐞 Fixed
@@ -18,6 +30,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- `defender_zap_for_teams_enabled` check for M365 provider [(#9838)](https://github.com/prowler-cloud/prowler/pull/9838)
- `compute_instance_suspended_without_persistent_disks` check for GCP provider [(#9747)](https://github.com/prowler-cloud/prowler/pull/9747)
- `codebuild_project_webhook_filters_use_anchored_patterns` check for AWS provider to detect CodeBreach vulnerability [(#9840)](https://github.com/prowler-cloud/prowler/pull/9840)
- `defender_atp_safe_attachments_policy_enabled` check for M365 provider [(#9837)](https://github.com/prowler-cloud/prowler/pull/9837)
- `exchange_shared_mailbox_sign_in_disabled` check for M365 provider [(#9828)](https://github.com/prowler-cloud/prowler/pull/9828)
- CloudTrail Timeline abstraction for querying resource modification history [(#9101)](https://github.com/prowler-cloud/prowler/pull/9101)
- Cloudflare `--account-id` filter argument [(#9894)](https://github.com/prowler-cloud/prowler/pull/9894)
@@ -26,6 +39,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- `OpenStack` documentation for the support in the CLI [(#9848)](https://github.com/prowler-cloud/prowler/pull/9848)
- Add HIPAA compliance framework for the Azure provider [(#9957)](https://github.com/prowler-cloud/prowler/pull/9957)
- Cloudflare provider credentials as constructor parameters (`api_token`, `api_key`, `api_email`) [(#9907)](https://github.com/prowler-cloud/prowler/pull/9907)
- CIS 3.1 for the Oracle Cloud provider [(#9971)](https://github.com/prowler-cloud/prowler/pull/9971)
### 🔄 Changed
@@ -45,7 +59,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- Update Azure Network service metadata to new format [(#9624)](https://github.com/prowler-cloud/prowler/pull/9624)
- Update Azure Storage service metadata to new format [(#9628)](https://github.com/prowler-cloud/prowler/pull/9628)
### 🐛 Fixed
### 🐞 Fixed
- Duplicated findings in `entra_user_with_vm_access_has_mfa` check when user has multiple VM access roles [(#9914)](https://github.com/prowler-cloud/prowler/pull/9914)
- Jira integration failing with `INVALID_INPUT` error when sending findings with long resource UIDs exceeding 255-character summary limit [(#9926)](https://github.com/prowler-cloud/prowler/pull/9926)
@@ -113,6 +127,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- Update Azure AI Search service metadata to new format [(#9087)](https://github.com/prowler-cloud/prowler/pull/9087)
- Update Azure AKS service metadata to new format [(#9611)](https://github.com/prowler-cloud/prowler/pull/9611)
- Update Azure API Management service metadata to new format [(#9612)](https://github.com/prowler-cloud/prowler/pull/9612)
- Enhance AWS IAM privilege escalation detection with patterns from pathfinding.cloud library [(#9922)](https://github.com/prowler-cloud/prowler/pull/9922)
### Fixed
+9 -3
View File
@@ -318,7 +318,9 @@
{
"Id": "2.1.1",
"Description": "Enabling Safe Links policy for Office applications allows URL's that exist inside of Office documents and email applications opened by Office, Office Online and Office mobile to be processed against Defender for Office time-of-click verification and rewritten if required.**Note:** E5 Licensing includes a number of Built-in Protection policies. When auditing policies note which policy you are viewing, and keep in mind CIS recommendations often extend the Default or Build-in Policies provided by MS. In order to **Pass** the highest priority policy must match all settings recommended.",
"Checks": [],
"Checks": [
"defender_safelinks_policy_enabled"
],
"Attributes": [
{
"Section": "2 Microsoft 365 Defender",
@@ -385,7 +387,9 @@
{
"Id": "2.1.4",
"Description": "The Safe Attachments policy helps protect users from malware in email attachments by scanning attachments for viruses, malware, and other malicious content. When an email attachment is received by a user, Safe Attachments will scan the attachment in a secure environment and provide a verdict on whether the attachment is safe or not.",
"Checks": [],
"Checks": [
"defender_safe_attachments_policy_enabled"
],
"Attributes": [
{
"Section": "2 Microsoft 365 Defender",
@@ -406,7 +410,9 @@
{
"Id": "2.1.5",
"Description": "Safe Attachments for SharePoint, OneDrive, and Microsoft Teams scans these services for malicious files.",
"Checks": [],
"Checks": [
"defender_atp_safe_attachments_and_docs_configured"
],
"Attributes": [
{
"Section": "2 Microsoft 365 Defender",
+9 -3
View File
@@ -339,7 +339,9 @@
{
"Id": "2.1.1",
"Description": "Safe Links for Office Applications is a feature in Microsoft 365 that provides URL scanning and rewriting of inbound email messages in mail flow, and time-of-click verification of URLs and links in email messages, other Microsoft 365 applications such as Teams, and other locations such as SharePoint Online. It can help protect organizations from malicious links used in phishing and other attacks.",
"Checks": [],
"Checks": [
"defender_safelinks_policy_enabled"
],
"Attributes": [
{
"Section": "2 Microsoft 365 Defender",
@@ -404,7 +406,9 @@
{
"Id": "2.1.4",
"Description": "The Safe Attachments policy helps protect users from malware in email attachments by scanning attachments for viruses, malware, and other malicious content. When an email attachment is received by a user, Safe Attachments will scan the attachment in a secure environment and provide a verdict on whether the attachment is safe or not.",
"Checks": [],
"Checks": [
"defender_safe_attachments_policy_enabled"
],
"Attributes": [
{
"Section": "2 Microsoft 365 Defender",
@@ -425,7 +429,9 @@
{
"Id": "2.1.5",
"Description": "Safe Attachments for SharePoint, OneDrive, and Microsoft Teams scans these services for malicious files.",
"Checks": [],
"Checks": [
"defender_atp_safe_attachments_and_docs_configured"
],
"Attributes": [
{
"Section": "2 Microsoft 365 Defender",
@@ -115,6 +115,7 @@
"defender_antiphishing_policy_configured",
"defender_antispam_outbound_policy_configured",
"defender_malware_policy_notifications_internal_users_malware_enabled",
"defender_safelinks_policy_enabled",
"defender_zap_for_teams_enabled",
"entra_admin_users_phishing_resistant_mfa_enabled",
"entra_identity_protection_sign_in_risk_enabled",
@@ -692,6 +693,8 @@
"defender_malware_policy_common_attachments_filter_enabled",
"defender_malware_policy_comprehensive_attachments_filter_applied",
"defender_malware_policy_notifications_internal_users_malware_enabled",
"defender_safe_attachments_policy_enabled",
"defender_safelinks_policy_enabled",
"defender_zap_for_teams_enabled",
"teams_external_domains_restricted",
"teams_external_users_cannot_start_conversations"
@@ -729,6 +732,7 @@
],
"Checks": [
"defender_antiphishing_policy_configured",
"defender_safelinks_policy_enabled",
"entra_admin_users_phishing_resistant_mfa_enabled"
]
},
@@ -833,10 +837,11 @@
}
],
"Checks": [
"teams_external_domains_restricted",
"teams_external_users_cannot_start_conversations",
"defender_safelinks_policy_enabled",
"sharepoint_external_sharing_managed",
"sharepoint_external_sharing_restricted",
"sharepoint_external_sharing_managed"
"teams_external_domains_restricted",
"teams_external_users_cannot_start_conversations"
]
},
{
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -38,7 +38,7 @@ class _MutableTimestamp:
timestamp = _MutableTimestamp(datetime.today())
timestamp_utc = _MutableTimestamp(datetime.now(timezone.utc))
prowler_version = "5.18.2"
prowler_version = "5.19.0"
html_logo_url = "https://github.com/prowler-cloud/prowler/"
square_logo_img = "https://raw.githubusercontent.com/prowler-cloud/prowler/dc7d2d5aeb92fdf12e8604f42ef6472cd3e8e889/docs/img/prowler-logo-black.png"
aws_logo = "https://user-images.githubusercontent.com/38561120/235953920-3e3fba08-0795-41dc-b480-9bea57db9f2e.png"
+12 -1
View File
@@ -193,10 +193,21 @@ class PowerShellSession:
result = default
if error_result:
logger.error(f"PowerShell error output: {error_result}")
self._process_error(error_result)
return result
def _process_error(self, error_result: str) -> None:
"""
Process error output from the PowerShell command.
Subclasses can override this to provide custom error handling.
Args:
error_result (str): The error output from the PowerShell command.
"""
logger.error(f"PowerShell error output: {error_result}")
def json_parse_output(self, output: str) -> dict:
"""
Parse command execution output to JSON format.
@@ -1285,7 +1285,12 @@
"b2bi": {
"regions": {
"aws": [
"ap-south-2",
"ap-southeast-2",
"ca-central-1",
"eu-central-1",
"eu-west-1",
"eu-west-3",
"us-east-1",
"us-east-2",
"us-west-2"
@@ -1479,6 +1484,7 @@
"ap-southeast-3",
"ap-southeast-4",
"ap-southeast-5",
"ap-southeast-6",
"ap-southeast-7",
"ca-central-1",
"ca-west-1",
@@ -1539,11 +1545,16 @@
"regions": {
"aws": [
"ap-northeast-1",
"ap-northeast-2",
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
"ca-central-1",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"eu-west-3",
"us-east-1",
"us-east-2",
"us-west-2"
@@ -2748,7 +2759,9 @@
"us-west-2"
],
"aws-cn": [],
"aws-eusc": [],
"aws-eusc": [
"eusc-de-east-1"
],
"aws-us-gov": [
"us-gov-east-1",
"us-gov-west-1"
@@ -2794,7 +2807,9 @@
"aws-cn": [
"cn-north-1"
],
"aws-eusc": [],
"aws-eusc": [
"eusc-de-east-1"
],
"aws-us-gov": [
"us-gov-east-1",
"us-gov-west-1"
@@ -2838,7 +2853,9 @@
"us-west-2"
],
"aws-cn": [],
"aws-eusc": [],
"aws-eusc": [
"eusc-de-east-1"
],
"aws-us-gov": [
"us-gov-east-1",
"us-gov-west-1"
@@ -3166,7 +3183,9 @@
"us-west-2"
],
"aws-cn": [],
"aws-eusc": [],
"aws-eusc": [
"eusc-de-east-1"
],
"aws-us-gov": [
"us-gov-east-1",
"us-gov-west-1"
@@ -3212,7 +3231,9 @@
"us-west-2"
],
"aws-cn": [],
"aws-eusc": [],
"aws-eusc": [
"eusc-de-east-1"
],
"aws-us-gov": [
"us-gov-east-1",
"us-gov-west-1"
@@ -3245,16 +3266,6 @@
"aws-us-gov": []
}
},
"cur": {
"regions": {
"aws": [],
"aws-cn": [
"cn-northwest-1"
],
"aws-eusc": [],
"aws-us-gov": []
}
},
"customer-profiles": {
"regions": {
"aws": [
@@ -3395,6 +3406,7 @@
"datazone": {
"regions": {
"aws": [
"af-south-1",
"ap-east-1",
"ap-northeast-1",
"ap-northeast-2",
@@ -3407,6 +3419,7 @@
"eu-central-1",
"eu-central-2",
"eu-north-1",
"eu-south-2",
"eu-west-1",
"eu-west-2",
"eu-west-3",
@@ -4628,6 +4641,7 @@
"ap-southeast-2",
"ap-southeast-3",
"ap-southeast-4",
"ap-southeast-5",
"ca-central-1",
"ca-west-1",
"eu-central-1",
@@ -5021,6 +5035,7 @@
"ap-east-1",
"ap-northeast-1",
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
@@ -5221,7 +5236,9 @@
"cn-north-1",
"cn-northwest-1"
],
"aws-eusc": [],
"aws-eusc": [
"eusc-de-east-1"
],
"aws-us-gov": [
"us-gov-east-1",
"us-gov-west-1"
@@ -5269,7 +5286,9 @@
"cn-north-1",
"cn-northwest-1"
],
"aws-eusc": [],
"aws-eusc": [
"eusc-de-east-1"
],
"aws-us-gov": [
"us-gov-east-1",
"us-gov-west-1"
@@ -5317,7 +5336,9 @@
"cn-north-1",
"cn-northwest-1"
],
"aws-eusc": [],
"aws-eusc": [
"eusc-de-east-1"
],
"aws-us-gov": [
"us-gov-east-1",
"us-gov-west-1"
@@ -5364,7 +5385,9 @@
"cn-north-1",
"cn-northwest-1"
],
"aws-eusc": [],
"aws-eusc": [
"eusc-de-east-1"
],
"aws-us-gov": [
"us-gov-east-1",
"us-gov-west-1"
@@ -5412,7 +5435,9 @@
"cn-north-1",
"cn-northwest-1"
],
"aws-eusc": [],
"aws-eusc": [
"eusc-de-east-1"
],
"aws-us-gov": [
"us-gov-east-1",
"us-gov-west-1"
@@ -5600,7 +5625,10 @@
],
"aws-cn": [],
"aws-eusc": [],
"aws-us-gov": []
"aws-us-gov": [
"us-gov-east-1",
"us-gov-west-1"
]
}
},
"greengrass": {
@@ -5668,6 +5696,7 @@
"ap-southeast-3",
"ap-southeast-4",
"ap-southeast-5",
"ap-southeast-6",
"ap-southeast-7",
"ca-central-1",
"ca-west-1",
@@ -7100,6 +7129,7 @@
"ap-southeast-3",
"ap-southeast-4",
"ap-southeast-5",
"ap-southeast-6",
"ap-southeast-7",
"ca-central-1",
"ca-west-1",
@@ -7787,6 +7817,7 @@
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-4",
"ap-southeast-5",
"ca-central-1",
"eu-central-1",
"eu-north-1",
@@ -7853,6 +7884,7 @@
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-4",
"ap-southeast-5",
"ca-central-1",
"eu-central-1",
"eu-north-1",
@@ -8162,6 +8194,7 @@
"ap-southeast-3",
"ap-southeast-4",
"ap-southeast-5",
"ap-southeast-7",
"ca-central-1",
"ca-west-1",
"eu-central-1",
@@ -8261,16 +8294,30 @@
"neptune-graph": {
"regions": {
"aws": [
"af-south-1",
"ap-east-1",
"ap-northeast-1",
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-5",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"eu-west-3",
"il-central-1",
"me-central-1",
"me-south-1",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-1",
"us-west-2"
],
"aws-cn": [],
@@ -8402,18 +8449,28 @@
"networkmonitor": {
"regions": {
"aws": [
"af-south-1",
"ap-east-1",
"ap-northeast-1",
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-south-2",
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
"eu-south-1",
"eu-south-2",
"eu-west-1",
"eu-west-2",
"eu-west-3",
"il-central-1",
"me-central-1",
"me-south-1",
"sa-east-1",
"us-east-1",
@@ -8575,6 +8632,7 @@
"regions": {
"aws": [
"ap-northeast-1",
"ca-central-1",
"eu-central-1",
"us-east-1",
"us-east-2",
@@ -9146,6 +9204,7 @@
"regions": {
"aws": [
"af-south-1",
"ap-east-2",
"ap-northeast-1",
"ap-northeast-2",
"ap-northeast-3",
@@ -9155,6 +9214,7 @@
"ap-southeast-2",
"ap-southeast-3",
"ap-southeast-4",
"ap-southeast-6",
"ca-central-1",
"ca-west-1",
"eu-central-1",
@@ -10223,6 +10283,7 @@
"aws": [
"af-south-1",
"ap-east-1",
"ap-east-2",
"ap-northeast-1",
"ap-northeast-2",
"ap-northeast-3",
@@ -10233,6 +10294,7 @@
"ap-southeast-3",
"ap-southeast-4",
"ap-southeast-5",
"ap-southeast-6",
"ap-southeast-7",
"ca-central-1",
"ca-west-1",
@@ -11671,11 +11733,17 @@
"ap-south-2",
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-6",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-north-1",
"eu-south-2",
"eu-west-1",
"eu-west-2",
"me-central-1",
"me-south-1",
"mx-central-1",
"sa-east-1",
"us-east-1",
"us-east-2",
@@ -18,16 +18,88 @@ from prowler.providers.aws.services.iam.lib.policy import get_effective_actions
# - https://bishopfox.com/blog/privilege-escalation-in-aws
# - https://github.com/RhinoSecurityLabs/Security-Research/blob/master/tools/aws-pentest-tools/aws_escalate.py
# - https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/
# - https://github.com/DataDog/pathfinding.cloud (AWS IAM Privilege Escalation Path Library)
privilege_escalation_policies_combination = {
# IAM self-escalation and policy manipulation
"OverPermissiveIAM": {"iam:*"},
"IAMPut": {"iam:Put*"},
"CreatePolicyVersion": {"iam:CreatePolicyVersion"},
"SetDefaultPolicyVersion": {"iam:SetDefaultPolicyVersion"},
"iam:CreateAccessKey": {"iam:CreateAccessKey"},
"iam:CreateLoginProfile": {"iam:CreateLoginProfile"},
"iam:UpdateLoginProfile": {"iam:UpdateLoginProfile"},
"iam:AttachUserPolicy": {"iam:AttachUserPolicy"},
"iam:AttachGroupPolicy": {"iam:AttachGroupPolicy"},
"iam:AttachRolePolicy": {"iam:AttachRolePolicy"},
"iam:PutGroupPolicy": {"iam:PutGroupPolicy"},
"iam:PutRolePolicy": {"iam:PutRolePolicy"},
"iam:PutUserPolicy": {"iam:PutUserPolicy"},
"iam:AddUserToGroup": {"iam:AddUserToGroup"},
"iam:UpdateAssumeRolePolicy": {"iam:UpdateAssumeRolePolicy"},
# IAM chained privilege escalation patterns
"CreateAccessKey+DeleteAccessKey": {
"iam:CreateAccessKey",
"iam:DeleteAccessKey",
},
"AttachUserPolicy+CreateAccessKey": {
"iam:AttachUserPolicy",
"iam:CreateAccessKey",
},
"PutUserPolicy+CreateAccessKey": {
"iam:PutUserPolicy",
"iam:CreateAccessKey",
},
"AttachRolePolicy+UpdateAssumeRolePolicy": {
"iam:AttachRolePolicy",
"iam:UpdateAssumeRolePolicy",
},
"CreatePolicyVersion+UpdateAssumeRolePolicy": {
"iam:CreatePolicyVersion",
"iam:UpdateAssumeRolePolicy",
},
"PutRolePolicy+UpdateAssumeRolePolicy": {
"iam:PutRolePolicy",
"iam:UpdateAssumeRolePolicy",
},
# STS-based privilege escalation patterns
"AssumeRole+AttachRolePolicy": {"sts:AssumeRole", "iam:AttachRolePolicy"},
"AssumeRole+PutRolePolicy": {"sts:AssumeRole", "iam:PutRolePolicy"},
"AssumeRole+UpdateAssumeRolePolicy": {
"sts:AssumeRole",
"iam:UpdateAssumeRolePolicy",
},
"AssumeRole+CreatePolicyVersion": {
"sts:AssumeRole",
"iam:CreatePolicyVersion",
},
# EC2-based privilege escalation patterns
"PassRole+EC2": {
"iam:PassRole",
"ec2:RunInstances",
},
"PassRole+EC2SpotInstances": {
"iam:PassRole",
"ec2:RequestSpotInstances",
},
# Prerequisite: Existing EC2 instance with admin role attached
"EC2ModifyInstanceAttribute": {
"ec2:ModifyInstanceAttribute",
"ec2:StopInstances",
"ec2:StartInstances",
},
# Prerequisite: Existing launch template used by instances with admin role
"EC2ModifyLaunchTemplate": {
"ec2:CreateLaunchTemplateVersion",
"ec2:ModifyLaunchTemplate",
},
# EC2 Instance Connect privilege escalation
# Prerequisite: Running EC2 with Instance Connect enabled and admin role
"EC2InstanceConnect+SendSSHPublicKey": {
"ec2-instance-connect:SendSSHPublicKey",
"ec2:DescribeInstances",
},
# Lambda-based privilege escalation patterns
"PassRole+CreateLambda+Invoke": {
"iam:PassRole",
"lambda:CreateFunction",
@@ -45,68 +117,131 @@ privilege_escalation_policies_combination = {
"dynamodb:CreateTable",
"dynamodb:PutItem",
},
"PassRole+GlueEndpoint": {
"PassRole+CreateLambda+AddPermission": {
"iam:PassRole",
"lambda:CreateFunction",
"lambda:AddPermission",
},
# Prerequisite: Existing Lambda function with admin execution role
"lambda:UpdateFunctionCode": {"lambda:UpdateFunctionCode"},
# Prerequisite: Existing Lambda function with admin execution role
"lambda:UpdateFunctionConfiguration": {"lambda:UpdateFunctionConfiguration"},
# Prerequisite: Existing Lambda function with admin execution role
"UpdateFunctionCode+InvokeFunction": {
"lambda:UpdateFunctionCode",
"lambda:InvokeFunction",
},
# Prerequisite: Existing Lambda function with admin execution role
"UpdateFunctionCode+AddPermission": {
"lambda:UpdateFunctionCode",
"lambda:AddPermission",
},
# Glue-based privilege escalation patterns
"PassRole+GlueCreateDevEndpoint": {
"iam:PassRole",
"glue:CreateDevEndpoint",
"glue:GetDevEndpoint",
},
"PassRole+GlueEndpoints": {
# Prerequisite: Existing Glue dev endpoint with admin role
"GlueUpdateDevEndpoint": {"glue:UpdateDevEndpoint"},
"PassRole+GlueCreateJob+StartJobRun": {
"iam:PassRole",
"glue:CreateDevEndpoint",
"glue:GetDevEndpoints",
"glue:CreateJob",
"glue:StartJobRun",
},
"PassRole+CloudFormation": {
"PassRole+GlueCreateJob+CreateTrigger": {
"iam:PassRole",
"glue:CreateJob",
"glue:CreateTrigger",
},
# Prerequisite: Existing Glue job
"PassRole+GlueUpdateJob+StartJobRun": {
"iam:PassRole",
"glue:UpdateJob",
"glue:StartJobRun",
},
# Prerequisite: Existing Glue job
"PassRole+GlueUpdateJob+CreateTrigger": {
"iam:PassRole",
"glue:UpdateJob",
"glue:CreateTrigger",
},
# CloudFormation-based privilege escalation patterns
"PassRole+CloudFormationCreateStack": {
"iam:PassRole",
"cloudformation:CreateStack",
"cloudformation:DescribeStacks",
},
# Prerequisite: Existing CloudFormation stack with admin service role
"CloudFormationUpdateStack": {"cloudformation:UpdateStack"},
"PassRole+CloudFormationCreateStackSet": {
"iam:PassRole",
"cloudformation:CreateStackSet",
"cloudformation:CreateStackInstances",
},
# Prerequisite: Existing CloudFormation StackSet
"PassRole+CloudFormationUpdateStackSet": {
"iam:PassRole",
"cloudformation:UpdateStackSet",
},
# Prerequisite: Existing CloudFormation stack with admin service role
"CloudFormationChangeSet": {
"cloudformation:CreateChangeSet",
"cloudformation:ExecuteChangeSet",
},
# DataPipeline-based privilege escalation patterns
"PassRole+DataPipeline": {
"iam:PassRole",
"datapipeline:CreatePipeline",
"datapipeline:PutPipelineDefinition",
"datapipeline:ActivatePipeline",
},
"GlueUpdateDevEndpoint": {"glue:UpdateDevEndpoint"},
"lambda:UpdateFunctionCode": {"lambda:UpdateFunctionCode"},
"lambda:UpdateFunctionConfiguration": {"lambda:UpdateFunctionConfiguration"},
# CodeStar-based privilege escalation patterns
"PassRole+CodeStar": {
"iam:PassRole",
"codestar:CreateProject",
},
# CodeBuild-based privilege escalation patterns
"PassRole+CodeBuildCreateProject+StartBuild": {
"iam:PassRole",
"codebuild:CreateProject",
"codebuild:StartBuild",
},
"PassRole+CodeBuildCreateProject+StartBuildBatch": {
"iam:PassRole",
"codebuild:CreateProject",
"codebuild:StartBuildBatch",
},
# Prerequisite: Existing CodeBuild project with admin service role
"CodeBuildStartBuild": {"codebuild:StartBuild"},
# Prerequisite: Existing CodeBuild project with admin service role
"CodeBuildStartBuildBatch": {"codebuild:StartBuildBatch"},
# AutoScaling-based privilege escalation patterns
"PassRole+CreateAutoScaling": {
"iam:PassRole",
"autoscaling:CreateAutoScalingGroup",
"autoscaling:CreateLaunchConfiguration",
},
# Prerequisite: Existing Auto Scaling group
"PassRole+UpdateAutoScaling": {
"iam:PassRole",
"autoscaling:UpdateAutoScalingGroup",
"autoscaling:CreateLaunchConfiguration",
},
"iam:CreateAccessKey": {"iam:CreateAccessKey"},
"iam:CreateLoginProfile": {"iam:CreateLoginProfile"},
"iam:UpdateLoginProfile": {"iam:UpdateLoginProfile"},
"iam:AttachUserPolicy": {"iam:AttachUserPolicy"},
"iam:AttachGroupPolicy": {"iam:AttachGroupPolicy"},
"iam:AttachRolePolicy": {"iam:AttachRolePolicy"},
"AssumeRole+AttachRolePolicy": {"sts:AssumeRole", "iam:AttachRolePolicy"},
"iam:PutGroupPolicy": {"iam:PutGroupPolicy"},
"iam:PutRolePolicy": {"iam:PutRolePolicy"},
"AssumeRole+PutRolePolicy": {"sts:AssumeRole", "iam:PutRolePolicy"},
"iam:PutUserPolicy": {"iam:PutUserPolicy"},
"iam:AddUserToGroup": {"iam:AddUserToGroup"},
"iam:UpdateAssumeRolePolicy": {"iam:UpdateAssumeRolePolicy"},
"AssumeRole+UpdateAssumeRolePolicy": {
"sts:AssumeRole",
"iam:UpdateAssumeRolePolicy",
},
# AgentCore privilege escalation patterns
"PassRole+AgentCoreCreateInterpreter+InvokeInterpreter": {
"iam:PassRole",
"bedrock-agentcore:CreateCodeInterpreter",
"bedrock-agentcore:InvokeCodeInterpreter",
},
# ECS-based privilege escalation patterns
"PassRole+ECS+RegisterTaskDef+CreateService": {
"iam:PassRole",
"ecs:RegisterTaskDefinition",
"ecs:CreateService",
},
"PassRole+ECS+RegisterTaskDef+RunTask": {
"iam:PassRole",
"ecs:RegisterTaskDefinition",
"ecs:RunTask",
},
"PassRole+ECS+RegisterTaskDef+StartTask": {
"iam:PassRole",
"ecs:RegisterTaskDefinition",
"ecs:StartTask",
},
# Reference: https://labs.reversec.com/posts/2025/08/another-ecs-privilege-escalation-path
"PassRole+ECS+StartTask": {
"iam:PassRole",
@@ -114,10 +249,58 @@ privilege_escalation_policies_combination = {
"ecs:RegisterContainerInstance",
"ecs:DeregisterContainerInstance",
},
# Prerequisite: Existing ECS cluster and task definition with admin role
"PassRole+ECS+RunTask": {
"iam:PassRole",
"ecs:RunTask",
},
# SageMaker-based privilege escalation patterns
"PassRole+SageMakerCreateNotebookInstance": {
"iam:PassRole",
"sagemaker:CreateNotebookInstance",
},
"PassRole+SageMakerCreateTrainingJob": {
"iam:PassRole",
"sagemaker:CreateTrainingJob",
},
"PassRole+SageMakerCreateProcessingJob": {
"iam:PassRole",
"sagemaker:CreateProcessingJob",
},
# Prerequisite: Existing SageMaker notebook instance with admin role
"SageMakerCreatePresignedNotebookInstanceUrl": {
"sagemaker:CreatePresignedNotebookInstanceUrl",
},
# Prerequisite: Existing SageMaker notebook instance with admin role
"SageMakerNotebookLifecycleConfig": {
"sagemaker:CreateNotebookInstanceLifecycleConfig",
"sagemaker:StopNotebookInstance",
"sagemaker:UpdateNotebookInstance",
"sagemaker:StartNotebookInstance",
},
# SSM-based privilege escalation patterns
# Prerequisite: Running EC2 with SSM agent and admin instance profile
"SSMStartSession": {"ssm:StartSession"},
# Prerequisite: Running EC2 with SSM agent and admin instance profile
"SSMSendCommand": {"ssm:SendCommand"},
# AppRunner-based privilege escalation patterns
"PassRole+AppRunnerCreateService": {
"iam:PassRole",
"apprunner:CreateService",
},
# Prerequisite: Existing App Runner service with admin role
"AppRunnerUpdateService": {"apprunner:UpdateService"},
# Bedrock AgentCore privilege escalation patterns
"PassRole+AgentCoreCreateInterpreter+InvokeInterpreter": {
"iam:PassRole",
"bedrock-agentcore:CreateCodeInterpreter",
"bedrock-agentcore:InvokeCodeInterpreter",
},
# Prerequisite: Existing Bedrock code interpreter with admin role
"AgentCoreSessionInvoke": {
"bedrock-agentcore:StartCodeInterpreterSession",
"bedrock-agentcore:InvokeCodeInterpreter",
},
# TO-DO: We have to handle AssumeRole just if the resource is * and without conditions
# "sts:AssumeRole": {"sts:AssumeRole"},
}
@@ -1,30 +1,37 @@
{
"Provider": "azure",
"CheckID": "monitor_alert_create_policy_assignment",
"CheckTitle": "Ensure that Activity Log Alert exists for Create Policy Assignment",
"CheckTitle": "Subscription has an Azure Monitor activity log alert for policy assignment creation",
"CheckType": [],
"ServiceName": "monitor",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "Monitor",
"Severity": "medium",
"ResourceType": "microsoft.insights/activitylogalerts",
"ResourceGroup": "monitoring",
"Description": "Create an activity log alert for the Create Policy Assignment event.",
"Risk": "Monitoring for create policy assignment events gives insight into changes done in 'Azure policy - assignments' and can reduce the time it takes to detect unsolicited changes.",
"RelatedUrl": "https://azure.microsoft.com/en-us/updates/classic-alerting-monitoring-retirement",
"Description": "**Azure Monitor Activity Log alert** configurations are assessed for an **activity log alert** on `Microsoft.Authorization/policyAssignments/write`, indicating monitoring of newly created **Azure Policy assignments**",
"Risk": "Absent alerts on new policy assignments, unauthorized or accidental changes can silently weaken governance. Adversaries could assign permissive policies or replace deny rules, enabling misconfigurations, privilege expansion, and data exposure-degrading **integrity** and threatening **confidentiality** and **availability**.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/dotnet/api/azure.resourcemanager.monitor.activitylogalertresource?view=azure-dotnet",
"https://learn.microsoft.com/en-in/azure/azure-monitor/alerts/alerts-create-activity-log-alert-rule?tabs=activity-log",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/ActivityLog/create-alert-for-create-policy-assignment-events.html"
],
"Remediation": {
"Code": {
"CLI": "az monitor activity-log alert create --resource-group '<resource group name>' --condition category=Administrative and operationName=Microsoft.Authorization/policyAssignments/write and level=<verbose | information | warning | error | critical> --scope '/subscriptions/<subscription ID>' --name '<activity log rule name>' -- subscription <subscription ID> --action-group <action group ID> --location global",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/azure/ActivityLog/create-alert-for-create-policy-assignment-events.html#trendmicro",
"Terraform": ""
"CLI": "az monitor activity-log alert create --name '<activity log rule name>' --resource-group '<resource group name>' --location global --scopes '/subscriptions/<subscription ID>' --condition \"category=Administrative and operationName=Microsoft.Authorization/policyAssignments/write\" --enabled true",
"NativeIaC": "```bicep\n// Azure Monitor Activity Log Alert for Policy Assignment creation\nresource activityLogAlert 'Microsoft.Insights/activityLogAlerts@2020-10-01' = {\n name: '<example_resource_name>'\n location: 'global'\n properties: {\n enabled: true\n scopes: [ '/subscriptions/<subscription ID>' ]\n condition: {\n allOf: [\n {\n field: 'category'\n equals: 'Administrative' // Critical: filter Activity Log category to Administrative\n }\n {\n field: 'operationName'\n equals: 'Microsoft.Authorization/policyAssignments/write' // Critical: alert on Policy Assignment creation\n }\n ]\n }\n }\n}\n```",
"Other": "1. In the Azure Portal, go to Monitor > Alerts > Alert rules\n2. Click + Create > Alert rule\n3. Scope: Select the target Subscription and click Apply\n4. Condition: Choose Activity log, then set Category = Administrative and Operation name = Microsoft.Authorization/policyAssignments/write; click Apply\n5. Actions: Skip or select an existing Action group (optional)\n6. Details: Enter a Name and ensure Enable alert rule upon creation is checked\n7. Click Review + create, then Create",
"Terraform": "```hcl\n# Azure Monitor Activity Log Alert for Policy Assignment creation\nresource \"azurerm_monitor_activity_log_alert\" \"<example_resource_name>\" {\n name = \"<example_resource_name>\"\n resource_group_name = \"<example_resource_name>\"\n location = \"global\"\n scopes = [\"/subscriptions/<subscription ID>\"]\n\n criteria {\n category = \"Administrative\" # Critical: Activity Log category\n operation_name = \"Microsoft.Authorization/policyAssignments/write\" # Critical: Policy Assignment creation\n }\n}\n```"
},
"Recommendation": {
"Text": "1. Navigate to the Monitor blade. 2. Select Alerts. 3. Select Create. 4. Select Alert rule. 5. Under Filter by subscription, choose a subscription. 6. Under Filter by resource type, select Policy assignment (policyAssignments). 7. Under Filter by location, select All. 8. From the results, select the subscription. 9. Select Done. 10. Select the Condition tab. 11. Under Signal name, click Create policy assignment (Microsoft.Authorization/policyAssignments). 12. Select the Actions tab. 13. To use an existing action group, click elect action groups. To create a new action group, click Create action group. Fill out the appropriate details for the selection. 14. Select the Details tab. 15. Select a Resource group, provide an Alert rule name and an optional Alert rule description. 16. Click Review + create. 17. Click Create.",
"Url": "https://docs.microsoft.com/en-in/azure/azure-monitor/platform/alerts-activity-log"
"Text": "Implement an **activity log alert** for `Microsoft.Authorization/policyAssignments/write` and route to an action group for timely response.\n\nApply across all subscriptions, restrict assignment rights (**least privilege**), require change approval, and integrate notifications with your SIEM for **defense in depth**.",
"Url": "https://hub.prowler.com/check/monitor_alert_create_policy_assignment"
}
},
"Categories": [],
"Categories": [
"logging"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "By default, no monitoring alerts are created."
@@ -1,30 +1,37 @@
{
"Provider": "azure",
"CheckID": "monitor_alert_create_update_nsg",
"CheckTitle": "Ensure that Activity Log Alert exists for Create or Update Network Security Group",
"CheckTitle": "Subscription has an Activity Log alert for Network Security Group create or update operations",
"CheckType": [],
"ServiceName": "monitor",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "Monitor",
"ResourceType": "microsoft.insights/activitylogalerts",
"ResourceGroup": "monitoring",
"Description": "Create an Activity Log Alert for the Create or Update Network Security Group event.",
"Risk": "Monitoring for Create or Update Network Security Group events gives insight into network access changes and may reduce the time it takes to detect suspicious activity.",
"RelatedUrl": "https://docs.microsoft.com/en-in/azure/azure-monitor/platform/alerts-activity-log",
"Description": "**Azure Monitor Activity Log alert** monitors **Network Security Group** changes via the `Microsoft.Network/networkSecurityGroups/write` operation to capture create/update events across the subscription",
"Risk": "Lack of alerting on NSG changes allows **unauthorized network policy modifications** to go unnoticed. Adversaries or mistakes could open ports, reduce segmentation, and enable **lateral movement**, impacting data **confidentiality** and service **availability** through exposure or disruption of critical traffic",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-in/azure/azure-monitor/alerts/alerts-create-activity-log-alert-rule?tabs=activity-log",
"https://learn.microsoft.com/en-us/azure/azure-monitor/platform/activity-log-schema",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/ActivityLog/create-update-network-security-group-rule-alert-in-use.html"
],
"Remediation": {
"Code": {
"CLI": "az monitor activity-log alert create --resource-group '<resource group name>' --condition category=Administrative and operationName=Microsoft.Network/networkSecurityGroups/write and level=verbose --scope '/subscriptions/<subscription ID>' --name '<activity log rule name>' --subscription <subscription id> --action-group <action group ID> --location global",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/azure/ActivityLog/create-update-network-security-group-rule-alert-in-use.html#trendmicro",
"Terraform": ""
"CLI": "az monitor activity-log alert create --resource-group '<example_resource_name>' --name '<example_resource_name>' --scopes '/subscriptions/<subscription ID>' --condition \"category=Administrative and operationName=Microsoft.Network/networkSecurityGroups/write\" --location global",
"NativeIaC": "```bicep\n// Activity Log alert for NSG create/update\nresource alert 'Microsoft.Insights/activityLogAlerts@2020-10-01' = {\n name: '<example_resource_name>'\n location: 'Global'\n properties: {\n scopes: [ subscription().id ]\n condition: {\n allOf: [\n { field: 'category', equals: 'Administrative' }\n { field: 'operationName', equals: 'Microsoft.Network/networkSecurityGroups/write' } // Critical: triggers on NSG create/update\n ]\n }\n enabled: true // Ensures the alert is active\n }\n}\n```",
"Other": "1. In the Azure portal, go to Monitor > Alerts > Alert rules > Create\n2. Scope: Select your subscription and click Apply\n3. Condition: Choose Activity log, set Category to Administrative, set Operation name to Microsoft.Network/networkSecurityGroups/write, then Done\n4. Actions: Skip (optional)\n5. Details: Name the rule and set Region to Global, ensure Enable upon creation is checked\n6. Review + create > Create",
"Terraform": "```hcl\n# Activity Log alert for NSG create/update\nresource \"azurerm_monitor_activity_log_alert\" \"<example_resource_name>\" {\n name = \"<example_resource_name>\"\n resource_group_name = \"<example_resource_name>\"\n scopes = [\"/subscriptions/<subscription_id>\"]\n\n criteria {\n category = \"Administrative\"\n operation_name = \"Microsoft.Network/networkSecurityGroups/write\" # Critical: triggers on NSG create/update\n }\n}\n```"
},
"Recommendation": {
"Text": "1. Navigate to the Monitor blade. 2. Select Alerts. 3. Select Create. 4. Select Alert rule. 5. Under Filter by subscription, choose a subscription. 6. Under Filter by resource type, select Network security groups. 7. Under Filter by location, select All. 8. From the results, select the subscription. 9. Select Done. 10. Select the Condition tab. 11. Under Signal name, click Create or Update Network Security Group (Microsoft.Network/networkSecurityGroups). 12. Select the Actions tab. 13. To use an existing action group, click Select action groups. To create a new action group, click Create action group. Fill out the appropriate details for the selection. 14. Select the Details tab. 15. Select a Resource group, provide an Alert rule name and an optional Alert rule description. 16. Click Review + create. 17. Click Create.",
"Url": "https://azure.microsoft.com/en-us/updates/classic-alerting-monitoring-retirement"
"Text": "Implement a subscription-wide **Activity Log alert** for NSG change operations and route notifications to an **action group** for rapid triage.\n\nApply **least privilege** for change tooling, enforce **change management**, and add complementary alerts for `Microsoft.Network/networkSecurityGroups/securityRules/write` and `.../delete`. *Integrate with SIEM for correlation*",
"Url": "https://hub.prowler.com/check/monitor_alert_create_update_nsg"
}
},
"Categories": [],
"Categories": [
"logging"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "By default, no monitoring alerts are created."
@@ -1,30 +1,39 @@
{
"Provider": "azure",
"CheckID": "monitor_alert_create_update_public_ip_address_rule",
"CheckTitle": "Ensure that Activity Log Alert exists for Create or Update Public IP Address rule",
"CheckTitle": "Subscription has an Activity Log Alert for Public IP address create or update operations",
"CheckType": [],
"ServiceName": "monitor",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "Monitor",
"Severity": "medium",
"ResourceType": "microsoft.insights/activitylogalerts",
"ResourceGroup": "monitoring",
"Description": "Create an activity log alert for the Create or Update Public IP Addresses rule.",
"Risk": "Monitoring for Create or Update Public IP Address events gives insight into network access changes and may reduce the time it takes to detect suspicious activity.",
"RelatedUrl": "https://docs.microsoft.com/en-in/azure/azure-monitor/platform/alerts-activity-log",
"Description": "**Azure Monitor activity log alert** for **Public IP addresses** tracks `Microsoft.Network/publicIPAddresses/write` events at the subscription level, covering any creation or update of public IP resources.",
"Risk": "Without this alert, unauthorized or mistaken public IP changes can go unnoticed, exposing workloads to the Internet.\n- Confidentiality: unexpected ingress paths\n- Integrity: shadow endpoints for control\n- Availability: larger DDoS surface and outages",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-in/azure/azure-monitor/alerts/alerts-create-activity-log-alert-rule?tabs=activity-log",
"https://trendmicro.com/cloudoneconformity/knowledge-base/azure/ActivityLog/create-or-update-public-ip-alert.html",
"https://support.icompaas.com/support/solutions/articles/62000229918-ensure-that-activity-log-alert-exists-for-create-or-update-public-ip-address-rule",
"https://learn.microsoft.com/en-us/azure/virtual-network/ip-services/monitor-public-ip"
],
"Remediation": {
"Code": {
"CLI": "az monitor activity-log alert create --resource-group '<resource group name>' --condition category=Administrative and operationName=Microsoft.Network/publicIPAddresses/write and level=<verbose | information | warning | error | critical>--scope '/subscriptions/<subscription ID>' --name '<activity log rule name>' -- subscription <subscription id> --action-group <action group ID> --location global",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/azure/ActivityLog/create-or-update-public-ip-alert.html#trendmicro",
"Terraform": ""
"CLI": "az monitor activity-log alert create --resource-group <example_resource_name> --name <example_resource_name> --scopes /subscriptions/<example_resource_id> --condition \"category=Administrative and operationName=Microsoft.Network/publicIPAddresses/write\" --location global",
"NativeIaC": "```bicep\n// Activity Log Alert for Public IP create/update\nresource alert 'Microsoft.Insights/activityLogAlerts@2020-10-01' = {\n name: '<example_resource_name>'\n location: 'global'\n properties: {\n enabled: true\n scopes: ['/subscriptions/<example_resource_id>']\n condition: {\n allOf: [\n { field: 'category', equals: 'Administrative' }\n { field: 'operationName', equals: 'Microsoft.Network/publicIPAddresses/write' } // Critical: alerts on Public IP create/update\n ]\n }\n }\n}\n```",
"Other": "1. In Azure Portal, go to Monitor > Alerts > Alert rules > Create\n2. Scope: Select your subscription and click Done\n3. Condition: Choose Activity log, then select the signal \"Create or Update Public Ip Address (publicIPAddresses)\"\n4. Details: Enter an alert rule name; Region: Global; Ensure Enable alert rule upon creation is checked\n5. Click Review + create, then Create",
"Terraform": "```hcl\n# Activity Log Alert for Public IP create/update\nresource \"azurerm_monitor_activity_log_alert\" \"<example_resource_name>\" {\n name = \"<example_resource_name>\"\n resource_group_name = \"<example_resource_name>\"\n scopes = [\"/subscriptions/<example_resource_id>\"]\n\n criteria {\n category = \"Administrative\"\n operation_name = \"Microsoft.Network/publicIPAddresses/write\" # Critical: alerts on Public IP create/update\n }\n}\n```"
},
"Recommendation": {
"Text": "1. Navigate to the Monitor blade. 2. Select Alerts. 3. Select Create. 4. Select Alert rule. 5. Under Filter by subscription, choose a subscription. 6. Under Filter by resource type, select Public IP addresses. 7. Under Filter by location, select All. 8. From the results, select the subscription. 9. Select Done. 10. Select the Condition tab. 11. Under Signal name, click Create or Update Public Ip Address (Microsoft.Network/publicIPAddresses). 12. Select the Actions tab. 13. To use an existing action group, click Select action groups. To create a new action group, click Create action group. Fill out the appropriate details for the selection. 14. Select the Details tab. 15. Select a Resource group, provide an Alert rule name and an optional Alert rule description. 16. Click Review + create. 17. Click Create.",
"Url": "https://azure.microsoft.com/en-us/updates/classic-alerting-monitoring-retirement"
"Text": "Create a subscription-wide **activity log alert** on `Microsoft.Network/publicIPAddresses/write` and route it to an **action group**.\n\nEnforce **least privilege** for IP management, apply **change control**, and use **defense in depth** (private endpoints, bastions, VPN) to minimize public exposure and speed response.",
"Url": "https://hub.prowler.com/check/monitor_alert_create_update_public_ip_address_rule"
}
},
"Categories": [],
"Categories": [
"logging",
"forensics-ready"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "By default, no monitoring alerts are created."
@@ -1,30 +1,37 @@
{
"Provider": "azure",
"CheckID": "monitor_alert_create_update_security_solution",
"CheckTitle": "Ensure that Activity Log Alert exists for Create or Update Security Solution",
"CheckTitle": "Subscription has Activity Log alert for Security Solution create or update",
"CheckType": [],
"ServiceName": "monitor",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "Monitor",
"Severity": "medium",
"ResourceType": "microsoft.insights/activitylogalerts",
"ResourceGroup": "monitoring",
"Description": "Create an activity log alert for the Create or Update Security Solution event.",
"Risk": "Monitoring for Create or Update Security Solution events gives insight into changes to the active security solutions and may reduce the time it takes to detect suspicious activity.",
"RelatedUrl": "https://docs.microsoft.com/en-in/azure/azure-monitor/platform/alerts-activity-log",
"Description": "**Azure Monitor activity log alert** is configured to capture **Security Solutions** create/update operations (`Microsoft.Security/securitySolutions/write`) at subscription scope.",
"Risk": "Without this alert, **unauthorized or mistaken changes** to security tooling can go undetected. Attackers could disable defenses, alter integrations, or weaken policies, eroding the **integrity** of controls, creating blind spots that threaten **confidentiality**, and delaying incident response.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://azure.microsoft.com/en-us/updates/classic-alerting-monitoring-retirement",
"https://learn.microsoft.com/en-in/azure/azure-monitor/alerts/alerts-create-activity-log-alert-rule?tabs=activity-log",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/ActivityLog/create-or-update-security-solution-alert.html#trendmicro"
],
"Remediation": {
"Code": {
"CLI": "az monitor activity-log alert create --resource-group '<resource group name>' --condition category=Administrative and operationName=Microsoft.Security/securitySolutions/write and level=<verbose | information | warning | error | critical>--scope '/subscriptions/<subscription ID>' --name '<activity log rule name>' -- subscription <subscription id> --action-group <action group ID> --location global",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/azure/ActivityLog/create-or-update-security-solution-alert.html#trendmicro",
"Terraform": ""
"CLI": "az monitor activity-log alert create --name \"<activity log rule name>\" --resource-group \"<example_resource_name>\" --scopes \"/subscriptions/<example_resource_id>\" --condition \"category=Administrative and operationName=Microsoft.Security/securitySolutions/write\" --location Global",
"NativeIaC": "```bicep\n// Activity Log Alert for Security Solution create/update\nresource activityLogAlert 'Microsoft.Insights/activityLogAlerts@2020-10-01' = {\n name: '<example_resource_name>'\n location: 'Global'\n properties: {\n scopes: [ '/subscriptions/<example_resource_id>' ]\n condition: {\n allOf: [\n {\n field: 'category'\n equals: 'Administrative'\n }\n {\n field: 'operationName' // Critical: match Security Solution create/update\n equals: 'Microsoft.Security/securitySolutions/write' // Triggers on this operation\n }\n ]\n }\n enabled: true\n }\n}\n```",
"Other": "1. In the Azure portal, go to Monitor > Alerts > + Create > Alert rule\n2. Scope: Select your Subscription and click Apply\n3. Condition: Choose Activity log, set Signal name to Administrative, then add a filter Operation name = Microsoft.Security/securitySolutions/write\n4. Actions: Skip (no action group required)\n5. Details: Enter a Name, set Region to Global, ensure Enable alert rule upon creation is checked\n6. Review + create > Create",
"Terraform": "```hcl\n# Activity Log Alert for Security Solution create/update\nresource \"azurerm_monitor_activity_log_alert\" \"<example_resource_name>\" {\n name = \"<example_resource_name>\"\n resource_group_name = \"<example_resource_name>\"\n scopes = [\"/subscriptions/<example_resource_id>\"]\n\n criteria {\n category = \"Administrative\"\n operation_name = \"Microsoft.Security/securitySolutions/write\" # Critical: fires on Security Solution create/update\n }\n}\n```"
},
"Recommendation": {
"Text": "1. Navigate to the Monitor blade. 2. Select Alerts. 3. Select Create. 4. Select Alert rule. 5. Under Filter by subscription, choose a subscription. 6. Under Filter by resource type, select Security Solutions (securitySolutions). 7. Under Filter by location, select All. 8. From the results, select the subscription. 9. Select Done. 10. Select the Condition tab. 11. Under Signal name, click Create or Update Security Solutions (Microsoft.Security/securitySolutions). 12. Select the Actions tab. 13. To use an existing action group, click Select action groups. To create a new action group, click Create action group. Fill out the appropriate details for the selection. 14. Select the Details tab. 15. Select a Resource group, provide an Alert rule name and an optional Alert rule description. 16. Click Review + create. 17. Click Create.",
"Url": "https://azure.microsoft.com/en-us/updates/classic-alerting-monitoring-retirement"
"Text": "Configure an **activity log alert** for `Microsoft.Security/securitySolutions/write` and route it to action groups for prompt notification/automation.\n\nApply **least privilege**, require **change control**, and forward alerts to a central SIEM to strengthen **defense in depth**.",
"Url": "https://hub.prowler.com/check/monitor_alert_create_update_security_solution"
}
},
"Categories": [],
"Categories": [
"logging"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "By default, no monitoring alerts are created."
@@ -1,30 +1,37 @@
{
"Provider": "azure",
"CheckID": "monitor_alert_create_update_sqlserver_fr",
"CheckTitle": "Ensure that Activity Log Alert exists for Create or Update SQL Server Firewall Rule",
"CheckTitle": "Subscription has an Activity Log alert for SQL Server firewall rule create or update events",
"CheckType": [],
"ServiceName": "monitor",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "Monitor",
"Severity": "medium",
"ResourceType": "microsoft.insights/activitylogalerts",
"ResourceGroup": "monitoring",
"Description": "Create an activity log alert for the Create or Update SQL Server Firewall Rule event.",
"Risk": "Monitoring for Create or Update SQL Server Firewall Rule events gives insight into network access changes and may reduce the time it takes to detect suspicious activity.",
"RelatedUrl": "https://docs.microsoft.com/en-in/azure/azure-monitor/platform/alerts-activity-log",
"Description": "**Azure Monitor activity log alerts** are configured for **Azure SQL Server firewall rule changes**, targeting the `Microsoft.Sql/servers/firewallRules/write` operation.\n\nThis evaluates whether notifications or automated actions are set when firewall rules are created or updated.",
"Risk": "Without alerting on firewall rule changes, unauthorized or accidental openings can remain unnoticed, exposing databases to untrusted networks.\n\nThis harms **confidentiality** (data exfiltration via widened IP ranges) and **integrity** (unauthorized queries), while increasing attacker dwell time.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://azure.microsoft.com/en-us/updates/classic-alerting-monitoring-retirement",
"https://learn.microsoft.com/en-in/azure/azure-monitor/alerts/alerts-create-activity-log-alert-rule?tabs=activity-log",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/ActivityLog/create-or-update-or-delete-sql-server-firewall-rule-alert.html#trendmicro"
],
"Remediation": {
"Code": {
"CLI": "az monitor activity-log alert create --resource-group '<resource group name>' --condition category=Administrative and operationName=Microsoft.Sql/servers/firewallRules/write and level=<verbose | information | warning | error | critical>--scope '/subscriptions/<subscription ID>' --name '<activity log rule name>' -- subscription <subscription id> --action-group <action group ID> --location global",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/azure/ActivityLog/create-or-update-or-delete-sql-server-firewall-rule-alert.html#trendmicro",
"Terraform": ""
"CLI": "az monitor activity-log alert create --name <activity_log_rule_name> --resource-group <resource_group_name> --scopes /subscriptions/<subscription_id> --condition \"category=Administrative and operationName=Microsoft.Sql/servers/firewallRules/write\" --location global",
"NativeIaC": "```bicep\n// Activity Log alert for SQL Server firewall rule create/update\nresource example_activity_log_alert 'Microsoft.Insights/activityLogAlerts@2020-10-01' = {\n name: '<example_resource_name>'\n location: 'Global'\n properties: {\n enabled: true\n scopes: [ '/subscriptions/<example_resource_id>' ]\n condition: {\n allOf: [\n {\n field: 'category'\n equals: 'Administrative'\n }\n {\n field: 'operationName'\n equals: 'Microsoft.Sql/servers/firewallRules/write' // Critical: alert on SQL Server firewall rule create/update\n }\n ]\n }\n }\n}\n```",
"Other": "1. In the Azure portal, go to Monitor > Alerts > + Create > Alert rule\n2. Scope: Select the subscription and click Done\n3. Condition: Choose Signal type \"Activity log\", then set\n - Category: Administrative\n - Operation name: Microsoft.Sql/servers/firewallRules/write\n Click Done\n4. Actions: Skip (no action group required)\n5. Details: Enter an Alert rule name and ensure Enable alert rule upon creation is checked\n6. Review + create > Create",
"Terraform": "```hcl\n# Activity Log alert for SQL Server firewall rule create/update\nresource \"azurerm_monitor_activity_log_alert\" \"<example_resource_name>\" {\n name = \"<example_resource_name>\"\n resource_group_name = \"<example_resource_name>\"\n scopes = [\"/subscriptions/<example_resource_id>\"]\n\n criteria {\n category = \"Administrative\"\n operation_name = \"Microsoft.Sql/servers/firewallRules/write\" # Critical: alert on SQL Server firewall rule create/update\n }\n}\n```"
},
"Recommendation": {
"Text": "1. Navigate to the Monitor blade. 2. Select Alerts. 3. Select Create. 4. Select Alert rule. 5. Under Filter by subscription, choose a subscription. 6. Under Filter by resource type, select Server Firewall Rule (servers/firewallRules). 7. Under Filter by location, select All. 8. From the results, select the subscription. 9. Select Done. 10. Select the Condition tab. 11. Under Signal name, click Create/Update server firewall rule (Microsoft.Sql/servers/firewallRules). 12. Select the Actions tab. 13. To use an existing action group, click Select action groups. To create a new action group, click Create action group. Fill out the appropriate details for the selection. 14. Select the Details tab. 15. Select a Resource group, provide an Alert rule name and an optional Alert rule description. 16. Click Review + create. 17. Click Create.",
"Url": "https://azure.microsoft.com/en-us/updates/classic-alerting-monitoring-retirement"
"Text": "Enable an activity log alert for `Microsoft.Sql/servers/firewallRules/write` and route it to responsive action groups.\n\nApply **least privilege** for firewall management, enforce change approvals, and use **defense in depth**: prefer **private endpoints** and avoid broad public network access.",
"Url": "https://hub.prowler.com/check/monitor_alert_create_update_sqlserver_fr"
}
},
"Categories": [],
"Categories": [
"logging"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "By default, no monitoring alerts are created."
@@ -1,30 +1,38 @@
{
"Provider": "azure",
"CheckID": "monitor_alert_delete_nsg",
"CheckTitle": "Ensure that Activity Log Alert exists for Delete Network Security Group",
"CheckTitle": "Subscription has an Activity Log alert for Network Security Group delete operations",
"CheckType": [],
"ServiceName": "monitor",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "Monitor",
"ResourceType": "microsoft.insights/activitylogalerts",
"ResourceGroup": "monitoring",
"Description": "Create an activity log alert for the Delete Network Security Group event.",
"Risk": "Monitoring for 'Delete Network Security Group' events gives insight into network access changes and may reduce the time it takes to detect suspicious activity.",
"RelatedUrl": "https://docs.microsoft.com/en-in/azure/azure-monitor/platform/alerts-activity-log",
"Description": "**Azure Monitor activity log alerts** include the NSG deletion signal (`Microsoft.Network/networkSecurityGroups/delete` or `Microsoft.ClassicNetwork/networkSecurityGroups/delete`). The finding indicates whether a subscription has an alert rule configured to trigger when a Network Security Group is deleted.",
"Risk": "Without alerting on **NSG deletions**, network segmentation can be removed unnoticed, exposing services to broad ingress/egress. Malicious actors or automation may delete NSGs to enable **lateral movement** and **data exfiltration**. Missing alerts delay response, impacting confidentiality and availability.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-in/azure/azure-monitor/alerts/alerts-create-activity-log-alert-rule?tabs=activity-log",
"https://azure.microsoft.com/en-us/updates/classic-alerting-monitoring-retirement",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/ActivityLog/delete-network-security-group-rule-alert-in-use.html#trendmicro"
],
"Remediation": {
"Code": {
"CLI": "az monitor activity-log alert create --resource-group '<resource group name>' --condition category=Administrative and operationName=Microsoft.Network/networkSecurityGroups/delete and level=<verbose | information | warning | error | critical>--scope '/subscriptions/<subscription ID>' --name '<activity log rule name>' -- subscription <subscription id> --action-group <action group ID> --location global",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/azure/ActivityLog/delete-network-security-group-rule-alert-in-use.html#trendmicro",
"Terraform": ""
"CLI": "az monitor activity-log alert create --name \"<activity log rule name>\" --resource-group \"<resource group name>\" --scopes \"/subscriptions/<subscription ID>\" --condition \"category=Administrative and operationName=Microsoft.Network/networkSecurityGroups/delete\" --location global",
"NativeIaC": "```bicep\n// Activity Log alert for NSG delete\nresource activityAlert 'Microsoft.Insights/activityLogAlerts@2020-10-01' = {\n name: '<example_resource_name>'\n location: 'Global'\n properties: {\n scopes: ['/subscriptions/<example_resource_id>']\n enabled: true\n condition: {\n allOf: [\n { field: 'category', equals: 'Administrative' } // Critical: filter Activity Log to Administrative category\n { field: 'operationName', equals: 'Microsoft.Network/networkSecurityGroups/delete' } // Critical: triggers on NSG delete\n ]\n }\n }\n}\n```",
"Other": "1. In Azure Portal, go to Monitor > Alerts > Alert rules\n2. Click + Create > Alert rule\n3. Scope: Select the target subscription and click Apply\n4. Condition: Choose Activity log, select the signal \"Delete Network Security Group\" (operation Microsoft.Network/networkSecurityGroups/delete); ensure Category is Administrative\n5. Details: Enter a name; leave other settings as default\n6. Click Review + create, then Create",
"Terraform": "```hcl\n# Activity Log alert for NSG delete\nresource \"azurerm_monitor_activity_log_alert\" \"example\" {\n name = \"<example_resource_name>\"\n resource_group_name = \"<example_resource_name>\"\n scopes = [\"/subscriptions/<example_resource_id>\"]\n\n criteria {\n category = \"Administrative\" # Critical: Activity Log category filter\n operation_name = \"Microsoft.Network/networkSecurityGroups/delete\" # Critical: alert on NSG delete\n }\n}\n```"
},
"Recommendation": {
"Text": "1. Navigate to the Monitor blade. 2. Select Alerts. 3. Select Create. 4. Select Alert rule. 5. Under Filter by subscription, choose a subscription. 6. Under Filter by resource type, select Network security groups. 7. Under Filter by location, select All. 8. From the results, select the subscription. 9. Select Done. 10. Select the Condition tab. 11. Under Signal name, click Delete Network Security Group (Microsoft.Network/networkSecurityGroups). 12. Select the Actions tab. 13. To use an existing action group, click Select action groups. To create a new action group, click Create action group. Fill out the appropriate details for the selection. 14. Select the Details tab. 15. Select a Resource group, provide an Alert rule name and an optional Alert rule description. 16. Click Review + create. 17. Click Create.",
"Url": "https://azure.microsoft.com/en-us/updates/classic-alerting-monitoring-retirement"
"Text": "Configure a subscription-wide **activity log alert** for the NSG delete operation (`Microsoft.Network/networkSecurityGroups/delete`; include Classic if applicable) and route notifications via **action groups**. Enforce **least privilege** for NSG changes, require **change control**, and integrate with your **SIEM** for correlation.",
"Url": "https://hub.prowler.com/check/monitor_alert_delete_nsg"
}
},
"Categories": [],
"Categories": [
"logging",
"forensics-ready"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "By default, no monitoring alerts are created."
@@ -1,30 +1,37 @@
{
"Provider": "azure",
"CheckID": "monitor_alert_delete_policy_assignment",
"CheckTitle": "Ensure that Activity Log Alert exists for Delete Policy Assignment",
"CheckTitle": "Subscription has an Activity Log alert for policy assignment deletion",
"CheckType": [],
"ServiceName": "monitor",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "Monitor",
"ResourceType": "microsoft.insights/activitylogalerts",
"ResourceGroup": "monitoring",
"Description": "Create an activity log alert for the Delete Policy Assignment event.",
"Risk": "Monitoring for delete policy assignment events gives insight into changes done in 'azure policy - assignments' and can reduce the time it takes to detect unsolicited changes.",
"RelatedUrl": "https://docs.microsoft.com/en-in/rest/api/monitor/activitylogalerts/createorupdate",
"Description": "**Azure Monitor Activity log alerts** for policy assignment deletions using the `Microsoft.Authorization/policyAssignments/delete` operation at subscription scope",
"Risk": "Without this alert, **policy assignment deletions** can go unnoticed, eroding configuration **integrity** and enabling governance drift. Malicious or accidental changes may remove guardrails, increasing exposure and threatening **confidentiality** of protected resources.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-in/azure/azure-monitor/alerts/alerts-create-activity-log-alert-rule?tabs=activity-log",
"https://learn.microsoft.com/en-in/rest/api/monitor/activity-log-alerts/create-or-update?view=rest-monitor-2020-10-01&tabs=HTTP",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/ActivityLog/delete-policy-assignment-alert-in-use.html#trendmicro"
],
"Remediation": {
"Code": {
"CLI": "az monitor activity-log alert create --resource-group '<resource group name>' --condition category=Administrative and operationName=Microsoft.Authorization/policyAssignments/delete and level=<verbose | information | warning | error | critical> --scope '/subscriptions/<subscription ID>' --name '<activity log rule name>' -- subscription <subscription id> --action-group <action group ID> --location global",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/azure/ActivityLog/delete-policy-assignment-alert-in-use.html#trendmicro",
"Terraform": ""
"CLI": "az monitor activity-log alert create --resource-group <example_resource_name> --name <example_resource_name> --scopes \"/subscriptions/<example_resource_id>\" --condition \"operationName=Microsoft.Authorization/policyAssignments/delete\" --location global",
"NativeIaC": "```bicep\n// Activity Log alert for Policy Assignment deletion\nresource alert 'Microsoft.Insights/activityLogAlerts@2020-10-01' = {\n name: '<example_resource_name>'\n location: 'Global'\n properties: {\n scopes: [\n '/subscriptions/<example_resource_id>'\n ]\n condition: {\n allOf: [\n {\n field: 'operationName'\n equals: 'Microsoft.Authorization/policyAssignments/delete' // CRITICAL: alerts on policy assignment deletion\n }\n ]\n }\n actions: {\n actionGroups: [] // Required property; empty keeps rule minimal\n }\n }\n}\n```",
"Other": "1. In Azure Portal, go to Monitor > Alerts > Alert rules\n2. Click + Create > Alert rule\n3. Scope: Select your subscription and click Apply\n4. Condition: Choose Activity log, then set Operation name equals \"Microsoft.Authorization/policyAssignments/delete\"\n5. Actions: Skip (optional)\n6. Details: Enter a name and set Enable alert rule upon creation\n7. Click Create",
"Terraform": "```hcl\n# Activity Log alert for Policy Assignment deletion\nresource \"azurerm_monitor_activity_log_alert\" \"<example_resource_name>\" {\n name = \"<example_resource_name>\"\n resource_group_name = \"<example_resource_name>\"\n scopes = [\"/subscriptions/<example_resource_id>\"]\n\n criteria {\n operation_name = \"Microsoft.Authorization/policyAssignments/delete\" # CRITICAL: alerts on policy assignment deletion\n }\n}\n```"
},
"Recommendation": {
"Text": "1. Navigate to the Monitor blade. 2. Select Alerts. 3. Select Create. 4. Select Alert rule. 5. Under Filter by subscription, choose a subscription. 6. Under Filter by resource type, select Policy assignment (policyAssignments). 7. Under Filter by location, select All. 8. From the results, select the subscription. 9. Select Done. 10. Select the Condition tab. 11. Under Signal name, click Delete policy assignment (Microsoft.Authorization/policyAssignments). 12. Select the Actions tab. 13. To use an existing action group, click Select action groups. To create a new action group, click Create action group. Fill out the appropriate details for the selection. 14. Select the Details tab. 15. Select a Resource group, provide an Alert rule name and an optional Alert rule description. 16. Click Review + create. 17. Click Create.",
"Url": "https://docs.microsoft.com/en-in/azure/azure-monitor/platform/alerts-activity-log"
"Text": "- Configure an activity log alert for `Microsoft.Authorization/policyAssignments/delete` and route to an action group.\n- Enforce **least privilege** and **separation of duties** for policy changes and require approvals.\n- Integrate alerts with your SIEM and define playbooks for rapid response.",
"Url": "https://hub.prowler.com/check/monitor_alert_delete_policy_assignment"
}
},
"Categories": [],
"Categories": [
"logging"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "By default, no monitoring alerts are created."
@@ -1,30 +1,37 @@
{
"Provider": "azure",
"CheckID": "monitor_alert_delete_public_ip_address_rule",
"CheckTitle": "Ensure that Activity Log Alert exists for Delete Public IP Address rule",
"CheckTitle": "Azure subscription has an Activity Log alert for public IP address deletion",
"CheckType": [],
"ServiceName": "monitor",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "Monitor",
"Severity": "medium",
"ResourceType": "microsoft.insights/activitylogalerts",
"ResourceGroup": "monitoring",
"Description": "Create an activity log alert for the Delete Public IP Address rule.",
"Risk": "Monitoring for Delete Public IP Address events gives insight into network access changes and may reduce the time it takes to detect suspicious activity.",
"RelatedUrl": "https://docs.microsoft.com/en-in/azure/azure-monitor/platform/alerts-activity-log",
"Description": "**Azure Monitor activity log alert** exists for the **Delete Public IP Address** operation (`Microsoft.Network/publicIPAddresses/delete`), capturing subscription-wide events when Public IP resources are removed.",
"Risk": "Unmonitored deletion of Public IPs can abruptly sever ingress/egress, break DNS and allowlists, and take services offline (**availability**). Attackers or misconfigurations can delete IPs to cause **DoS** or evade controls, and delayed visibility hinders **incident response** and **forensics**.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/ActivityLog/delete-public-ip-alert.html#trendmicro",
"https://azure.microsoft.com/en-us/updates/classic-alerting-monitoring-retirement",
"https://learn.microsoft.com/en-in/azure/azure-monitor/alerts/alerts-create-activity-log-alert-rule?tabs=activity-log"
],
"Remediation": {
"Code": {
"CLI": "az monitor activity-log alert create --resource-group '<resource group name>' --condition category=Administrative and operationName=Microsoft.Network/publicIPAddresses/delete and level=<verbose | information | warning | error | critical>--scope '/subscriptions/<subscription ID>' --name '<activity log rule name>' -- subscription <subscription id> --action-group <action group ID> --location global",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/azure/ActivityLog/delete-public-ip-alert.html#trendmicro",
"Terraform": ""
"CLI": "az monitor activity-log alert create --name <activity_log_rule_name> --resource-group <resource_group_name> --location global --scopes /subscriptions/<subscription_id> --condition category=Administrative and operationName=Microsoft.Network/publicIPAddresses/delete",
"NativeIaC": "```bicep\n// Activity Log alert for Public IP deletion\nresource alert 'Microsoft.Insights/activityLogAlerts@2020-10-01' = {\n name: '<example_resource_name>'\n location: 'Global'\n properties: {\n enabled: true\n scopes: [\n '/subscriptions/<example_resource_id>' // Scope the alert to the subscription\n ]\n condition: {\n allOf: [\n { field: 'category', equals: 'Administrative' }\n { field: 'operationName', equals: 'Microsoft.Network/publicIPAddresses/delete' } // Critical: triggers when a Public IP is deleted\n ]\n }\n }\n}\n```",
"Other": "1. In the Azure portal, go to Monitor > Alerts > + Create > Alert rule\n2. Scope: Select your subscription and click Apply\n3. Condition: Choose Activity log, then set Category = Administrative and Operation name = Microsoft.Network/publicIPAddresses/delete; click Apply\n4. Actions: Skip (no action group required to pass)\n5. Details: Enter an alert name, set Region to Global, ensure Enable alert rule upon creation is checked\n6. Review + create > Create",
"Terraform": "```hcl\nresource \"azurerm_monitor_activity_log_alert\" \"<example_resource_name>\" {\n name = \"<example_resource_name>\"\n resource_group_name = \"<example_resource_name>\"\n scopes = [\"/subscriptions/<example_resource_id>\"]\n\n criteria {\n category = \"Administrative\"\n operation_name = \"Microsoft.Network/publicIPAddresses/delete\" # Critical: alert when a Public IP is deleted\n }\n}\n```"
},
"Recommendation": {
"Text": "1. Navigate to the Monitor blade. 2. Select Alerts. 3. Select Create. 4. Select Alert rule. 5. Under Filter by subscription, choose a subscription. 6. Under Filter by resource type, select Public IP addresses. 7. Under Filter by location, select All. 8. From the results, select the subscription. 9. Select Done. 10. Select the Condition tab. 11. Under Signal name, click Delete Public Ip Address (Microsoft.Network/publicIPAddresses). 12. Select the Actions tab. 13. To use an existing action group, click Select action groups. To create a new action group, click Create action group. Fill out the appropriate details for the selection. 14. Select the Details tab. 15. Select a Resource group, provide an Alert rule name and an optional Alert rule description. 16. Click Review + create. 17. Click Create.",
"Url": "https://azure.microsoft.com/en-us/updates/classic-alerting-monitoring-retirement"
"Text": "Implement an activity log alert for `Microsoft.Network/publicIPAddresses/delete` and route it to an action group for rapid response.\n- Apply **least privilege** and change approval for IP deletions\n- Use **resource locks** on critical IPs\n- Centralize alerts in your SIEM and define runbooks for containment",
"Url": "https://hub.prowler.com/check/monitor_alert_delete_public_ip_address_rule"
}
},
"Categories": [],
"Categories": [
"logging"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "By default, no monitoring alerts are created."
@@ -1,30 +1,38 @@
{
"Provider": "azure",
"CheckID": "monitor_alert_delete_security_solution",
"CheckTitle": "Ensure that Activity Log Alert exists for Delete Security Solution",
"CheckTitle": "Subscription has an Azure Monitor Activity Log alert for Microsoft.Security/securitySolutions delete operations",
"CheckType": [],
"ServiceName": "monitor",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "Monitor",
"Severity": "medium",
"ResourceType": "microsoft.insights/activitylogalerts",
"ResourceGroup": "monitoring",
"Description": "Create an activity log alert for the Delete Security Solution event.",
"Risk": "Monitoring for Delete Security Solution events gives insight into changes to the active security solutions and may reduce the time it takes to detect suspicious activity.",
"RelatedUrl": "https://docs.microsoft.com/en-in/azure/azure-monitor/platform/alerts-activity-log",
"Description": "**Azure activity log alerts** monitor deletions of **Security Solutions** by targeting the operation `Microsoft.Security/securitySolutions/delete` at subscription scope.\n\nIdentifies whether notifications are configured for security solution removal events.",
"Risk": "Without this alert, **unauthorized or accidental deletions** of security tooling may go **unnoticed**, reducing the **availability** of protections and the **integrity** of monitoring. Adversaries can evade defenses, prolong dwell time, and enable **data exfiltration** under reduced visibility.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/ActivityLog/delete-security-solution-alert.html",
"https://learn.microsoft.com/en-in/azure/azure-monitor/alerts/alerts-create-activity-log-alert-rule?tabs=activity-log",
"https://learn.microsoft.com/en-us/cli/azure/monitor/activity-log/alert?view=azure-cli-latest",
"https://azure.microsoft.com/en-us/updates/classic-alerting-monitoring-retirement"
],
"Remediation": {
"Code": {
"CLI": "az monitor activity-log alert create --resource-group '<resource group name>' --condition category=Administrative and operationName=Microsoft.Security/securitySolutions/delete and level=<verbose | information | warning | error | critical>--scope '/subscriptions/<subscription ID>' --name '<activity log rule name>' -- subscription <subscription id> --action-group <action group ID> --location global",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/azure/ActivityLog/delete-security-solution-alert.html#trendmicro",
"Terraform": ""
"CLI": "az monitor activity-log alert create -g <example_resource_name> -n <example_resource_name> --condition operationName=Microsoft.Security/securitySolutions/delete --scope /subscriptions/<example_resource_id>",
"NativeIaC": "```bicep\n// Activity Log Alert for Security Solution delete\nresource alert 'Microsoft.Insights/activityLogAlerts@2020-10-01' = {\n name: '<example_resource_name>'\n location: 'global'\n properties: {\n enabled: true\n scopes: [ subscription().id ]\n condition: {\n allOf: [\n {\n field: 'operationName'\n equals: 'Microsoft.Security/securitySolutions/delete' // Critical: alerts on Security Solution delete\n }\n ]\n }\n }\n}\n```",
"Other": "1. In Azure portal, go to Monitor > Alerts > + Create > Alert rule\n2. Scope: Select your subscription and click Apply\n3. Condition: Click Add condition, search and select \"Delete Security Solutions (Microsoft.Security/securitySolutions)\", then Add\n4. Ensure no filters for Level or Status are set\n5. Details: Enter an Alert rule name and choose a resource group\n6. Create: Review + create, then Create",
"Terraform": "```hcl\n# Activity Log Alert for Security Solution delete\nresource \"azurerm_monitor_activity_log_alert\" \"<example_resource_name>\" {\n name = \"<example_resource_name>\"\n resource_group_name = \"<example_resource_name>\"\n scopes = [\"/subscriptions/<example_resource_id>\"]\n\n criteria {\n operation_name = \"Microsoft.Security/securitySolutions/delete\" # Critical: alerts on delete operation\n }\n}\n```"
},
"Recommendation": {
"Text": "1. Navigate to the Monitor blade. 2. Select Alerts. 3. Select Create. 4. Select Alert rule. 5. Under Filter by subscription, choose a subscription. 6. Under Filter by resource type, select Security Solutions (securitySolutions). 7. Under Filter by location, select All. 8. From the results, select the subscription. 9. Select Done. 10. Select the Condition tab. 11. Under Signal name, click Delete Security Solutions (Microsoft.Security/securitySolutions). 12. Select the Actions tab. 13. To use an existing action group, click Select action groups. To create a new action group, click Create action group. Fill out the appropriate details for the selection. 14. Select the Details tab. 15. Select a Resource group, provide an Alert rule name and an optional Alert rule description. 16. Click Review + create. 17. Click Create.curitySolutions). 7. Under Filter by location, select All. 8. From the results, select the subscription. 9. Select Done. 10. Select the Condition tab. 11. Under Signal name, click Create or Update Security Solutions (Microsoft.Security/securitySolutions). 12. Select the Actions tab. 13. To use an existing action group, click Select action groups. To create a new action group, click Create action group. Fill out the appropriate details for the selection. 14. Select the Details tab. 15. Select a Resource group, provide an Alert rule name and an optional Alert rule description. 16. Click Review + create. 17. Click Create.",
"Url": "https://azure.microsoft.com/en-us/updates/classic-alerting-monitoring-retirement"
"Text": "Configure a **dedicated activity log alert** for `Microsoft.Security/securitySolutions/delete` and route it to resilient **action groups** (email, chat, ticketing, SIEM). Apply **least privilege** and **resource locks** to deter tampering. Test alerting routinely and integrate it into **defense-in-depth** monitoring.",
"Url": "https://hub.prowler.com/check/monitor_alert_delete_security_solution"
}
},
"Categories": [],
"Categories": [
"logging"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "By default, no monitoring alerts are created."
@@ -1,30 +1,37 @@
{
"Provider": "azure",
"CheckID": "monitor_alert_delete_sqlserver_fr",
"CheckTitle": "Ensure that Activity Log Alert exists for Delete SQL Server Firewall Rule",
"CheckTitle": "Subscription has an Activity Log Alert for SQL Server firewall rule deletions",
"CheckType": [],
"ServiceName": "monitor",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "Monitor",
"Severity": "medium",
"ResourceType": "microsoft.insights/activitylogalerts",
"ResourceGroup": "monitoring",
"Description": "Create an activity log alert for the 'Delete SQL Server Firewall Rule.'",
"Risk": "Monitoring for Delete SQL Server Firewall Rule events gives insight into SQL network access changes and may reduce the time it takes to detect suspicious activity.",
"RelatedUrl": "https://docs.microsoft.com/en-in/azure/azure-monitor/platform/alerts-activity-log",
"Description": "**Azure Monitor Activity log alerts** watch the admin operation `Microsoft.Sql/servers/firewallRules/delete`, indicating when an **Azure SQL firewall rule** is removed across a subscription.",
"Risk": "Without alerting on firewall rule deletions, unexpected changes to SQL network allowlists can go unnoticed, causing **availability** loss for apps and masking **unauthorized tampering**. A compromised admin could remove rules to disrupt service, erode control **integrity**, and delay response.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://azure.microsoft.com/en-us/updates/classic-alerting-monitoring-retirement",
"https://learn.microsoft.com/en-in/azure/azure-monitor/alerts/alerts-create-activity-log-alert-rule?tabs=activity-log",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/ActivityLog/create-or-update-or-delete-sql-server-firewall-rule-alert.html#trendmicro"
],
"Remediation": {
"Code": {
"CLI": "az monitor activity-log alert create --resource-group '<resource group name>' --condition category=Administrative and operationName=Microsoft.Sql/servers/firewallRules/delete and level=<verbose | information | warning | error | critical>--scope '/subscriptions/<subscription ID>' --name '<activity log rule name>' -- subscription <subscription id> --action-group <action group ID> --location global",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/azure/ActivityLog/create-or-update-or-delete-sql-server-firewall-rule-alert.html#trendmicro",
"Terraform": ""
"CLI": "az monitor activity-log alert create --resource-group <resource_group_name> --name <alert_name> --scopes /subscriptions/<subscription_id> --condition \"category=Administrative and operationName=Microsoft.Sql/servers/firewallRules/delete\" --location global",
"NativeIaC": "```bicep\n// Activity Log Alert for SQL Server firewall rule deletions\nresource activityLogAlert 'Microsoft.Insights/activityLogAlerts@2020-10-01' = {\n name: '<example_resource_name>'\n location: 'Global'\n properties: {\n scopes: [\n '/subscriptions/<example_subscription_id>'\n ]\n enabled: true\n condition: {\n allOf: [\n {\n field: 'category' // Critical: filter Activity Log category\n equals: 'Administrative' // Ensures Administrative events are matched\n }\n {\n field: 'operationName' // Critical: target deletion of SQL Server firewall rules\n equals: 'Microsoft.Sql/servers/firewallRules/delete' // This makes the check PASS\n }\n ]\n }\n }\n}\n```",
"Other": "1. In the Azure Portal, go to Monitor > Alerts > + Create > Alert rule\n2. Scope: Select the target Subscription and click Done\n3. Condition: Click Add condition, choose Signal type = Activity log, search for and select the operation with type \"Microsoft.Sql/servers/firewallRules/delete\" (display name like \"Delete Server Firewall Rule\"), then Click Apply\n4. Actions: Skip (optional)\n5. Details: Enter an Alert rule name and ensure Enable upon creation is selected\n6. Click Create",
"Terraform": "```hcl\n# Activity Log Alert for SQL Server firewall rule deletions\nresource \"azurerm_monitor_activity_log_alert\" \"<example_resource_name>\" {\n name = \"<example_resource_name>\"\n resource_group_name = \"<example_resource_name>\"\n scopes = [\"/subscriptions/<example_subscription_id>\"]\n\n criteria {\n category = \"Administrative\" # Critical: filter Activity Log category\n operation_name = \"Microsoft.Sql/servers/firewallRules/delete\" # Critical: match deletion of SQL Server firewall rules\n }\n}\n```"
},
"Recommendation": {
"Text": "1. Navigate to the Monitor blade. 2. Select Alerts. 3. Select Create. 4. Select Alert rule. 5. Under Filter by subscription, choose a subscription. 6. Under Filter by resource type, select Server Firewall Rule (servers/firewallRules). 7. Under Filter by location, select All. 8. From the results, select the subscription. 9. Select Done. 10. Select the Condition tab. 11. Under Signal name, click Delete server firewall rule (Microsoft.Sql/servers/firewallRules). 12. Select the Actions tab. 13. To use an existing action group, click Select action groups. To create a new action group, click Create action group. Fill out the appropriate details for the selection. 14. Select the Details tab. 15. Select a Resource group, provide an Alert rule name and an optional Alert rule description. 16. Click Review + create. 17. Click Create.",
"Url": "https://azure.microsoft.com/en-us/updates/classic-alerting-monitoring-retirement"
"Text": "Create an **activity log alert** for `Microsoft.Sql/servers/firewallRules/delete` and route it via an **action group** for rapid triage.\n\nEnforce **least privilege** and **separation of duties** on SQL admins, add alerts for related create/update operations, integrate with **SIEM**, and require *change approval* to strengthen defense in depth.",
"Url": "https://hub.prowler.com/check/monitor_alert_delete_sqlserver_fr"
}
},
"Categories": [],
"Categories": [
"logging"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "By default, no monitoring alerts are created."
@@ -1,30 +1,39 @@
{
"Provider": "azure",
"CheckID": "monitor_alert_service_health_exists",
"CheckTitle": "Ensure that an Activity Log Alert exists for Service Health",
"CheckTitle": "Azure subscription has an enabled Activity Log alert for Service Health incidents",
"CheckType": [],
"ServiceName": "monitor",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "Monitor",
"Severity": "medium",
"ResourceType": "microsoft.insights/activitylogalerts",
"ResourceGroup": "monitoring",
"Description": "Ensure that an Azure activity log alert is configured to trigger when Service Health events occur within your Microsoft Azure cloud account. The alert should activate when new events match the specified conditions in the alert rule configuration.",
"Risk": "Lack of monitoring for Service Health events may result in missing critical service issues, planned maintenance, security advisories, or other changes that could impact Azure services and regions in use.",
"RelatedUrl": "https://learn.microsoft.com/en-us/azure/service-health/overview",
"Description": "**Azure Monitor Activity Log alert** is configured for **Service Health** notifications where `category` is `ServiceHealth` and `properties.incidentType` is `Incident`, with the rule enabled.",
"Risk": "Without alerts for **Service Health incidents**, teams may miss Azure outages or degradations, harming **availability** and delaying failover. Unseen incidents can cause cascading errors, timeouts, deployment failures, and SLA breaches across dependent workloads.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/azure/service-health/service-health-notifications-properties",
"https://learn.microsoft.com/en-us/azure/service-health/alerts-activity-log-service-notifications-portal",
"https://learn.microsoft.com/en-us/azure/service-health/overview",
"https://learn.microsoft.com/en-us/azure/azure-monitor/platform/activity-log-schema",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/ActivityLog/service-health-alert.html"
],
"Remediation": {
"Code": {
"CLI": "az monitor activity-log alert create --subscription <subscription-id> --resource-group <resource-group> --name <alert-rule> --condition category=ServiceHealth and properties.incidentType=Incident --scope /subscriptions/<subscription-id> --action-group <action-group>",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/ActivityLog/service-health-alert.html",
"Terraform": ""
"CLI": "az monitor activity-log alert create --resource-group <resource-group> --name <alert-rule> --scopes /subscriptions/<subscription-id> --condition \"category=ServiceHealth and properties.incidentType=Incident\"",
"NativeIaC": "```bicep\n// Activity Log Alert for Service Health Incidents\nresource alert 'Microsoft.Insights/activityLogAlerts@2020-10-01' = {\n name: '<example_resource_name>'\n location: 'Global'\n properties: {\n enabled: true\n scopes: [ subscription().id ]\n condition: {\n allOf: [\n { field: 'category', equals: 'ServiceHealth' } // Critical: match Service Health category\n { field: 'properties.incidentType', equals: 'Incident' } // Critical: alert only on Incident events\n ]\n }\n }\n}\n```",
"Other": "1. In the Azure portal, go to Service Health > Health alerts > Create service health alert\n2. Scope: select your Subscription and choose the Resource group to save the alert\n3. Event types: select only Service issues (Incidents)\n4. Leave other filters as default, ensure Enable rule is On, then click Create",
"Terraform": "```hcl\n# Activity Log Alert for Service Health Incidents\nresource \"azurerm_monitor_activity_log_alert\" \"<example_resource_name>\" {\n name = \"<example_resource_name>\"\n resource_group_name = \"<example_resource_name>\"\n scopes = [\"/subscriptions/<example_subscription_id>\"]\n\n criteria {\n category = \"ServiceHealth\" # Critical: Service Health category\n service_health {\n events = [\"Incident\"] # Critical: alert only on Incident type\n }\n }\n}\n```"
},
"Recommendation": {
"Text": "Create an activity log alert for Service Health events and configure an action group to notify appropriate personnel.",
"Url": "https://learn.microsoft.com/en-us/azure/service-health/alerts-activity-log-service-notifications-portal"
"Text": "Create and maintain an enabled **Activity Log alert** for **Service Health Incident** events.\n- Route via **Action Groups** to on-call channels\n- Filter to critical services/regions\n- Test routing and refine recipients regularly\n- Integrate with **incident response** and **defense-in-depth** monitoring",
"Url": "https://hub.prowler.com/check/monitor_alert_service_health_exists"
}
},
"Categories": [],
"Categories": [
"resilience"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "By default, in your Azure subscription there will not be any activity log alerts configured for Service Health events."
@@ -1,30 +1,37 @@
{
"Provider": "azure",
"CheckID": "monitor_diagnostic_setting_with_appropriate_categories",
"CheckTitle": "Ensure Diagnostic Setting captures appropriate categories",
"CheckTitle": "Subscription has a diagnostic setting capturing Administrative, Security, Alert, and Policy categories",
"CheckType": [],
"ServiceName": "monitor",
"SubServiceName": "Configuring Diagnostic Settings",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "Monitor",
"Severity": "high",
"ResourceType": "microsoft.resources/subscriptions",
"ResourceGroup": "monitoring",
"Description": "Prerequisite: A Diagnostic Setting must exist. If a Diagnostic Setting does not exist, the navigation and options within this recommendation will not be available. Please review the recommendation at the beginning of this subsection titled: 'Ensure that a 'Diagnostic Setting' exists.' The diagnostic setting should be configured to log the appropriate activities from the control/management plane.",
"Risk": "A diagnostic setting controls how the diagnostic log is exported. Capturing the diagnostic setting categories for appropriate control/management plane activities allows proper alerting.",
"RelatedUrl": "https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/diagnostic-settings",
"Description": "**Azure Monitor Diagnostic Settings** capture **control-plane events** at the subscription level. This evaluates whether at least one setting collects the categories: `Administrative`, `Security`, `Policy`, and `Alert`.",
"Risk": "Without these categories, critical control-plane actions may go unrecorded. Attackers could change policies, roles, or alerts unnoticed, enabling privilege escalation and resource tampering. This erodes **integrity**, threatens **confidentiality**, and weakens **availability** and **incident response**.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/diagnostic-settings",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/Monitor/diagnostic-setting-categories.html",
"https://learn.microsoft.com/en-us/azure/storage/common/manage-storage-analytics-logs?toc=%2Fazure%2Fstorage%2Fblobs%2Ftoc.json&bc=%2Fazure%2Fstorage%2Fblobs%2Fbreadcrumb%2Ftoc.json&tabs=azure-portal"
],
"Remediation": {
"Code": {
"CLI": "az monitor diagnostic-settings subscription create --subscription <subscription id> --name <diagnostic settings name> --location <location> <[- -event-hub <event hub ID> --event-hub-auth-rule <event hub auth rule ID>] [-- storage-account <storage account ID>] [--workspace <log analytics workspace ID>] --logs '[{category:Security,enabled:true},{category:Administrative,enabled:true},{ca tegory:Alert,enabled:true},{category:Policy,enabled:true}]'>",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/Monitor/diagnostic-setting-categories.html",
"Terraform": ""
"CLI": "az monitor diagnostic-settings subscription create --name <example_resource_name> --workspace <example_resource_id> --logs '[{\"category\":\"Administrative\",\"enabled\":true},{\"category\":\"Security\",\"enabled\":true},{\"category\":\"Alert\",\"enabled\":true},{\"category\":\"Policy\",\"enabled\":true}]'",
"NativeIaC": "```bicep\n// Create a subscription-level diagnostic setting capturing required categories\ntargetScope = 'subscription'\n\nresource diag 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {\n name: '<example_resource_name>'\n properties: {\n workspaceId: '<example_resource_id>' // Critical: send Activity Log to this Log Analytics workspace\n logs: [\n { category: 'Administrative', enabled: true } // Critical: required category\n { category: 'Security', enabled: true } // Critical: required category\n { category: 'Alert', enabled: true } // Critical: required category\n { category: 'Policy', enabled: true } // Critical: required category\n ]\n }\n}\n```",
"Other": "1. In Azure portal, go to Monitor > Activity log\n2. Click Diagnostic settings > Add diagnostic setting\n3. Name the setting\n4. Under Categories, check: Administrative, Security, Alert, Policy\n5. Under Destination, select Send to Log Analytics workspace and choose your workspace\n6. Click Save",
"Terraform": "```hcl\n# Subscription Activity Log diagnostic setting capturing required categories\nresource \"azurerm_monitor_diagnostic_setting\" \"example\" {\n name = \"<example_resource_name>\"\n target_resource_id = \"/subscriptions/<example_resource_id>\" # Critical: scope set to the subscription\n log_analytics_workspace_id = \"<example_resource_id>\" # Critical: destination workspace\n\n enabled_log { category = \"Administrative\" } # Critical: required category\n enabled_log { category = \"Security\" } # Critical: required category\n enabled_log { category = \"Alert\" } # Critical: required category\n enabled_log { category = \"Policy\" } # Critical: required category\n}\n```"
},
"Recommendation": {
"Text": "1. Go to Azure Monitor 2. Click Activity log 3. Click on Export Activity Logs 4. Select the Subscription from the drop down menu 5. Click on Add diagnostic setting 6. Enter a name for your new Diagnostic Setting 7. Check the following categories: Administrative, Alert, Policy, and Security 8. Choose the destination details according to your organization's needs.",
"Url": "https://learn.microsoft.com/en-us/azure/storage/common/manage-storage-analytics-logs?toc=%2Fazure%2Fstorage%2Fblobs%2Ftoc.json&bc=%2Fazure%2Fstorage%2Fblobs%2Fbreadcrumb%2Ftoc.json&tabs=azure-portal"
"Text": "Collect `Administrative`, `Security`, `Policy`, and `Alert` via a subscription diagnostic setting and route them to a centralized, tamper-resistant destination. Enforce **least privilege** on log access, set retention, and create **alerts** for high-risk changes as part of **defense in depth**.",
"Url": "https://hub.prowler.com/check/monitor_diagnostic_setting_with_appropriate_categories"
}
},
"Categories": [],
"Categories": [
"logging"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "When the diagnostic setting is created using Azure Portal, by default no categories are selected."
@@ -1,30 +1,38 @@
{
"Provider": "azure",
"CheckID": "monitor_diagnostic_settings_exists",
"CheckTitle": "Ensure that a 'Diagnostic Setting' exists for Subscription Activity Logs ",
"CheckTitle": "Subscription has an Activity Log diagnostic setting",
"CheckType": [],
"ServiceName": "monitor",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "Monitor",
"Severity": "high",
"ResourceType": "microsoft.resources/subscriptions",
"ResourceGroup": "monitoring",
"Description": "Enable Diagnostic settings for exporting activity logs. Diagnostic settings are available for each individual resource within a subscription. Settings should be configured for all appropriate resources for your environment.",
"Risk": "A diagnostic setting controls how a diagnostic log is exported. By default, logs are retained only for 90 days. Diagnostic settings should be defined so that logs can be exported and stored for a longer duration in order to analyze security activities within an Azure subscription.",
"RelatedUrl": "https://learn.microsoft.com/en-us/cli/azure/monitor/diagnostic-settings?view=azure-cli-latest",
"Description": "**Azure Monitor Diagnostic Settings** are configured to export the **Activity Log** to an external destination (Log Analytics, Storage, Event Hub, or partner).",
"Risk": "Without exporting the **Activity Log**, control-plane events lack **centralization and retention**.\n\nUndetected RBAC changes, policy updates, and resource deletions reduce **detectability**, hinder **forensics**, and weaken incident response and audit evidence.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/diagnostic-settings?WT.mc_id=AZ-MVP-5003450&tabs=portal",
"https://learn.microsoft.com/en-us/azure/azure-monitor/fundamentals/data-sources#export-the-activity-log-with-a-log-profile",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/Monitor/subscription-activity-log-diagnostic-settings.html",
"https://learn.microsoft.com/en-us/cli/azure/monitor/diagnostic-settings?view=azure-cli-latest"
],
"Remediation": {
"Code": {
"CLI": "az monitor diagnostic-settings subscription create --subscription <subscription id> --name <diagnostic settings name> --location <location> <[- -event-hub <event hub ID> --event-hub-auth-rule <event hub auth rule ID>] [-- storage-account <storage account ID>] [--workspace <log analytics workspace ID>] --logs '<JSON encoded categories>' (e.g. [{category:Security,enabled:true},{category:Administrative,enabled:true},{cat egory:Alert,enabled:true},{category:Policy,enabled:true}])",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/azure/Monitor/subscription-activity-log-diagnostic-settings.html#trendmicro",
"Terraform": ""
"CLI": "az monitor diagnostic-settings subscription create --subscription <subscription id> --name <example_resource_name> --workspace <log analytics workspace ID> --logs '[{\"category\":\"Administrative\",\"enabled\":true}]'",
"NativeIaC": "```bicep\n// Subscription-level Activity Log diagnostic setting\nresource diag 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {\n name: '<example_resource_name>'\n scope: subscription() // CRITICAL: targets the subscription Activity Log\n properties: {\n workspaceId: '<example_resource_id>' // CRITICAL: sends logs to this Log Analytics workspace\n logs: [\n { category: 'Administrative', enabled: true } // CRITICAL: enables at least one Activity Log category\n ]\n }\n}\n```",
"Other": "1. In the Azure portal, go to Subscriptions and select your subscription\n2. Open Monitoring > Activity log, then click Diagnostic settings\n3. Click + Add diagnostic setting and enter a name\n4. Under Destination details, select Send to Log Analytics workspace and choose your workspace\n5. Under Categories, select Administrative\n6. Click Save",
"Terraform": "```hcl\n# Subscription-level Activity Log diagnostic setting\nresource \"azurerm_monitor_diagnostic_setting\" \"<example_resource_name>\" {\n name = \"<example_resource_name>\"\n target_resource_id = \"/subscriptions/<subscription id>\" # CRITICAL: subscription scope\n log_analytics_workspace_id = \"<example_resource_id>\" # CRITICAL: destination workspace\n\n log {\n category = \"Administrative\" # CRITICAL: enable at least one Activity Log category\n enabled = true\n }\n}\n```"
},
"Recommendation": {
"Text": "To enable Diagnostic Settings on a Subscription: 1. Go to Monitor 2. Click on Activity Log 3. Click on Export Activity Logs 4. Click + Add diagnostic setting 5. Enter a Diagnostic setting name 6. Select Categories for the diagnostic settings 7. Select the appropriate Destination details (this may be Log Analytics, Storage Account, Event Hub, or Partner solution) 8. Click Save To enable Diagnostic Settings on a specific resource: 1. Go to Monitor 2. Click Diagnostic settings 3. Click on the resource that has a diagnostics status of disabled 4. Select Add Diagnostic Setting 5. Enter a Diagnostic setting name 6. Select the appropriate log, metric, and destination. (this may be Log Analytics, Storage Account, Event Hub, or Partner solution) 7. Click save Repeat these step for all resources as needed.",
"Url": "https://docs.microsoft.com/en-us/azure/monitoring-and-diagnostics/monitoring-overview-activity-logs#export-the-activity-log-with-a-log-profile"
"Text": "Enable **subscription Diagnostic Settings** to send the **Activity Log** to a trusted destination.\n\nUse **immutable storage** or a **SIEM**, enforce coverage with **Azure Policy**, apply **least privilege** to log access, include essential categories, and set retention aligned to regulatory needs.",
"Url": "https://hub.prowler.com/check/monitor_diagnostic_settings_exists"
}
},
"Categories": [],
"Categories": [
"logging"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "By default, diagnostic setting is not set."
@@ -1,30 +1,37 @@
{
"Provider": "azure",
"CheckID": "monitor_storage_account_with_activity_logs_cmk_encrypted",
"CheckTitle": "Ensure the storage account containing the container with activity logs is encrypted with Customer Managed Key",
"CheckTitle": "Storage account storing Activity Log data is encrypted with a customer-managed key",
"CheckType": [],
"ServiceName": "monitor",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "Monitor",
"ResourceType": "microsoft.storage/storageaccounts",
"ResourceGroup": "monitoring",
"Description": "Storage accounts with the activity log exports can be configured to use CustomerManaged Keys (CMK).",
"Risk": "Configuring the storage account with the activity log export container to use CMKs provides additional confidentiality controls on log data, as a given user must have read permission on the corresponding storage account and must be granted decrypt permission by the CMK.",
"RelatedUrl": "https://learn.microsoft.com/en-us/security/benchmark/azure/security-controls-v3-data-protection#dp-5-encrypt-sensitive-data-at-rest",
"Description": "**Azure Monitor Activity Logs** sent to a **Storage account** are evaluated to confirm encryption with **Customer-Managed Keys** (`CMK`) instead of **Microsoft-managed keys**.",
"Risk": "Storing activity logs without **CMK** weakens confidentiality and control of audit data. You lose independent key ownership, limiting rapid rotation/revocation and separation of duties. If storage credentials are compromised, attackers can exfiltrate logs that map resources and changes, aiding targeted attacks and hindering effective incident response.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/activity-log?tabs=cli#managing-legacy-log-profiles",
"https://learn.microsoft.com/en-us/security/benchmark/azure/security-controls-v3-data-protection#dp-5-encrypt-sensitive-data-at-rest",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/Monitor/use-cmk-for-activity-log-storage-container-encryption.html"
],
"Remediation": {
"Code": {
"CLI": "az storage account update --name <name of the storage account> --resource-group <resource group for a storage account> --encryption-key-source=Microsoft.Keyvault --encryption-key-vault <Key Vault URI> --encryption-key-name <KeyName> --encryption-key-version <Key Version>",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/azure/Monitor/use-cmk-for-activity-log-storage-container-encryption.html",
"Terraform": "https://docs.prowler.com/checks/azure/azure-general-policies/ensure-that-storage-accounts-use-customer-managed-key-for-encryption#terraform"
"CLI": "az storage account update --name <example_resource_name> --resource-group <example_resource_name> --assign-identity --encryption-key-source Microsoft.Keyvault --encryption-key-vault <KeyVaultURI> --encryption-key-name <KeyName>",
"NativeIaC": "```bicep\n// Storage account encrypted with a customer-managed key (CMK)\nresource stg 'Microsoft.Storage/storageAccounts@2023-01-01' = {\n name: '<example_resource_name>'\n location: resourceGroup().location\n kind: 'StorageV2'\n sku: { name: 'Standard_LRS' }\n identity: { type: 'SystemAssigned' } // Required for Storage to access the Key Vault key\n properties: {\n encryption: {\n keySource: 'Microsoft.Keyvault' // CRITICAL: switches encryption from Microsoft.Storage to CMK\n keyVaultProperties: {\n keyName: '<KeyName>'\n keyVaultUri: '<KeyVaultURI>' // Uses latest key version if not specified\n }\n }\n }\n}\n```",
"Other": "1. In the Azure portal, go to Storage accounts and open the account used by your Activity Log diagnostic setting\n2. Select Identity > System assigned > set Status to On > Save\n3. Go to Settings > Encryption\n4. Select Customer-managed keys, choose your Key vault and Key, then click Save\n5. Ensure the storage account's identity has Get, Wrap Key, and Unwrap Key permissions on the key in Key Vault",
"Terraform": "```hcl\n# Storage account encrypted with a customer-managed key (CMK)\nresource \"azurerm_storage_account\" \"<example_resource_name>\" {\n name = \"<example_resource_name>\"\n resource_group_name = \"<example_resource_name>\"\n location = \"<example_location>\"\n account_tier = \"Standard\"\n account_replication_type = \"LRS\"\n\n identity {\n type = \"SystemAssigned\" # Required for Storage to access the Key Vault key\n }\n\n customer_managed_key {\n key_vault_key_id = \"<key_vault_key_id>\" # CRITICAL: enables CMK by pointing to the Key Vault key\n }\n}\n```"
},
"Recommendation": {
"Text": "1. Go to Activity log 2. Select Export 3. Select Subscription 4. In section Storage Account, note the name of the Storage account 5. Close the Export Audit Logs blade. Close the Monitor - Activity Log blade. 6. In right column, Click service Storage Accounts to access Storage account blade 7. Click on the storage account name noted in step 4. This will open blade specific to that storage account 8. Under Security + networking, click Encryption. 9. Ensure Customer-managed keys is selected and Key URI is set.",
"Url": "https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/activity-log?tabs=cli#managing-legacy-log-profiles"
"Text": "Encrypt the storage account that holds exported **Activity Logs** with **Customer-Managed Keys** via Azure Key Vault or Managed HSM. Apply **least privilege** to key usage, enforce regular rotation and revocation, and enable soft delete and purge protection. Complement with network isolation and immutable retention for **defense in depth**.",
"Url": "https://hub.prowler.com/check/monitor_storage_account_with_activity_logs_cmk_encrypted"
}
},
"Categories": [],
"Categories": [
"encryption"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "NOTE: You must have your key vault setup to utilize this. All Audit Logs will be encrypted with a key you provide. You will need to set up customer managed keys separately, and you will select which key to use via the instructions here. You will be responsible for the lifecycle of the keys, and will need to manually replace them at your own determined intervals to keep the data secure."
@@ -1,30 +1,38 @@
{
"Provider": "azure",
"CheckID": "monitor_storage_account_with_activity_logs_is_private",
"CheckTitle": "Ensure the Storage Container Storing the Activity Logs is not Publicly Accessible",
"CheckTitle": "Storage account storing activity logs does not allow public blob access",
"CheckType": [],
"ServiceName": "monitor",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "Monitor",
"ResourceType": "microsoft.storage/storageaccounts",
"ResourceGroup": "monitoring",
"Description": "The storage account container containing the activity log export should not be publicly accessible.",
"Risk": "Allowing public access to activity log content may aid an adversary in identifying weaknesses in the affected account's use or configuration.",
"RelatedUrl": "https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/diagnostic-settings",
"Description": "**Azure Monitor Activity Logs** sent to a **Storage account** are evaluated for **Blob public access**. The finding identifies whether the account that stores the logs has `AllowBlobPublicAccess` turned on.",
"Risk": "Exposed log data undermines **confidentiality** by revealing operations, resource IDs, IPs, and identities.\n\nAdversaries gain **reconnaissance** to map controls, craft targeted attacks, and time actions to avoid detection, enabling **lateral movement** and broader compromise.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/diagnostic-settings",
"https://learn.microsoft.com/en-us/security/benchmark/azure/security-controls-v3-network-security#ns-2-secure-cloud-services-with-network-controls",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/Monitor/check-for-publicly-accessible-activity-log-storage-container.html"
],
"Remediation": {
"Code": {
"CLI": "az storage container set-permission --name insights-activity-logs --account-name <Storage Account Name> --public-access off",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/azure/Monitor/check-for-publicly-accessible-activity-log-storage-container.html",
"Terraform": "https://docs.prowler.com/checks/azure/azure-logging-policies/ensure-the-storage-container-storing-the-activity-logs-is-not-publicly-accessible#terraform"
"CLI": "az storage account update --name <STORAGE_ACCOUNT_NAME> --resource-group <RESOURCE_GROUP_NAME> --allow-blob-public-access false",
"NativeIaC": "```bicep\n// Set storage account to disallow public blob access\nresource sa 'Microsoft.Storage/storageAccounts@2023-01-01' = {\n name: '<example_resource_name>'\n location: resourceGroup().location\n sku: { name: 'Standard_LRS' }\n kind: 'StorageV2'\n properties: {\n allowBlobPublicAccess: false // Critical: disables public access at the account level\n }\n}\n```",
"Other": "1. In Azure Portal, go to the storage account used by the diagnostic/Activity Log export\n2. Under Settings, select Configuration\n3. Set \"Allow Blob public access\" to Disabled\n4. Click Save",
"Terraform": "```hcl\n# Disable public blob access on the storage account\nresource \"azurerm_storage_account\" \"<example_resource_name>\" {\n name = \"<example_resource_name>\"\n resource_group_name = \"<example_resource_name>\"\n location = \"<example_resource_name>\"\n account_tier = \"Standard\"\n account_replication_type = \"LRS\"\n\n allow_blob_public_access = false # Critical: disables public access at the account level\n}\n```"
},
"Recommendation": {
"Text": "1. From Azure Home select the Portal Menu 2. Search for Storage Accounts to access Storage account blade 3. Click on the storage account name 4. Click on Configuration under settings 5. Select Enabled under 'Allow Blob public access'",
"Url": "https://docs.microsoft.com/en-us/security/benchmark/azure/security-controls-v3-network-security#ns-2-secure-cloud-services-with-network-controls"
"Text": "Set `AllowBlobPublicAccess=false` on the storage account holding logs. Enforce **least privilege** via RBAC or scoped SAS, use **private endpoints** and network restrictions, and enable **immutability** for log containers to add **defense in depth** and prevent unauthorized access.",
"Url": "https://hub.prowler.com/check/monitor_storage_account_with_activity_logs_is_private"
}
},
"Categories": [],
"Categories": [
"internet-exposed",
"logging"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Configuring container Access policy to private will remove access from the container for everyone except owners of the storage account. Access policy needs to be set explicitly in order to allow access to other desired users."
@@ -1,4 +1,7 @@
import os
import re
from typing_extensions import override
from prowler.lib.logger import logger
from prowler.lib.powershell.powershell import PowerShellSession
@@ -46,6 +49,28 @@ class M365PowerShell(PowerShellSession):
self.tenant_identity = identity
self.init_credential(credentials)
@override
def _process_error(self, error_result: str) -> None:
"""
Process PowerShell errors with M365-specific handling.
Detects cmdlet not found errors which typically indicate missing licensing
(e.g., Microsoft Defender for Office 365) or insufficient permissions.
Args:
error_result (str): The error output from the PowerShell command.
"""
if "is not recognized as a name of a cmdlet" in error_result:
cmdlet_match = re.search(r"'([^']+)'.*is not recognized", error_result)
cmdlet_name = cmdlet_match.group(1) if cmdlet_match else "Unknown"
logger.warning(
f"PowerShell cmdlet '{cmdlet_name}' is not available. "
f"This may indicate missing module, licensing (e.g., Microsoft Defender for Office 365) "
f"or insufficient permissions. Related checks will be skipped."
)
else:
super()._process_error(error_result)
def clean_certificate_content(self, cert_content: str) -> str:
"""
Clean certificate content for PowerShell consumption.
@@ -823,6 +848,78 @@ class M365PowerShell(PowerShellSession):
"Get-SharingPolicy | ConvertTo-Json -Depth 10", json_parse=True
)
def get_safe_attachments_policy(self) -> dict:
"""
Get Safe Attachments Policy.
Retrieves the Safe Attachments policy settings for Microsoft Defender for Office 365.
Returns:
dict: Safe Attachments policy settings in JSON format.
Example:
>>> get_safe_attachments_policy()
{
"Name": "Built-In Protection Policy",
"Identity": "Built-In Protection Policy",
"Enable": true,
"Action": "Block",
"QuarantineTag": "AdminOnlyAccessPolicy"
}
"""
return self.execute(
"Get-SafeAttachmentPolicy | ConvertTo-Json -Depth 10", json_parse=True
)
def get_safe_attachments_rule(self) -> dict:
"""
Get Safe Attachments Rules.
Retrieves the Safe Attachments rules that define which users, groups,
and domains are targeted by Safe Attachments policies.
Returns:
dict: Safe Attachments rules in JSON format.
Example:
>>> get_safe_attachments_rule()
{
"Name": "Custom Safe Attachments Rule",
"SafeAttachmentPolicy": "Custom Policy",
"State": "Enabled",
"Priority": 0,
"SentTo": ["user@contoso.com"],
"SentToMemberOf": ["group@contoso.com"],
"RecipientDomainIs": ["contoso.com"]
}
"""
return self.execute(
"Get-SafeAttachmentRule | ConvertTo-Json -Depth 10", json_parse=True
)
def get_advanced_threat_protection_policy(self) -> dict:
"""
Get Advanced Threat Protection Policy.
Retrieves the current Advanced Threat Protection policy settings,
including Safe Attachments for SharePoint, OneDrive, and Teams, and Safe Documents settings.
Returns:
dict: Advanced Threat Protection policy settings in JSON format.
Example:
>>> get_advanced_threat_protection_policy()
{
"Identity": "Default",
"EnableATPForSPOTeamsODB": true,
"EnableSafeDocs": true,
"AllowSafeDocsOpen": false
}
"""
return self.execute(
"Get-AtpPolicyForO365 | ConvertTo-Json -Depth 10", json_parse=True
)
def get_teams_protection_policy(self) -> dict:
"""
Get Teams Protection Policy.
@@ -883,6 +980,57 @@ class M365PowerShell(PowerShellSession):
json_parse=True,
)
def get_safe_links_policy(self) -> dict:
"""
Get Safe Links Policy.
Retrieves the current Safe Links policy settings for Microsoft Defender for Office 365.
Returns:
dict: Safe Links policy settings in JSON format.
Example:
>>> get_safe_links_policy()
{
"Name": "Built-In Protection Policy",
"Identity": "Built-In Protection Policy",
"EnableSafeLinksForEmail": true,
"EnableSafeLinksForTeams": true,
"EnableSafeLinksForOffice": true,
"TrackClicks": true,
"AllowClickThrough": false,
"ScanUrls": true,
"EnableForInternalSenders": true,
"DeliverMessageAfterScan": true,
"DisableUrlRewrite": false
}
"""
return self.execute(
"Get-SafeLinksPolicy | ConvertTo-Json -Depth 10", json_parse=True
)
def get_safe_links_rule(self) -> dict:
"""
Get Safe Links Rule.
Retrieves the current Safe Links rule settings for Microsoft Defender for Office 365.
Returns:
dict: Safe Links rule settings in JSON format.
Example:
>>> get_safe_links_rule()
{
"Name": "Safe Links Rule",
"State": "Enabled",
"Priority": 0,
"SafeLinksPolicy": "Policy Name"
}
"""
return self.execute(
"Get-SafeLinksRule | ConvertTo-Json -Depth 10", json_parse=True
)
# This function is used to install the required M365 PowerShell modules in Docker containers
def initialize_m365_powershell_modules():
@@ -0,0 +1,37 @@
{
"Provider": "m365",
"CheckID": "defender_atp_safe_attachments_and_docs_configured",
"CheckTitle": "ATP Safe Attachments policy has Safe Documents enabled and click-through blocked for SharePoint, OneDrive, and Microsoft Teams",
"CheckType": [],
"ServiceName": "defender",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "Defender ATP Policy",
"ResourceGroup": "security",
"Description": "**Safe Attachments** for SharePoint, OneDrive, and Microsoft Teams protects organizations from inadvertently sharing malicious files. When enabled, files identified as malicious are blocked and cannot be opened, copied, moved, or shared until further actions are taken by the organization's security team.",
"Risk": "Without **Safe Attachments** enabled, users may download, sync, or access **malicious files** from SharePoint, OneDrive, or Teams, potentially leading to **malware infections** across the organization. If **Safe Documents** is disabled or users can bypass **Protected View**, malicious content in Office documents may execute and **compromise endpoints**.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/defender-office-365/safe-attachments-about",
"https://learn.microsoft.com/en-us/defender-office-365/safe-documents-in-e5-plus-security-about"
],
"Remediation": {
"Code": {
"CLI": "Set-AtpPolicyForO365 -EnableATPForSPOTeamsODB $true -EnableSafeDocs $true -AllowSafeDocsOpen $false",
"NativeIaC": "",
"Other": "1. Navigate to Microsoft 365 Defender portal at https://security.microsoft.com.\n2. Go to **Email & Collaboration** > **Policies & Rules** > **Threat policies**.\n3. Under **Policies**, select **Safe Attachments**.\n4. Click on **Global settings**.\n5. Enable **Turn on Defender for Office 365 for SharePoint, OneDrive, and Microsoft Teams**.\n6. Enable **Turn on Safe Documents for Office clients**.\n7. Disable **Allow people to click through Protected View even if Safe Documents identified the file as malicious**.\n8. Click **Save**.",
"Terraform": ""
},
"Recommendation": {
"Text": "Enable **Safe Attachments** for SharePoint, OneDrive, and Microsoft Teams along with **Safe Documents** to protect users from malicious files. Block users from bypassing **Protected View** warnings for files identified as malicious to maintain **defense-in-depth**.",
"Url": "https://hub.prowler.com/check/defender_atp_safe_attachments_and_docs_configured"
}
},
"Categories": [
"e5"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "This check requires Microsoft Defender for Office 365 Plan 1 or Plan 2 (included in E5 license). Safe Documents specifically requires Microsoft 365 E5 or Microsoft 365 E5 Security."
}
@@ -0,0 +1,62 @@
from typing import List
from prowler.lib.check.models import Check, CheckReportM365
from prowler.providers.m365.services.defender.defender_client import defender_client
class defender_atp_safe_attachments_and_docs_configured(Check):
"""
Check if Safe Attachments for SharePoint, OneDrive, and Teams is properly configured.
This check verifies that the ATP (Advanced Threat Protection) policy for Office 365 has:
- EnableATPForSPOTeamsODB = True (Safe Attachments enabled for SPO/OneDrive/Teams)
- EnableSafeDocs = True (Safe Documents enabled)
- AllowSafeDocsOpen = False (Users cannot bypass Protected View for malicious files)
- PASS: All three settings are properly configured.
- FAIL: One or more settings are not properly configured.
"""
def execute(self) -> List[CheckReportM365]:
"""
Execute the check to verify Safe Attachments ATP policy configuration.
Returns:
List[CheckReportM365]: A list of reports containing the result of the check.
"""
findings = []
if defender_client.advanced_threat_protection_policy:
policy = defender_client.advanced_threat_protection_policy
report = CheckReportM365(
metadata=self.metadata(),
resource=policy,
resource_name=policy.identity,
resource_id=policy.identity,
)
# Check all three required settings
is_atp_enabled = policy.enable_atp_for_spo_teams_odb
is_safe_docs_enabled = policy.enable_safe_docs
is_safe_docs_open_blocked = not policy.allow_safe_docs_open
if is_atp_enabled and is_safe_docs_enabled and is_safe_docs_open_blocked:
# Case 1: ATP policy exists and is properly configured
report.status = "PASS"
report.status_extended = f"ATP policy {policy.identity} has Safe Attachments for SharePoint, OneDrive, and Teams properly configured with Safe Documents enabled and click-through blocked."
else:
# Case 2: ATP policy exists but is not properly configured
report.status = "FAIL"
issues = []
if not is_atp_enabled:
issues.append("Safe Attachments for SPO/OneDrive/Teams is disabled")
if not is_safe_docs_enabled:
issues.append("Safe Documents is disabled")
if not is_safe_docs_open_blocked:
issues.append("users can bypass Protected View for malicious files")
report.status_extended = f"ATP policy {policy.identity} is not properly configured: {'; '.join(issues)}."
findings.append(report)
return findings
@@ -0,0 +1,40 @@
{
"Provider": "m365",
"CheckID": "defender_safe_attachments_policy_enabled",
"CheckTitle": "Safe Attachments policy is enabled with secure configuration",
"CheckType": [],
"ServiceName": "defender",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "Defender Safe Attachments Policy",
"ResourceGroup": "security",
"Description": "Safe Attachments provides an additional layer of protection by scanning email attachments in a secure environment before delivering them to recipients.\n\nThe Built-In Protection Policy should have **Enable=True**, **Action=Block**, and **QuarantineTag=AdminOnlyAccessPolicy** to ensure malicious attachments are blocked and quarantined with admin-only access.",
"Risk": "Without properly configured Safe Attachments policies, malicious email attachments could reach users' mailboxes and potentially compromise the organization through:\n\n- **Malware delivery** via infected documents\n- **Ransomware attacks** through weaponized attachments\n- **Data exfiltration** using malicious scripts",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/defender-office-365/safe-attachments-about",
"https://learn.microsoft.com/en-us/defender-office-365/preset-security-policies"
],
"Remediation": {
"Code": {
"CLI": "Set-SafeAttachmentPolicy -Identity 'Built-In Protection Policy' -Enable $true -Action Block -QuarantineTag AdminOnlyAccessPolicy",
"NativeIaC": "",
"Other": "1. Go to Microsoft 365 Defender portal (https://security.microsoft.com)\n2. Navigate to Email & collaboration > Policies & rules > Threat policies\n3. Select Safe Attachments under Policies\n4. Click on Built-In Protection Policy\n5. Set Enable to True, Action to Block, and QuarantineTag to AdminOnlyAccessPolicy\n6. Save the configuration",
"Terraform": ""
},
"Recommendation": {
"Text": "Enable Safe Attachments with the **Block** action to prevent malicious attachments from reaching users. Use **AdminOnlyAccessPolicy** for the quarantine tag to ensure only administrators can release quarantined items, following the principle of least privilege.",
"Url": "https://hub.prowler.com/check/defender_safe_attachments_policy_enabled"
}
},
"Categories": [
"email-security",
"e5"
],
"DependsOn": [],
"RelatedTo": [
"defender_malware_policy_common_attachments_filter_enabled"
],
"Notes": "This check requires Microsoft Defender for Office 365 Plan 1 or higher license."
}
@@ -0,0 +1,112 @@
from typing import List
from prowler.lib.check.models import Check, CheckReportM365
from prowler.providers.m365.services.defender.defender_client import defender_client
class defender_safe_attachments_policy_enabled(Check):
"""
Check if Safe Attachments policy is properly configured in Microsoft Defender for Office 365.
This check verifies that Safe Attachments policies have the following settings
configured according to CIS Microsoft 365 Foundations Benchmark:
- Enable = True
- Action = Block
- QuarantineTag = AdminOnlyAccessPolicy
Note: The Built-in Protection Policy has fixed settings that cannot be changed
and always provides baseline protection.
"""
def execute(self) -> List[CheckReportM365]:
findings = []
if defender_client.safe_attachments_policies:
# Only Built-in Protection Policy exists (no custom policies with rules)
if not defender_client.safe_attachments_rules:
policy = next(iter(defender_client.safe_attachments_policies.values()))
report = CheckReportM365(
metadata=self.metadata(),
resource=policy,
resource_name=policy.name,
resource_id=policy.identity,
)
# Case 1: Only Built-in policy exists - always PASS (fixed settings)
report.status = "PASS"
report.status_extended = f"{policy.name} is the only Safe Attachments policy and provides baseline protection for all users."
findings.append(report)
# Multiple Safe Attachments Policies (Built-in + custom policies)
else:
for (
policy_name,
policy,
) in defender_client.safe_attachments_policies.items():
report = CheckReportM365(
metadata=self.metadata(),
resource=policy,
resource_name=policy_name,
resource_id=policy.identity,
)
if policy.is_built_in_protection:
# Case 2: Built-in policy with custom policies - always PASS
report.status = "PASS"
report.status_extended = (
f"{policy_name} provides baseline Safe Attachments protection, "
f"but could be overridden by a misconfigured custom policy for specific users."
)
findings.append(report)
else:
# Custom policy - check configuration
rule = defender_client.safe_attachments_rules.get(policy_name)
if not rule:
continue
included_resources = []
if rule.users:
included_resources.append(f"users: {', '.join(rule.users)}")
if rule.groups:
included_resources.append(
f"groups: {', '.join(rule.groups)}"
)
if rule.domains:
included_resources.append(
f"domains: {', '.join(rule.domains)}"
)
# If no users, groups, or domains specified, the policy applies to all recipients
included_resources_str = (
"; ".join(included_resources)
if included_resources
else "all users"
)
if self._is_policy_properly_configured(policy, rule):
# Case 2: Custom policy is properly configured
report.status = "PASS"
report.status_extended = (
f"Custom Safe Attachments policy {policy_name} is properly configured and includes {included_resources_str}, "
f"with priority {rule.priority} (0 is the highest)."
)
else:
# Case 3: Custom policy is not properly configured
report.status = "FAIL"
report.status_extended = (
f"Custom Safe Attachments policy {policy_name} is not properly configured and includes {included_resources_str}, "
f"with priority {rule.priority} (0 is the highest)."
)
findings.append(report)
return findings
def _is_policy_properly_configured(self, policy, rule) -> bool:
"""Check if a custom policy is properly configured according to CIS recommendations."""
return (
rule.state.lower() == "enabled"
and policy.enable
and policy.action == "Block"
and policy.quarantine_tag == "AdminOnlyAccessPolicy"
)
@@ -0,0 +1,40 @@
{
"Provider": "m365",
"CheckID": "defender_safelinks_policy_enabled",
"CheckTitle": "Safe Links policy is enabled and properly configured in Microsoft Defender for Office 365.",
"CheckType": [],
"ServiceName": "defender",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "Defender Safe Links Policy",
"ResourceGroup": "security",
"Description": "Safe Links is a Microsoft Defender for Office 365 feature that provides URL scanning and rewriting of inbound email messages, as well as time-of-click verification of URLs and links in email messages, Teams, and Office apps.\n\nThis check verifies that the Safe Links policy is properly configured with all recommended settings enabled.",
"Risk": "Without properly configured Safe Links protection, users may be vulnerable to **phishing attacks** and **malicious URLs** in emails, Teams messages, and Office documents.\n\nAttackers could bypass security by using URLs that redirect to malicious content after initial scanning.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/defender-office-365/safe-links-about",
"https://learn.microsoft.com/en-us/defender-office-365/safe-links-policies-configure"
],
"Remediation": {
"Code": {
"CLI": "Set-SafeLinksPolicy -Identity 'Built-In Protection Policy' -EnableSafeLinksForEmail $true -EnableSafeLinksForTeams $true -EnableSafeLinksForOffice $true -TrackClicks $true -AllowClickThrough $false -ScanUrls $true -EnableForInternalSenders $true -DeliverMessageAfterScan $true -DisableUrlRewrite $false",
"NativeIaC": "",
"Other": "1. Navigate to Microsoft 365 Defender at https://security.microsoft.com\n2. Click to expand Email & collaboration and select Policies & rules\n3. Select Threat policies\n4. Under Policies, select Safe Links\n5. Select or create a policy and configure these settings:\n - Enable Safe Links for Email: On\n - Enable Safe Links for Teams: On\n - Enable Safe Links for Office: On\n - Track user clicks: On\n - Let users click through to the original URL: Off\n - Scan URLs: On\n - Apply Safe Links to messages sent within the organization: On\n - Wait for URL scanning to complete before delivering the message: On\n - Do not rewrite URLs: Off\n6. Save the policy",
"Terraform": ""
},
"Recommendation": {
"Text": "Enable and properly configure Safe Links policies to protect users from malicious URLs in emails, Teams, and Office applications. Ensure URL scanning and time-of-click verification are enabled across all communication channels.",
"Url": "https://hub.prowler.com/check/defender_safelinks_policy_enabled"
}
},
"Categories": [
"email-security",
"e5"
],
"DependsOn": [],
"RelatedTo": [
"defender_antiphishing_policy_configured"
],
"Notes": "Safe Links requires Microsoft Defender for Office 365 Plan 1 (P1) or higher licensing."
}
@@ -0,0 +1,121 @@
from typing import List
from prowler.lib.check.models import Check, CheckReportM365
from prowler.providers.m365.services.defender.defender_client import defender_client
class defender_safelinks_policy_enabled(Check):
"""
Check if Safe Links policy is enabled and properly configured in Microsoft Defender for Office 365.
This check verifies that Safe Links policies have the following settings
configured according to CIS Microsoft 365 Foundations Benchmark:
- EnableSafeLinksForEmail = True
- EnableSafeLinksForTeams = True
- EnableSafeLinksForOffice = True
- TrackClicks = True
- AllowClickThrough = False
- ScanUrls = True
- EnableForInternalSenders = True
- DeliverMessageAfterScan = True
- DisableUrlRewrite = False
Note: The Built-in Protection Policy has fixed settings that cannot be changed
and always provides baseline protection.
"""
def execute(self) -> List[CheckReportM365]:
findings = []
if defender_client.safe_links_policies:
# Only Built-in Protection Policy exists (no custom policies with rules)
if not defender_client.safe_links_rules:
policy = next(iter(defender_client.safe_links_policies.values()))
report = CheckReportM365(
metadata=self.metadata(),
resource=policy,
resource_name=policy.name,
resource_id=policy.identity,
)
# Case 1: Only Built-in policy exists - always PASS (fixed settings)
report.status = "PASS"
report.status_extended = f"{policy.name} is the only Safe Links policy and provides baseline protection for all users."
findings.append(report)
# Multiple Safe Links Policies (Built-in + custom policies)
else:
for policy_name, policy in defender_client.safe_links_policies.items():
report = CheckReportM365(
metadata=self.metadata(),
resource=policy,
resource_name=policy_name,
resource_id=policy.identity,
)
if policy.is_built_in_protection:
# Case 2: Built-in policy with custom policies - always PASS
report.status = "PASS"
report.status_extended = (
f"{policy_name} provides baseline Safe Links protection, "
f"but could be overridden by a misconfigured custom policy for specific users."
)
findings.append(report)
else:
# Custom policy - check configuration
rule = defender_client.safe_links_rules.get(policy_name)
if not rule:
continue
included_resources = []
if rule.users:
included_resources.append(f"users: {', '.join(rule.users)}")
if rule.groups:
included_resources.append(
f"groups: {', '.join(rule.groups)}"
)
if rule.domains:
included_resources.append(
f"domains: {', '.join(rule.domains)}"
)
# If no users, groups, or domains specified, the policy applies to all recipients
included_resources_str = (
"; ".join(included_resources)
if included_resources
else "all users"
)
if self._is_policy_properly_configured(policy, rule):
# Case 2: Custom policy is properly configured
report.status = "PASS"
report.status_extended = (
f"Custom Safe Links policy {policy_name} is properly configured and includes {included_resources_str}, "
f"with priority {rule.priority} (0 is the highest)."
)
else:
# Case 3: Custom policy is not properly configured
report.status = "FAIL"
report.status_extended = (
f"Custom Safe Links policy {policy_name} is not properly configured and includes {included_resources_str}, "
f"with priority {rule.priority} (0 is the highest)."
)
findings.append(report)
return findings
def _is_policy_properly_configured(self, policy, rule) -> bool:
"""Check if a custom policy is properly configured according to CIS recommendations."""
return (
rule.state.lower() == "enabled"
and policy.enable_safe_links_for_email
and policy.enable_safe_links_for_teams
and policy.enable_safe_links_for_office
and policy.track_clicks
and not policy.allow_click_through
and policy.scan_urls
and policy.enable_for_internal_senders
and policy.deliver_message_after_scan
and not policy.disable_url_rewrite
)
@@ -9,18 +9,38 @@ from prowler.providers.m365.m365_provider import M365Provider
class Defender(M365Service):
"""
Microsoft 365 Defender service implementation.
Microsoft Defender for Office 365 service class.
Provides access to Microsoft Defender for Office 365 configurations including
malware policies, spam filtering, anti-phishing, and Teams protection settings.
This class provides methods to retrieve and manage Microsoft Defender for Office 365
security policies and configurations, including malware filters, spam policies,
anti-phishing settings, Safe Attachments, Safe Links, ATP (Advanced Threat Protection),
and Teams protection policies.
Attributes:
malware_policies (list): List of malware filter policies.
outbound_spam_policies (dict): Dictionary of outbound spam filter policies.
outbound_spam_rules (dict): Dictionary of outbound spam filter rules.
antiphishing_policies (dict): Dictionary of anti-phishing policies.
antiphishing_rules (dict): Dictionary of anti-phishing rules.
connection_filter_policy: Connection filter policy configuration.
dkim_configurations (list): List of DKIM signing configurations.
inbound_spam_policies (list): List of inbound spam filter policies.
inbound_spam_rules (dict): Dictionary of inbound spam filter rules.
report_submission_policy: Report submission policy configuration.
safe_attachments_policies (dict): Dictionary of Safe Attachments policies.
safe_attachments_rules (dict): Dictionary of Safe Attachments rules.
advanced_threat_protection_policy: Advanced Threat Protection policy configuration.
safe_links_policies (dict): Dictionary of Safe Links policies.
safe_links_rules (dict): Dictionary of Safe Links rules.
teams_protection_policy: Teams protection policy configuration.
"""
def __init__(self, provider: M365Provider):
"""
Initialize the Defender service.
Initialize the Defender service client.
Args:
provider: The M365 provider instance.
provider: The M365Provider instance for authentication and configuration.
"""
super().__init__(provider)
self.malware_policies = []
@@ -33,6 +53,11 @@ class Defender(M365Service):
self.inbound_spam_policies = []
self.inbound_spam_rules = {}
self.report_submission_policy = None
self.safe_attachments_policies = {}
self.safe_attachments_rules = {}
self.advanced_threat_protection_policy = None
self.safe_links_policies = {}
self.safe_links_rules = {}
self.teams_protection_policy = None
if self.powershell:
if self.powershell.connect_exchange_online():
@@ -47,6 +72,13 @@ class Defender(M365Service):
self.inbound_spam_policies = self._get_inbound_spam_filter_policy()
self.inbound_spam_rules = self._get_inbound_spam_filter_rule()
self.report_submission_policy = self._get_report_submission_policy()
self.safe_attachments_policies = self._get_safe_attachments_policies()
self.safe_attachments_rules = self._get_safe_attachments_rules()
self.advanced_threat_protection_policy = (
self._get_advanced_threat_protection_policy()
)
self.safe_links_policies = self._get_safe_links_policy()
self.safe_links_rules = self._get_safe_links_rule()
self.teams_protection_policy = self._get_teams_protection_policy()
self.powershell.close()
@@ -366,10 +398,10 @@ class Defender(M365Service):
def _get_report_submission_policy(self):
"""
Retrieve the Defender report submission policy.
Get the Defender report submission policy.
Returns:
ReportSubmissionPolicy: The report submission policy configuration.
ReportSubmissionPolicy: The report submission policy configuration or None.
"""
logger.info("Microsoft365 - Getting Defender report submission policy...")
report_submission_policy = None
@@ -408,6 +440,179 @@ class Defender(M365Service):
)
return report_submission_policy
def _get_safe_attachments_policies(self):
"""
Retrieve Safe Attachments policies from Microsoft Defender for Office 365.
Returns:
dict[str, SafeAttachmentsPolicy]: A dictionary of Safe Attachments policies keyed by name.
"""
logger.info("Microsoft365 - Getting Defender Safe Attachments policies...")
safe_attachments_policies = {}
try:
policies_data = self.powershell.get_safe_attachments_policy()
if not policies_data:
return safe_attachments_policies
if isinstance(policies_data, dict):
policies_data = [policies_data]
for policy in policies_data:
if policy:
policy_name = policy.get("Name", "")
is_built_in = policy_name == "Built-In Protection Policy"
safe_attachments_policies[policy_name] = SafeAttachmentsPolicy(
name=policy_name,
identity=policy.get("Identity", ""),
enable=policy.get("Enable", False),
action=policy.get("Action", ""),
quarantine_tag=policy.get("QuarantineTag", ""),
redirect=policy.get("Redirect", False),
redirect_address=policy.get("RedirectAddress", ""),
is_built_in_protection=is_built_in,
)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return safe_attachments_policies
def _get_safe_attachments_rules(self):
"""
Retrieve Safe Attachments rules from Microsoft Defender for Office 365.
Returns:
dict[str, SafeAttachmentsRule]: A dictionary of Safe Attachments rules keyed by policy name.
"""
logger.info("Microsoft365 - Getting Defender Safe Attachments rules...")
safe_attachments_rules = {}
try:
rules_data = self.powershell.get_safe_attachments_rule()
if not rules_data:
return safe_attachments_rules
if isinstance(rules_data, dict):
rules_data = [rules_data]
for rule in rules_data:
if rule:
policy_name = rule.get("SafeAttachmentPolicy", "")
safe_attachments_rules[policy_name] = SafeAttachmentsRule(
state=rule.get("State", ""),
priority=rule.get("Priority", 0),
users=rule.get("SentTo"),
groups=rule.get("SentToMemberOf"),
domains=rule.get("RecipientDomainIs"),
)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return safe_attachments_rules
def _get_advanced_threat_protection_policy(self):
"""
Get the Advanced Threat Protection policy.
Retrieves the ATP policy settings including Safe Attachments for SharePoint,
OneDrive, and Teams, as well as Safe Documents configuration.
Returns:
AdvancedThreatProtectionPolicy: The Advanced Threat Protection policy configuration.
"""
logger.info("Microsoft365 - Getting Advanced Threat Protection policy...")
atp_policy = None
try:
policy = self.powershell.get_advanced_threat_protection_policy()
if policy:
atp_policy = AdvancedThreatProtectionPolicy(
identity=policy.get("Identity", "Default"),
enable_atp_for_spo_teams_odb=policy.get(
"EnableATPForSPOTeamsODB", False
),
enable_safe_docs=policy.get("EnableSafeDocs", False),
allow_safe_docs_open=policy.get("AllowSafeDocsOpen", True),
)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return atp_policy
def _get_safe_links_policy(self):
"""
Get Safe Links policies from Microsoft Defender for Office 365.
Returns:
dict: A dictionary mapping policy names to SafeLinksPolicy objects.
"""
logger.info("Microsoft365 - Getting Defender Safe Links policies...")
safe_links_policies = {}
try:
safe_links_policy_data = self.powershell.get_safe_links_policy()
if not safe_links_policy_data:
return safe_links_policies
if isinstance(safe_links_policy_data, dict):
safe_links_policy_data = [safe_links_policy_data]
for policy in safe_links_policy_data:
if policy:
safe_links_policies[policy.get("Name", "")] = SafeLinksPolicy(
name=policy.get("Name", ""),
identity=policy.get("Identity", ""),
enable_safe_links_for_email=policy.get(
"EnableSafeLinksForEmail", False
),
enable_safe_links_for_teams=policy.get(
"EnableSafeLinksForTeams", False
),
enable_safe_links_for_office=policy.get(
"EnableSafeLinksForOffice", False
),
track_clicks=policy.get("TrackClicks", False),
allow_click_through=policy.get("AllowClickThrough", True),
scan_urls=policy.get("ScanUrls", False),
enable_for_internal_senders=policy.get(
"EnableForInternalSenders", False
),
deliver_message_after_scan=policy.get(
"DeliverMessageAfterScan", False
),
disable_url_rewrite=policy.get("DisableUrlRewrite", True),
is_built_in_protection=policy.get("IsBuiltInProtection", False),
is_default=policy.get("IsDefault", False),
)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return safe_links_policies
def _get_safe_links_rule(self):
"""
Get Safe Links rules from Microsoft Defender for Office 365.
Returns:
dict: A dictionary mapping policy names to SafeLinksRule objects.
"""
logger.info("Microsoft365 - Getting Defender Safe Links rules...")
safe_links_rules = {}
try:
safe_links_rule_data = self.powershell.get_safe_links_rule()
if not safe_links_rule_data:
return safe_links_rules
if isinstance(safe_links_rule_data, dict):
safe_links_rule_data = [safe_links_rule_data]
for rule in safe_links_rule_data:
if rule:
safe_links_rules[rule.get("SafeLinksPolicy", "")] = SafeLinksRule(
state=rule.get("State", "Disabled"),
priority=rule.get("Priority", 0),
users=rule.get("SentTo", None),
groups=rule.get("SentToMemberOf", None),
domains=rule.get("RecipientDomainIs", None),
)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return safe_links_rules
def _get_teams_protection_policy(self):
"""
Retrieve the Teams protection policy including ZAP settings.
@@ -513,7 +718,7 @@ class InboundSpamRule(BaseModel):
class ReportSubmissionPolicy(BaseModel):
"""Model for Defender report submission policy settings."""
"""Model for Defender report submission policy configuration."""
report_junk_to_customized_address: bool
report_not_junk_to_customized_address: bool
@@ -525,6 +730,97 @@ class ReportSubmissionPolicy(BaseModel):
report_chat_message_to_customized_address_enabled: bool
class SafeAttachmentsPolicy(BaseModel):
"""
Data model for Safe Attachments policy settings.
Attributes:
name: The name of the policy.
identity: The unique identifier of the policy.
enable: Whether the policy is enabled.
action: The action to take on malicious attachments (Allow, Block, Replace, DynamicDelivery).
quarantine_tag: The quarantine policy applied to detected messages.
redirect: Whether to redirect messages with detected attachments.
redirect_address: The email address to redirect messages to.
is_built_in_protection: Whether this is the Built-in Protection Policy.
"""
name: str
identity: str
enable: bool
action: str
quarantine_tag: str
redirect: bool
redirect_address: str
is_built_in_protection: bool = False
class SafeAttachmentsRule(BaseModel):
"""
Data model for Safe Attachments rule settings.
Attributes:
state: The state of the rule (Enabled/Disabled).
priority: The priority of the rule (0 is highest).
users: List of users the rule applies to.
groups: List of groups the rule applies to.
domains: List of domains the rule applies to.
"""
state: str
priority: int
users: Optional[list[str]]
groups: Optional[list[str]]
domains: Optional[list[str]]
class AdvancedThreatProtectionPolicy(BaseModel):
"""
Model for Advanced Threat Protection policy.
Attributes:
identity: The identity of the ATP policy.
enable_atp_for_spo_teams_odb: Whether Safe Attachments is enabled for
SharePoint, OneDrive, and Microsoft Teams.
enable_safe_docs: Whether Safe Documents is enabled for clients in Protected View.
allow_safe_docs_open: Whether users can click through Protected View
even if Safe Documents identifies the file as malicious.
"""
identity: str
enable_atp_for_spo_teams_odb: bool
enable_safe_docs: bool
allow_safe_docs_open: bool
class SafeLinksPolicy(BaseModel):
"""Model for Defender Safe Links Policy configuration."""
name: str
identity: str
enable_safe_links_for_email: bool
enable_safe_links_for_teams: bool
enable_safe_links_for_office: bool
track_clicks: bool
allow_click_through: bool
scan_urls: bool
enable_for_internal_senders: bool
deliver_message_after_scan: bool
disable_url_rewrite: bool
is_built_in_protection: bool
is_default: bool
class SafeLinksRule(BaseModel):
"""Model for Defender Safe Links Rule configuration."""
state: str
priority: int
users: Optional[list[str]]
groups: Optional[list[str]]
domains: Optional[list[str]]
class TeamsProtectionPolicy(BaseModel):
"""Model for Teams protection policy settings including ZAP configuration."""
+1 -1
View File
@@ -92,7 +92,7 @@ maintainers = [{name = "Prowler Engineering", email = "engineering@prowler.com"}
name = "prowler"
readme = "README.md"
requires-python = ">3.9.1,<3.13"
version = "5.18.2"
version = "5.19.0"
[project.scripts]
prowler = "prowler.__main__:prowler"

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