Compare commits

..
Author SHA1 Message Date
Alan Buscaglia 173659ae0b fix(ci): prevent grep exit code 1 from failing empty dir check
- Add || true to grep -v that filters empty lines from VALID_PATHS
- GitHub Actions bash uses set -eo pipefail, so grep returning 1 (no
  matches) killed the script before reaching the graceful exit 0
2026-03-12 11:33:33 +01:00
Alan Buscaglia 37abe6b47e test(ci): add path resolution tests and architecture documentation
- Add test script for E2E path resolution logic (18 edge case scenarios)
- Add developer guide for test impact analysis system
2026-03-12 10:13:00 +01:00
Alan Buscaglia 7b2ad97317 fix(ci): gracefully skip E2E when test directories are empty
- Filter out resolved test paths that contain no spec/test files
- Exit gracefully instead of failing with Playwright "No tests found"
- Supports forward-looking e2e patterns for modules without tests yet
2026-03-12 09:58:41 +01:00
Josema Camacho 628a076118 docs(attack-paths): add module docstring to scan orchestrator (#10277) 2026-03-12 08:49:48 +01:00
Daniel Barranquero b08cb8ffb3 fix(csv): move OU columns to the end (#10307) 2026-03-12 08:28:52 +01:00
Josema CamachoandCopilot 57bcb74d0d fix(api): upgrade Cartography to 0.132.0 to fix exposed_internet on ELB/ELBv2 nodes (#10272)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-11 18:12:43 +01:00
39385567fc feat(organizations): add OU metadata to outputs (#10283)
Co-authored-by: Raajhesh Kannaa Chidambaram <495042+raajheshkannaa@users.noreply.github.com>
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-03-11 16:41:44 +01:00
Alan Buscaglia 125ba830f7 fix(ci): prevent E2E auth setups from running on broad path matches (#10304) 2026-03-11 15:38:18 +01:00
Alejandro Bailo db7554c8fb feat(ui): redesign providers page with modern table and cloud recursion (#10292) 2026-03-11 13:13:28 +01:00
lydiavilchez 65a7098104 feat(api): add Google Workspace provider API integration (#10247) 2026-03-11 12:06:30 +01:00
Daniel Barranquero e28bde797f feat(openstack): object storage service with 7 new checks (#10258) 2026-03-11 12:00:43 +01:00
Rubén De la Torre VicoandClaude Opus 4.6 cc0d83de91 docs(mcp_server): add Attack Paths MCP tools documentation (#10302)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 10:11:37 +01:00
UtwoandPepe Fagoaga e40beee315 feat: Helm CD (#10079)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
2026-03-11 10:07:22 +01:00
Daniel Barranquero e9855bbf2f docs: update mutelist docs (#10296) 2026-03-10 16:58:31 +01:00
Daniel Barranquero 2768b7ad4e docs: update readme and docs with new providers (#10295) 2026-03-10 16:58:08 +01:00
Josema Camacho 57f3920e66 refactor(api): migrate Attack Paths network exposure queries from APOC to openCypher (#10266) 2026-03-10 16:48:16 +01:00
Josema Camacho 3288a4a131 fix(api): add missing logging for Attack Paths query execution and scan error handling (#10269) 2026-03-10 16:47:53 +01:00
Michael WentzandDaniel Barranquero c4d692f77b feat(guardduty): add org-wide delegated admin check across all regions (#9867)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-03-10 12:56:00 +01:00
Adrián Peña 344a098ddc docs: document required permissions for mutelist features (#10294) 2026-03-10 12:20:25 +01:00
Eran CohenandDaniel Barranquero 0b461233c1 feat(iam): Add trusted IP configurable option to reduce false positives in 'opensearch' check (#8631)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-03-10 12:12:54 +01:00
Pepe FagoagaandCopilot d3213e9f1e chore(providers): Return 409 on conflict (#10293)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-10 10:54:09 +01:00
Alejandro Bailo e4bccfb26e chore(ui): move security changelog entry from v19.1 to v20 (#10291) 2026-03-10 09:54:30 +01:00
Rubén De la Torre VicoandDaniel Barranquero e3e2408717 chore(m365): enhance metadata for purview service (#9092)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-03-09 20:42:33 +01:00
Rubén De la Torre VicoandDaniel Barranquero 20efe001ff chore(m365): enhance metadata for defender service (#9681)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-03-09 20:13:45 +01:00
Rubén De la Torre VicoandDaniel Barranquero 9b64efeec2 chore(m365): enhance metadata for admincenter service (#9680)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-03-09 19:48:23 +01:00
Pedro Martín 23a8d4e680 feat(ui): improve organizations onboarding (#10274) 2026-03-09 16:54:50 +01:00
Daniel Barranquero 809142de35 chore(alibaba): update all metadata files (#10289) 2026-03-09 16:37:19 +01:00
Alejandro Bailo 1e95b48c86 fix(ui): rename error text token to text-text-error-primary (#10285) 2026-03-09 13:36:31 +01:00
Pepe Fagoaga 5a062b19dc chore: remove SaaS reference in dashboard (#10288) 2026-03-09 13:14:19 +01:00
Rubén De la Torre VicoandDaniel Barranquero b60867c5b6 chore(oraclecloud): enhance metadata for identity service (#9375)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-03-06 14:12:06 +01:00
Rubén De la Torre VicoandDaniel Barranquero 25c982d915 chore(oraclecloud): enhance metadata for events service (#9373)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-03-06 13:46:13 +01:00
Alejandro Bailo 2e60bb82d5 fix(ui): skip launch step when updating provider credentials (#10278) 2026-03-06 13:39:25 +01:00
Rubén De la Torre VicoandDaniel Barranquero ab92755e47 chore(oraclecloud): enhance metadata for objectstorage service (#9379)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-03-06 13:17:14 +01:00
Rubén De la Torre VicoandDaniel Barranquero 2e236a2cd1 chore(oraclecloud): enhance metadata for network service (#9378)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-03-06 13:05:51 +01:00
Rubén De la Torre VicoandDaniel Barranquero be6d1823c9 chore(oraclecloud): enhance metadata for kms service (#9377)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-03-06 12:39:01 +01:00
Pedro Martín 86daf7bc05 fix(pdf): align ENS report requirement status (#10270) 2026-03-06 12:36:50 +01:00
Rubén De la Torre VicoandDaniel Barranquero 1a6285c6a0 chore(oraclecloud): enhance metadata for integration service (#9376)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-03-06 12:30:50 +01:00
Alejandro Bailo acc6f731b4 chore(ui): update changelog for v1.20.0 (#10275) 2026-03-06 12:26:59 +01:00
Rubén De la Torre VicoandDaniel Barranquero 6aa524c47d chore(oraclecloud): enhance metadata for filestorage service (#9374)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-03-06 12:21:45 +01:00
Rubén De la Torre VicoandDaniel Barranquero ca992006b8 chore(oraclecloud): enhance metadata for database service (#9372)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-03-06 12:10:14 +01:00
Rubén De la Torre VicoandDaniel Barranquero 77c70114dc chore(oraclecloud): enhance metadata for compute service (#9371)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-03-06 12:01:15 +01:00
Daniel Barranquero 7ae14ea1ac chore(github): enhance metadata for 'organization' service (#10273) 2026-03-06 11:02:45 +01:00
Alejandro Bailo 48df613095 feat(ui): improve attack paths page layout and UX (#10249) 2026-03-06 10:49:11 +01:00
Rubén De la Torre VicoandDaniel Barranquero 97f4cb716d chore(github): enhance metadata for repository service (#9659)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-03-06 10:36:07 +01:00
Alejandro Bailo b1c5fa4c46 refactor(ui): migrate provider wizard forms from HeroUI to shadcn (#10259) 2026-03-06 10:13:47 +01:00
Rubén De la Torre VicoandDaniel Barranquero cc02c6f880 chore(mongodbatlas): enhance metadata for clusters service (#9657)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-03-06 10:09:24 +01:00
Rubén De la Torre VicoandDaniel Barranquero d5827f3e83 chore(mongodbatlas): enhance metadata for organizations service (#9658)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-03-06 09:58:38 +01:00
Hugo Pereira BritoandDaniel Barranquero 9cf63a2a68 feat(m365): add custom entra_conditional_access_policy_compliant_device_hybrid_joined_device_mfa_required check (#10197)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-03-05 18:11:20 +01:00
Alejandro Bailo e2fe482238 fix(ui): bump pnpm overrides to resolve 11 npm security vulnerabilities (#10267) 2026-03-05 14:00:44 +01:00
Pedro Martín 72938ca797 docs(aws): improve organizations (#10265) 2026-03-05 12:56:42 +01:00
Rubén De la Torre VicoandDaniel Barranquero fe9dbdfd2c chore(kubernetes): enhance metadata for scheduler service (#9679)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-03-05 12:08:47 +01:00
Rubén De la Torre VicoandDaniel Barranquero a5763289dd chore(kubernetes): enhance metadata for rbac service (#9678)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-03-05 11:57:18 +01:00
Rubén De la Torre VicoandDaniel Barranquero 36f4daf646 chore(kubernetes): enhance metadata for kubelet service (#9677)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-03-05 11:36:50 +01:00
Rubén De la Torre VicoandDaniel Barranquero 4a2d8111bc chore(kubernetes): enhance metadata for core service (#9676)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-03-05 11:24:54 +01:00
Hugo Pereira BritoandDaniel Barranquero 726b5665d0 feat(m365): add entra_conditional_access_policy_approved_client_app_required_for_mobile security check (#10216)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-03-05 10:58:18 +01:00
Rubén De la Torre VicoandDaniel Barranquero 5968441f59 chore(kubernetes): enhance metadata for controllermanager service (#9675)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-03-05 10:44:22 +01:00
Rubén De la Torre VicoandDaniel Barranquero 6069d6e231 chore(kubernetes): enhance metadata for apiserver service (#9674)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
2026-03-05 10:29:27 +01:00
Daniel BarranqueroandAndoni A. 9a4167d947 feat(docs): add Prowler Cloud docs to Openstack getting started (#10100)
Co-authored-by: Andoni A. <14891798+andoniaf@users.noreply.github.com>
2026-03-05 10:13:34 +01:00
Prowler Botandprowler-bot 43792f39c8 docs: Update version to v5.19.0 (#10255)
Co-authored-by: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
2026-03-04 21:18:41 +01:00
Prowler Botandprowler-bot 4e80e0564d chore(api): Bump version to v1.21.0 (#10254)
Co-authored-by: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
2026-03-04 21:18:34 +01:00
Prowler Botandprowler-bot a81931bb35 chore(release): Bump version to v5.20.0 (#10252)
Co-authored-by: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
2026-03-04 21:18:24 +01:00
Hugo Pereira BritoandAndoni A. 6ad991c63c docs(docs): add Prowler Cloud documentation for Cloudflare provider (#10151)
Co-authored-by: Andoni A. <14891798+andoniaf@users.noreply.github.com>
2026-03-04 17:36:21 +01:00
mintlify[bot]mintlify[bot] <109931778+mintlify[bot]@users.noreply.github.com>Andoni A.
104a4a92c3 docs: Add OCSF field requirements for Prowler Cloud integration (#10245)
Co-authored-by: mintlify[bot] <109931778+mintlify[bot]@users.noreply.github.com>
Co-authored-by: Andoni A. <14891798+andoniaf@users.noreply.github.com>
2026-03-04 11:59:22 +01:00
480 changed files with 16579 additions and 5970 deletions
+350
View File
@@ -0,0 +1,350 @@
#!/usr/bin/env bash
#
# Test script for E2E test path resolution logic from ui-e2e-tests-v2.yml.
# Validates that the shell logic correctly transforms E2E_TEST_PATHS into
# Playwright-compatible paths.
#
# Usage: .github/scripts/test-e2e-path-resolution.sh
set -euo pipefail
# -- Colors ------------------------------------------------------------------
RED='\033[0;31m'
GREEN='\033[0;32m'
BOLD='\033[1m'
RESET='\033[0m'
# -- Counters ----------------------------------------------------------------
TOTAL=0
PASSED=0
FAILED=0
# -- Temp directory setup & cleanup ------------------------------------------
TMPDIR_ROOT="$(mktemp -d)"
trap 'rm -rf "$TMPDIR_ROOT"' EXIT
# ---------------------------------------------------------------------------
# create_test_tree DIR [SUBDIRS_WITH_TESTS...]
#
# Creates a fake ui/tests/ tree inside DIR.
# All standard subdirs are created (empty).
# For each name in SUBDIRS_WITH_TESTS, a fake .spec.ts file is placed inside.
# ---------------------------------------------------------------------------
create_test_tree() {
local base="$1"; shift
local all_subdirs=(
auth home invitations profile providers scans
setups sign-in-base sign-up attack-paths findings
compliance browse manage-groups roles users overview
integrations
)
for d in "${all_subdirs[@]}"; do
mkdir -p "${base}/tests/${d}"
done
# Populate requested subdirs with a fake test file
for d in "$@"; do
mkdir -p "${base}/tests/${d}"
touch "${base}/tests/${d}/example.spec.ts"
done
}
# ---------------------------------------------------------------------------
# resolve_paths E2E_TEST_PATHS WORKING_DIR
#
# Extracted EXACT logic from .github/workflows/ui-e2e-tests-v2.yml lines 212-250.
# Outputs space-separated TEST_PATHS, or "SKIP" if no tests found.
# Must be run with WORKING_DIR as the cwd equivalent (we cd into it).
# ---------------------------------------------------------------------------
resolve_paths() {
local E2E_TEST_PATHS="$1"
local WORKING_DIR="$2"
(
cd "$WORKING_DIR"
# --- Line 212-214: strip ui/ prefix, strip **, deduplicate ---------------
TEST_PATHS="${E2E_TEST_PATHS}"
TEST_PATHS=$(echo "$TEST_PATHS" | sed 's|ui/||g' | sed 's|\*\*||g' | tr ' ' '\n' | sort -u)
# --- Line 216: drop setup helpers ----------------------------------------
TEST_PATHS=$(echo "$TEST_PATHS" | grep -v '^tests/setups/' || true)
# --- Lines 219-230: safety net for bare tests/ --------------------------
if echo "$TEST_PATHS" | grep -qx 'tests/'; then
SPECIFIC_DIRS=""
for dir in tests/*/; do
[[ "$dir" == "tests/setups/" ]] && continue
SPECIFIC_DIRS="${SPECIFIC_DIRS}${dir}"$'\n'
done
TEST_PATHS=$(echo "$TEST_PATHS" | grep -vx 'tests/' || true)
TEST_PATHS="${TEST_PATHS}"$'\n'"${SPECIFIC_DIRS}"
TEST_PATHS=$(echo "$TEST_PATHS" | grep -v '^$' | sort -u)
fi
# --- Lines 231-234: bail if empty ----------------------------------------
if [[ -z "$TEST_PATHS" ]]; then
echo "SKIP"
return
fi
# --- Lines 236-245: filter dirs with no test files -----------------------
VALID_PATHS=""
while IFS= read -r p; do
[[ -z "$p" ]] && continue
if find "$p" -name '*.spec.ts' -o -name '*.test.ts' 2>/dev/null | head -1 | grep -q .; then
VALID_PATHS="${VALID_PATHS}${p}"$'\n'
fi
done <<< "$TEST_PATHS"
VALID_PATHS=$(echo "$VALID_PATHS" | grep -v '^$')
# --- Lines 246-249: bail if all empty ------------------------------------
if [[ -z "$VALID_PATHS" ]]; then
echo "SKIP"
return
fi
# --- Line 250: final output (space-separated) ---------------------------
echo "$VALID_PATHS" | tr '\n' ' ' | sed 's/ $//'
)
}
# ---------------------------------------------------------------------------
# run_test NAME INPUT EXPECTED_TYPE [EXPECTED_VALUE]
#
# EXPECTED_TYPE is one of:
# "contains <path>" — output must contain this path
# "equals <value>" — output must exactly equal this value
# "skip" — expect SKIP (no runnable tests)
# "not_contains <p>" — output must NOT contain this path
#
# Multiple expectations can be specified by calling assert_* after run_test.
# For convenience, run_test supports a single assertion inline.
# ---------------------------------------------------------------------------
CURRENT_RESULT=""
CURRENT_TEST_NAME=""
run_test() {
local name="$1"
local input="$2"
local expect_type="$3"
local expect_value="${4:-}"
TOTAL=$((TOTAL + 1))
CURRENT_TEST_NAME="$name"
# Create a fresh temp tree per test
local test_dir="${TMPDIR_ROOT}/test_${TOTAL}"
mkdir -p "$test_dir"
# Default populated dirs: scans, providers, auth, home, profile, sign-up, sign-in-base
create_test_tree "$test_dir" scans providers auth home profile sign-up sign-in-base
CURRENT_RESULT=$(resolve_paths "$input" "$test_dir")
_check "$expect_type" "$expect_value"
}
# Like run_test but lets caller specify which subdirs have test files.
run_test_custom_tree() {
local name="$1"
local input="$2"
local expect_type="$3"
local expect_value="${4:-}"
shift 4
local populated_dirs=("$@")
TOTAL=$((TOTAL + 1))
CURRENT_TEST_NAME="$name"
local test_dir="${TMPDIR_ROOT}/test_${TOTAL}"
mkdir -p "$test_dir"
create_test_tree "$test_dir" "${populated_dirs[@]}"
CURRENT_RESULT=$(resolve_paths "$input" "$test_dir")
_check "$expect_type" "$expect_value"
}
_check() {
local expect_type="$1"
local expect_value="$2"
case "$expect_type" in
skip)
if [[ "$CURRENT_RESULT" == "SKIP" ]]; then
_pass
else
_fail "expected SKIP, got: '$CURRENT_RESULT'"
fi
;;
contains)
if [[ "$CURRENT_RESULT" == *"$expect_value"* ]]; then
_pass
else
_fail "expected to contain '$expect_value', got: '$CURRENT_RESULT'"
fi
;;
not_contains)
if [[ "$CURRENT_RESULT" != *"$expect_value"* ]]; then
_pass
else
_fail "expected NOT to contain '$expect_value', got: '$CURRENT_RESULT'"
fi
;;
equals)
if [[ "$CURRENT_RESULT" == "$expect_value" ]]; then
_pass
else
_fail "expected exactly '$expect_value', got: '$CURRENT_RESULT'"
fi
;;
*)
_fail "unknown expect_type: $expect_type"
;;
esac
}
_pass() {
PASSED=$((PASSED + 1))
printf '%b PASS%b %s\n' "$GREEN" "$RESET" "$CURRENT_TEST_NAME"
}
_fail() {
FAILED=$((FAILED + 1))
printf '%b FAIL%b %s\n' "$RED" "$RESET" "$CURRENT_TEST_NAME"
printf " %s\n" "$1"
}
# ===========================================================================
# TEST CASES
# ===========================================================================
echo ""
printf '%bE2E Path Resolution Tests%b\n' "$BOLD" "$RESET"
echo "=========================================="
# 1. Normal single module
run_test \
"1. Normal single module" \
"ui/tests/scans/**" \
"contains" "tests/scans/"
# 2. Multiple modules
run_test \
"2. Multiple modules — scans present" \
"ui/tests/scans/** ui/tests/providers/**" \
"contains" "tests/scans/"
run_test \
"2. Multiple modules — providers present" \
"ui/tests/scans/** ui/tests/providers/**" \
"contains" "tests/providers/"
# 3. Broad pattern (many modules)
run_test \
"3. Broad pattern — no bare tests/" \
"ui/tests/auth/** ui/tests/scans/** ui/tests/providers/** ui/tests/home/** ui/tests/profile/**" \
"not_contains" "tests/ "
# 4. Empty directory
run_test \
"4. Empty directory — skipped" \
"ui/tests/attack-paths/**" \
"skip"
# 5. Mix of populated and empty dirs
run_test \
"5. Mix populated+empty — scans present" \
"ui/tests/scans/** ui/tests/attack-paths/**" \
"contains" "tests/scans/"
run_test \
"5. Mix populated+empty — attack-paths absent" \
"ui/tests/scans/** ui/tests/attack-paths/**" \
"not_contains" "tests/attack-paths/"
# 6. All empty directories
run_test \
"6. All empty directories" \
"ui/tests/attack-paths/** ui/tests/findings/**" \
"skip"
# 7. Setup paths filtered
run_test \
"7. Setup paths filtered out" \
"ui/tests/setups/**" \
"skip"
# 8. Bare tests/ from broad pattern — safety net expands
run_test \
"8. Bare tests/ expands — scans present" \
"ui/tests/**" \
"contains" "tests/scans/"
run_test \
"8. Bare tests/ expands — setups excluded" \
"ui/tests/**" \
"not_contains" "tests/setups/"
# 9. Bare tests/ with all empty subdirs (only setups has files)
run_test_custom_tree \
"9. Bare tests/ — only setups has files" \
"ui/tests/**" \
"skip" "" \
setups
# 10. Duplicate paths
run_test \
"10. Duplicate paths — deduplicated" \
"ui/tests/scans/** ui/tests/scans/**" \
"equals" "tests/scans/"
# 11. Empty input
TOTAL=$((TOTAL + 1))
CURRENT_TEST_NAME="11. Empty input"
test_dir="${TMPDIR_ROOT}/test_${TOTAL}"
mkdir -p "$test_dir"
create_test_tree "$test_dir" scans providers
CURRENT_RESULT=$(resolve_paths "" "$test_dir")
_check "skip" ""
# 12. Trailing/leading whitespace
run_test \
"12. Whitespace handling" \
" ui/tests/scans/** " \
"contains" "tests/scans/"
# 13. Path without ui/ prefix
run_test \
"13. Path without ui/ prefix" \
"tests/scans/**" \
"contains" "tests/scans/"
# 14. Setup mixed with valid paths — only valid pass through
run_test \
"14. Setups + valid — setups filtered" \
"ui/tests/setups/** ui/tests/scans/**" \
"contains" "tests/scans/"
run_test \
"14. Setups + valid — setups absent" \
"ui/tests/setups/** ui/tests/scans/**" \
"not_contains" "tests/setups/"
# ===========================================================================
# SUMMARY
# ===========================================================================
echo ""
echo "=========================================="
if [[ "$FAILED" -eq 0 ]]; then
printf '%b%bAll tests passed: %d/%d%b\n' "$GREEN" "$BOLD" "$PASSED" "$TOTAL" "$RESET"
else
printf '%b%b%d/%d passed, %d FAILED%b\n' "$RED" "$BOLD" "$PASSED" "$TOTAL" "$FAILED" "$RESET"
fi
echo ""
exit "$FAILED"
+54 -6
View File
@@ -224,8 +224,24 @@ modules:
tests:
- api/src/backend/api/tests/test_views.py
e2e:
# API view changes can break UI
- ui/tests/**
# All E2E test suites (explicit to avoid triggering auth setups in tests/setups/)
- ui/tests/auth/**
- ui/tests/sign-in/**
- ui/tests/sign-up/**
- ui/tests/sign-in-base/**
- ui/tests/scans/**
- ui/tests/providers/**
- ui/tests/findings/**
- ui/tests/compliance/**
- ui/tests/invitations/**
- ui/tests/roles/**
- ui/tests/users/**
- ui/tests/integrations/**
- ui/tests/resources/**
- ui/tests/profile/**
- ui/tests/lighthouse/**
- ui/tests/home/**
- ui/tests/attack-paths/**
- name: api-serializers
match:
@@ -234,8 +250,24 @@ modules:
tests:
- api/src/backend/api/tests/**
e2e:
# Serializer changes affect API responses → UI
- ui/tests/**
# All E2E test suites (explicit to avoid triggering auth setups in tests/setups/)
- ui/tests/auth/**
- ui/tests/sign-in/**
- ui/tests/sign-up/**
- ui/tests/sign-in-base/**
- ui/tests/scans/**
- ui/tests/providers/**
- ui/tests/findings/**
- ui/tests/compliance/**
- ui/tests/invitations/**
- ui/tests/roles/**
- ui/tests/users/**
- ui/tests/integrations/**
- ui/tests/resources/**
- ui/tests/profile/**
- ui/tests/lighthouse/**
- ui/tests/home/**
- ui/tests/attack-paths/**
- name: api-filters
match:
@@ -407,8 +439,24 @@ modules:
- ui/components/ui/**
tests: []
e2e:
# Shared components can affect any E2E
- ui/tests/**
# All E2E test suites (explicit to avoid triggering auth setups in tests/setups/)
- ui/tests/auth/**
- ui/tests/sign-in/**
- ui/tests/sign-up/**
- ui/tests/sign-in-base/**
- ui/tests/scans/**
- ui/tests/providers/**
- ui/tests/findings/**
- ui/tests/compliance/**
- ui/tests/invitations/**
- ui/tests/roles/**
- ui/tests/users/**
- ui/tests/integrations/**
- ui/tests/resources/**
- ui/tests/profile/**
- ui/tests/lighthouse/**
- ui/tests/home/**
- ui/tests/attack-paths/**
- name: ui-attack-paths
match:
+48
View File
@@ -0,0 +1,48 @@
name: 'Helm: Chart Checks'
# DISCLAIMER: This workflow is not maintained by the Prowler team. Refer to contrib/k8s/helm/prowler-app for the source code.
on:
push:
branches:
- 'master'
- 'v5.*'
paths:
- 'contrib/k8s/helm/prowler-app/**'
pull_request:
branches:
- 'master'
- 'v5.*'
paths:
- 'contrib/k8s/helm/prowler-app/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
CHART_PATH: contrib/k8s/helm/prowler-app
jobs:
helm-lint:
if: github.repository == 'prowler-cloud/prowler'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: Set up Helm
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1
- name: Update chart dependencies
run: helm dependency update ${{ env.CHART_PATH }}
- name: Lint Helm chart
run: helm lint ${{ env.CHART_PATH }}
- name: Validate Helm chart template rendering
run: helm template prowler ${{ env.CHART_PATH }}
+54
View File
@@ -0,0 +1,54 @@
name: 'Helm: Chart Release'
# DISCLAIMER: This workflow is not maintained by the Prowler team. Refer to contrib/k8s/helm/prowler-app for the source code.
on:
release:
types:
- 'published'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
env:
CHART_PATH: contrib/k8s/helm/prowler-app
jobs:
release-helm-chart:
if: github.repository == 'prowler-cloud/prowler'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: Set up Helm
uses: azure/setup-helm@b9e51907a09c216f16ebe8536097933489208112 # v4.3.0
- name: Set appVersion from release tag
run: |
RELEASE_TAG="${GITHUB_EVENT_RELEASE_TAG_NAME}"
echo "Setting appVersion to ${RELEASE_TAG}"
sed -i "s/^appVersion:.*/appVersion: \"${RELEASE_TAG}\"/" ${{ env.CHART_PATH }}/Chart.yaml
env:
GITHUB_EVENT_RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
- name: Login to GHCR
run: echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io -u ${GITHUB_ACTOR} --password-stdin
- name: Update chart dependencies
run: helm dependency update ${{ env.CHART_PATH }}
- name: Package Helm chart
run: helm package ${{ env.CHART_PATH }} --destination .helm-packages
- name: Push chart to GHCR
run: |
PACKAGE=$(ls .helm-packages/*.tgz)
helm push "$PACKAGE" oci://ghcr.io/${{ github.repository_owner }}/charts
+30 -1
View File
@@ -214,11 +214,40 @@ jobs:
TEST_PATHS=$(echo "$TEST_PATHS" | sed 's|ui/||g' | sed 's|\*\*||g' | tr ' ' '\n' | sort -u)
# Drop auth setup helpers (not runnable test suites)
TEST_PATHS=$(echo "$TEST_PATHS" | grep -v '^tests/setups/')
# Safety net: if bare "tests/" appears (from broad patterns like ui/tests/**),
# expand to specific subdirs to avoid Playwright discovering setup files
if echo "$TEST_PATHS" | grep -qx 'tests/'; then
echo "Expanding bare 'tests/' to specific subdirs (excluding setups)..."
SPECIFIC_DIRS=""
for dir in tests/*/; do
[[ "$dir" == "tests/setups/" ]] && continue
SPECIFIC_DIRS="${SPECIFIC_DIRS}${dir}"$'\n'
done
# Replace "tests/" with specific dirs, keep other paths
TEST_PATHS=$(echo "$TEST_PATHS" | grep -vx 'tests/')
TEST_PATHS="${TEST_PATHS}"$'\n'"${SPECIFIC_DIRS}"
TEST_PATHS=$(echo "$TEST_PATHS" | grep -v '^$' | sort -u)
fi
if [[ -z "$TEST_PATHS" ]]; then
echo "No runnable E2E test paths after filtering setups"
exit 0
fi
TEST_PATHS=$(echo "$TEST_PATHS" | tr '\n' ' ')
# Filter out directories that don't contain any test files
VALID_PATHS=""
while IFS= read -r p; do
[[ -z "$p" ]] && continue
if find "$p" -name '*.spec.ts' -o -name '*.test.ts' 2>/dev/null | head -1 | grep -q .; then
VALID_PATHS="${VALID_PATHS}${p}"$'\n'
else
echo "Skipping empty test directory: $p"
fi
done <<< "$TEST_PATHS"
VALID_PATHS=$(echo "$VALID_PATHS" | grep -v '^$' || true)
if [[ -z "$VALID_PATHS" ]]; then
echo "No test files found in any resolved paths — skipping E2E"
exit 0
fi
TEST_PATHS=$(echo "$VALID_PATHS" | tr '\n' ' ')
echo "Resolved test paths: $TEST_PATHS"
pnpm exec playwright test $TEST_PATHS
fi
+7 -5
View File
@@ -109,14 +109,16 @@ Every AWS provider scan will enqueue an Attack Paths ingestion job automatically
| GCP | 100 | 13 | 15 | 11 | Official | UI, API, CLI |
| Kubernetes | 83 | 7 | 7 | 9 | Official | UI, API, CLI |
| GitHub | 21 | 2 | 1 | 2 | Official | UI, API, CLI |
| M365 | 75 | 7 | 4 | 4 | Official | UI, API, CLI |
| OCI | 51 | 13 | 3 | 12 | Official | UI, API, CLI |
| M365 | 89 | 9 | 4 | 5 | Official | UI, API, CLI |
| OCI | 48 | 13 | 3 | 10 | Official | UI, API, CLI |
| Alibaba Cloud | 61 | 9 | 3 | 9 | Official | UI, API, CLI |
| Cloudflare | 29 | 2 | 0 | 5 | Official | CLI, API |
| Cloudflare | 29 | 2 | 0 | 5 | Official | UI, API, 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 |
| MongoDB Atlas | 10 | 3 | 0 | 8 | 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 |
| Image | N/A | N/A | N/A | N/A | Official | CLI, API |
| Google Workspace | 1 | 1 | 0 | 1 | Official | CLI |
| OpenStack | 27 | 4 | 0 | 8 | Official | UI, API, CLI |
| NHN | 6 | 2 | 1 | 0 | Unofficial | CLI |
> [!Note]
+3 -1
View File
@@ -6,6 +6,7 @@ All notable changes to the **Prowler API** are documented in this file.
### 🔄 Changed
- Attack Paths: Migrate network exposure queries from APOC to standard openCypher for Neo4j and Neptune compatibility [(#10266)](https://github.com/prowler-cloud/prowler/pull/10266)
- `POST /api/v1/providers` returns `409 Conflict` if already exists [(#10293)](https://github.com/prowler-cloud/prowler/pull/10293)
---
@@ -14,7 +15,6 @@ All notable changes to the **Prowler API** are documented in this file.
### 🐞 Fixed
- Attack Paths: Security hardening for custom query endpoint (Cypher blocklist, input validation, rate limiting, Helm lockdown) [(#10238)](https://github.com/prowler-cloud/prowler/pull/10238)
- Attack Paths: Add missing logging for query execution and exception details in scan error handling [(#10269)](https://github.com/prowler-cloud/prowler/pull/10269)
- Attack Paths: Upgrade Cartography from 0.129.0 to 0.132.0, fixing `exposed_internet` not set on ELB/ELBv2 nodes [(#10272)](https://github.com/prowler-cloud/prowler/pull/10272)
@@ -29,6 +29,7 @@ All notable changes to the **Prowler API** are documented in this file.
- PDF report for the CSA CCM compliance framework [(#10088)](https://github.com/prowler-cloud/prowler/pull/10088)
- `image` provider support for container image scanning [(#10128)](https://github.com/prowler-cloud/prowler/pull/10128)
- Attack Paths: Custom query and Cartography schema endpoints (temporarily blocked) [(#10149)](https://github.com/prowler-cloud/prowler/pull/10149)
- `googleworkspace` provider support [(#10247)](https://github.com/prowler-cloud/prowler/pull/10247)
### 🔄 Changed
@@ -47,6 +48,7 @@ All notable changes to the **Prowler API** are documented in this file.
### 🐞 Fixed
- PDF compliance reports consistency with UI: exclude resourceless findings and fix ENS MANUAL status handling [(#10270)](https://github.com/prowler-cloud/prowler/pull/10270)
- Attack Paths: Orphaned temporary Neo4j databases are now cleaned up on scan failure and provider deletion [(#10101)](https://github.com/prowler-cloud/prowler/pull/10101)
- Attack Paths: scan no longer raises `DatabaseError` when provider is deleted mid-scan [(#10116)](https://github.com/prowler-cloud/prowler/pull/10116)
- Tenant compliance summaries recalculated after provider deletion [(#10172)](https://github.com/prowler-cloud/prowler/pull/10172)
+12 -382
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.19",
"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.20.1"
version = "1.21.0"
[project.scripts]
celery = "src.backend.config.settings.celery"
@@ -35,7 +35,6 @@ READ_EXCEPTION_CODES = [
"Neo.ClientError.Statement.AccessMode",
"Neo.ClientError.Procedure.ProcedureNotFound",
]
CLIENT_STATEMENT_EXCEPTION_PREFIX = "Neo.ClientError.Statement."
# Module-level process-wide driver singleton
_driver: neo4j.Driver | None = None
@@ -109,7 +108,6 @@ def get_session(
except neo4j.exceptions.Neo4jError as exc:
if (
default_access_mode == neo4j.READ_ACCESS
and exc.code
and exc.code in READ_EXCEPTION_CODES
):
message = "Read query not allowed"
@@ -117,10 +115,6 @@ def get_session(
raise WriteQueryNotAllowedException(message=message, code=code)
message = exc.message if exc.message is not None else str(exc)
if exc.code and exc.code.startswith(CLIENT_STATEMENT_EXCEPTION_PREFIX):
raise ClientStatementException(message=message, code=exc.code)
raise GraphDatabaseQueryException(message=message, code=exc.code)
finally:
@@ -233,7 +227,3 @@ class GraphDatabaseQueryException(Exception):
class WriteQueryNotAllowedException(GraphDatabaseQueryException):
pass
class ClientStatementException(GraphDatabaseQueryException):
pass
+17 -43
View File
@@ -16,8 +16,7 @@ AWS_INTERNET_EXPOSED_EC2_SENSITIVE_S3_ACCESS = AttackPathsQueryDefinition(
description="Detect EC2 instances with SSH exposed to the internet that can assume higher-privileged roles to read tagged sensitive S3 buckets despite bucket-level public access blocks.",
provider="aws",
cypher=f"""
CALL apoc.create.vNode(['Internet'], {{id: 'Internet', name: 'Internet', provider_id: $provider_id}})
YIELD node AS internet
OPTIONAL MATCH (internet:Internet {{_provider_id: $provider_id}})
MATCH path_s3 = (aws:AWSAccount {{id: $provider_uid}})--(s3:S3Bucket)--(t:AWSTag)
WHERE toLower(t.key) = toLower($tag_key) AND toLower(t.value) = toLower($tag_value)
@@ -32,8 +31,7 @@ AWS_INTERNET_EXPOSED_EC2_SENSITIVE_S3_ACCESS = AttackPathsQueryDefinition(
MATCH path_assume_role = (ec2)-[p:STS_ASSUMEROLE_ALLOW*1..9]-(r:AWSRole)
CALL apoc.create.vRelationship(internet, 'CAN_ACCESS', {{provider_id: $provider_id}}, ec2)
YIELD rel AS can_access
OPTIONAL MATCH (internet)-[can_access:CAN_ACCESS]->(ec2)
UNWIND nodes(path_s3) + nodes(path_ec2) + nodes(path_role) + nodes(path_assume_role) as n
OPTIONAL MATCH (n)-[pfr]-(pf:{PROWLER_FINDING_LABEL} {{status: 'FAIL', provider_uid: $provider_uid}})
@@ -181,14 +179,12 @@ AWS_EC2_INSTANCES_INTERNET_EXPOSED = AttackPathsQueryDefinition(
description="Find EC2 instances flagged as exposed to the internet within the selected account.",
provider="aws",
cypher=f"""
CALL apoc.create.vNode(['Internet'], {{id: 'Internet', name: 'Internet', provider_id: $provider_id}})
YIELD node AS internet
OPTIONAL MATCH (internet:Internet {{_provider_id: $provider_id}})
MATCH path = (aws:AWSAccount {{id: $provider_uid}})--(ec2:EC2Instance)
WHERE ec2.exposed_internet = true
CALL apoc.create.vRelationship(internet, 'CAN_ACCESS', {{provider_id: $provider_id}}, ec2)
YIELD rel AS can_access
OPTIONAL MATCH (internet)-[can_access:CAN_ACCESS]->(ec2)
UNWIND nodes(path) as n
OPTIONAL MATCH (n)-[pfr]-(pf:{PROWLER_FINDING_LABEL} {{status: 'FAIL', provider_uid: $provider_uid}})
@@ -205,16 +201,14 @@ AWS_SECURITY_GROUPS_OPEN_INTERNET_FACING = AttackPathsQueryDefinition(
description="Find internet-facing resources associated with security groups that allow inbound access from '0.0.0.0/0'.",
provider="aws",
cypher=f"""
CALL apoc.create.vNode(['Internet'], {{id: 'Internet', name: 'Internet', provider_id: $provider_id}})
YIELD node AS internet
OPTIONAL MATCH (internet:Internet {{_provider_id: $provider_id}})
// Match EC2 instances that are internet-exposed with open security groups (0.0.0.0/0)
MATCH path_ec2 = (aws:AWSAccount {{id: $provider_uid}})--(ec2:EC2Instance)--(sg:EC2SecurityGroup)--(ipi:IpPermissionInbound)--(ir:IpRange)
WHERE ec2.exposed_internet = true
AND ir.range = "0.0.0.0/0"
CALL apoc.create.vRelationship(internet, 'CAN_ACCESS', {{provider_id: $provider_id}}, ec2)
YIELD rel AS can_access
OPTIONAL MATCH (internet)-[can_access:CAN_ACCESS]->(ec2)
UNWIND nodes(path_ec2) as n
OPTIONAL MATCH (n)-[pfr]-(pf:{PROWLER_FINDING_LABEL} {{status: 'FAIL', provider_uid: $provider_uid}})
@@ -231,14 +225,12 @@ AWS_CLASSIC_ELB_INTERNET_EXPOSED = AttackPathsQueryDefinition(
description="Find Classic Load Balancers exposed to the internet along with their listeners.",
provider="aws",
cypher=f"""
CALL apoc.create.vNode(['Internet'], {{id: 'Internet', name: 'Internet', provider_id: $provider_id}})
YIELD node AS internet
OPTIONAL MATCH (internet:Internet {{_provider_id: $provider_id}})
MATCH path = (aws:AWSAccount {{id: $provider_uid}})--(elb:LoadBalancer)--(listener:ELBListener)
WHERE elb.exposed_internet = true
CALL apoc.create.vRelationship(internet, 'CAN_ACCESS', {{provider_id: $provider_id}}, elb)
YIELD rel AS can_access
OPTIONAL MATCH (internet)-[can_access:CAN_ACCESS]->(elb)
UNWIND nodes(path) as n
OPTIONAL MATCH (n)-[pfr]-(pf:{PROWLER_FINDING_LABEL} {{status: 'FAIL', provider_uid: $provider_uid}})
@@ -255,14 +247,12 @@ AWS_ELBV2_INTERNET_EXPOSED = AttackPathsQueryDefinition(
description="Find ELBv2 load balancers exposed to the internet along with their listeners.",
provider="aws",
cypher=f"""
CALL apoc.create.vNode(['Internet'], {{id: 'Internet', name: 'Internet', provider_id: $provider_id}})
YIELD node AS internet
OPTIONAL MATCH (internet:Internet {{_provider_id: $provider_id}})
MATCH path = (aws:AWSAccount {{id: $provider_uid}})--(elbv2:LoadBalancerV2)--(listener:ELBV2Listener)
WHERE elbv2.exposed_internet = true
CALL apoc.create.vRelationship(internet, 'CAN_ACCESS', {{provider_id: $provider_id}}, elbv2)
YIELD rel AS can_access
OPTIONAL MATCH (internet)-[can_access:CAN_ACCESS]->(elbv2)
UNWIND nodes(path) as n
OPTIONAL MATCH (n)-[pfr]-(pf:{PROWLER_FINDING_LABEL} {{status: 'FAIL', provider_uid: $provider_uid}})
@@ -279,31 +269,15 @@ AWS_PUBLIC_IP_RESOURCE_LOOKUP = AttackPathsQueryDefinition(
description="Given a public IP address, find the related AWS resource and its adjacent node within the selected account.",
provider="aws",
cypher=f"""
CALL apoc.create.vNode(['Internet'], {{id: 'Internet', name: 'Internet', provider_id: $provider_id}})
YIELD node AS internet
OPTIONAL MATCH (internet:Internet {{_provider_id: $provider_id}})
CALL () {{
MATCH path = (aws:AWSAccount {{id: $provider_uid}})-[r]-(x:EC2PrivateIp)-[q]-(y)
WHERE x.public_ip = $ip
RETURN path, x
MATCH path = (aws:AWSAccount {{id: $provider_uid}})-[r]-(x)-[q]-(y)
WHERE (x:EC2PrivateIp AND x.public_ip = $ip)
OR (x:EC2Instance AND x.publicipaddress = $ip)
OR (x:NetworkInterface AND x.public_ip = $ip)
OR (x:ElasticIPAddress AND x.public_ip = $ip)
UNION MATCH path = (aws:AWSAccount {{id: $provider_uid}})-[r]-(x:EC2Instance)-[q]-(y)
WHERE x.publicipaddress = $ip
RETURN path, x
UNION MATCH path = (aws:AWSAccount {{id: $provider_uid}})-[r]-(x:NetworkInterface)-[q]-(y)
WHERE x.public_ip = $ip
RETURN path, x
UNION MATCH path = (aws:AWSAccount {{id: $provider_uid}})-[r]-(x:ElasticIPAddress)-[q]-(y)
WHERE x.public_ip = $ip
RETURN path, x
}}
WITH path, x, internet
CALL apoc.create.vRelationship(internet, 'CAN_ACCESS', {{provider_id: $provider_id}}, x)
YIELD rel AS can_access
OPTIONAL MATCH (internet)-[can_access:CAN_ACCESS]->(x)
UNWIND nodes(path) as n
OPTIONAL MATCH (n)-[pfr]-(pf:{PROWLER_FINDING_LABEL} {{status: 'FAIL', provider_uid: $provider_uid}})
@@ -1,5 +1,4 @@
import logging
import re
from typing import Any, Iterable
@@ -118,38 +117,6 @@ def execute_query(
# Custom query helpers
# Patterns that indicate SSRF or dangerous procedure calls
# Defense-in-depth layer - the primary control is `neo4j.READ_ACCESS`
_BLOCKED_PATTERNS = [
re.compile(r"\bLOAD\s+CSV\b", re.IGNORECASE),
re.compile(r"\bapoc\.load\b", re.IGNORECASE),
re.compile(r"\bapoc\.import\b", re.IGNORECASE),
re.compile(r"\bapoc\.export\b", re.IGNORECASE),
re.compile(r"\bapoc\.cypher\b", re.IGNORECASE),
re.compile(r"\bapoc\.systemdb\b", re.IGNORECASE),
re.compile(r"\bapoc\.config\b", re.IGNORECASE),
re.compile(r"\bapoc\.periodic\b", re.IGNORECASE),
re.compile(r"\bapoc\.do\b", re.IGNORECASE),
re.compile(r"\bapoc\.trigger\b", re.IGNORECASE),
re.compile(r"\bapoc\.custom\b", re.IGNORECASE),
]
# Strip string literals so patterns inside quotes don't cause false positives
# Handles escaped quotes (\' and \") inside strings
_STRING_LITERALS = re.compile(r"'(?:[^'\\]|\\.)*'|\"(?:[^\"\\]|\\.)*\"")
def validate_custom_query(cypher: str) -> None:
"""Reject queries containing known SSRF or dangerous procedure patterns.
Raises ValidationError if a blocked pattern is found.
String literals are stripped before matching to avoid false positives.
"""
stripped = _STRING_LITERALS.sub("", cypher)
for pattern in _BLOCKED_PATTERNS:
if pattern.search(stripped):
raise ValidationError({"query": "Query contains a blocked operation"})
def normalize_custom_query_payload(raw_data):
if not isinstance(raw_data, dict):
@@ -168,8 +135,6 @@ def execute_custom_query(
cypher: str,
provider_id: str,
) -> dict[str, Any]:
validate_custom_query(cypher)
try:
graph = graph_database.execute_read_query(
database=database_name,
@@ -178,9 +143,6 @@ def execute_custom_query(
serialized = _serialize_graph(graph, provider_id)
return _truncate_graph(serialized)
except graph_database.ClientStatementException as exc:
raise ValidationError({"query": exc.message})
except graph_database.WriteQueryNotAllowedException:
raise PermissionDenied(
"Attack Paths query execution failed: read-only queries are enforced"
@@ -265,12 +227,6 @@ def _serialize_graph(graph, provider_id: str) -> dict[str, Any]:
},
)
filtered_count = len(graph.nodes) - len(nodes)
if filtered_count > 0:
logger.debug(
f"Filtered {filtered_count} nodes without matching provider_id={provider_id}"
)
relationships = []
for relationship in graph.relationships:
if relationship._properties.get("provider_id") != provider_id:
@@ -0,0 +1,39 @@
from django.db import migrations
import api.db_utils
class Migration(migrations.Migration):
dependencies = [
("api", "0083_image_provider"),
]
operations = [
migrations.AlterField(
model_name="provider",
name="provider",
field=api.db_utils.ProviderEnumField(
choices=[
("aws", "AWS"),
("azure", "Azure"),
("gcp", "GCP"),
("kubernetes", "Kubernetes"),
("m365", "M365"),
("github", "GitHub"),
("mongodbatlas", "MongoDB Atlas"),
("iac", "IaC"),
("oraclecloud", "Oracle Cloud Infrastructure"),
("alibabacloud", "Alibaba Cloud"),
("cloudflare", "Cloudflare"),
("openstack", "OpenStack"),
("image", "Image"),
("googleworkspace", "Google Workspace"),
],
default="aws",
),
),
migrations.RunSQL(
"ALTER TYPE provider ADD VALUE IF NOT EXISTS 'googleworkspace';",
reverse_sql=migrations.RunSQL.noop,
),
]
+10
View File
@@ -293,6 +293,7 @@ class Provider(RowLevelSecurityProtectedModel):
CLOUDFLARE = "cloudflare", _("Cloudflare")
OPENSTACK = "openstack", _("OpenStack")
IMAGE = "image", _("Image")
GOOGLEWORKSPACE = "googleworkspace", _("Google Workspace")
@staticmethod
def validate_aws_uid(value):
@@ -342,6 +343,15 @@ class Provider(RowLevelSecurityProtectedModel):
pointer="/data/attributes/uid",
)
@staticmethod
def validate_googleworkspace_uid(value):
if not re.match(r"^C[0-9a-zA-Z]+$", value):
raise ModelValidationError(
detail="Google Workspace Customer ID must start with 'C' followed by one or more alphanumeric characters (e.g., C01234abc, C12345678).",
code="googleworkspace-uid",
pointer="/data/attributes/uid",
)
@staticmethod
def validate_kubernetes_uid(value):
if not re.match(
File diff suppressed because it is too large Load Diff
@@ -501,72 +501,6 @@ def test_execute_custom_query_wraps_graph_errors():
mock_logger.error.assert_called_once()
# -- validate_custom_query ------------------------------------------------
@pytest.mark.parametrize(
"cypher",
[
"LOAD CSV FROM 'http://169.254.169.254/' AS x RETURN x",
"load csv from 'http://evil.com' as row return row",
"CALL apoc.load.json('http://evil.com/') YIELD value RETURN value",
"CALL apoc.load.csvParams('http://evil.com/', {}, null) YIELD list RETURN list",
"CALL apoc.import.csv([{fileName: 'f'}], [], {}) YIELD node RETURN node",
"CALL apoc.export.csv.all('file.csv', {})",
"CALL apoc.cypher.run('CREATE (n)', {}) YIELD value RETURN value",
"CALL apoc.systemdb.graph() YIELD nodes RETURN nodes",
"CALL apoc.config.list() YIELD key, value RETURN key, value",
"CALL apoc.periodic.iterate('MATCH (n) RETURN n', 'DELETE n', {batchSize: 100})",
"CALL apoc.do.when(true, 'CREATE (n) RETURN n', '', {}) YIELD value RETURN value",
"CALL apoc.trigger.add('t', 'RETURN 1', {phase: 'before'})",
"CALL apoc.custom.asProcedure('myProc', 'RETURN 1')",
],
ids=[
"LOAD_CSV",
"LOAD_CSV_lowercase",
"apoc.load.json",
"apoc.load.csvParams",
"apoc.import.csv",
"apoc.export.csv",
"apoc.cypher.run",
"apoc.systemdb.graph",
"apoc.config.list",
"apoc.periodic.iterate",
"apoc.do.when",
"apoc.trigger.add",
"apoc.custom.asProcedure",
],
)
def test_validate_custom_query_rejects_blocked_patterns(cypher):
with pytest.raises(ValidationError) as exc:
views_helpers.validate_custom_query(cypher)
assert "blocked operation" in str(exc.value.detail)
@pytest.mark.parametrize(
"cypher",
[
"MATCH (n:AWSAccount) RETURN n LIMIT 10",
"MATCH (a)-[r]->(b) RETURN a, r, b",
"MATCH (n) WHERE n.name CONTAINS 'load' RETURN n",
"CALL apoc.create.vNode(['Label'], {}) YIELD node RETURN node",
"MATCH (n) WHERE n.name = 'apoc.load.json' RETURN n",
'MATCH (n) WHERE n.description = "LOAD CSV is cool" RETURN n',
],
ids=[
"simple_match",
"traversal",
"contains_load_substring",
"apoc_virtual_node",
"apoc_load_inside_single_quotes",
"load_csv_inside_double_quotes",
],
)
def test_validate_custom_query_allows_clean_queries(cypher):
views_helpers.validate_custom_query(cypher)
# -- _truncate_graph ----------------------------------------------------------
+8
View File
@@ -23,6 +23,9 @@ from prowler.providers.azure.azure_provider import AzureProvider
from prowler.providers.cloudflare.cloudflare_provider import CloudflareProvider
from prowler.providers.gcp.gcp_provider import GcpProvider
from prowler.providers.github.github_provider import GithubProvider
from prowler.providers.googleworkspace.googleworkspace_provider import (
GoogleworkspaceProvider,
)
from prowler.providers.iac.iac_provider import IacProvider
from prowler.providers.image.image_provider import ImageProvider
from prowler.providers.kubernetes.kubernetes_provider import KubernetesProvider
@@ -113,6 +116,7 @@ class TestReturnProwlerProvider:
[
(Provider.ProviderChoices.AWS.value, AwsProvider),
(Provider.ProviderChoices.GCP.value, GcpProvider),
(Provider.ProviderChoices.GOOGLEWORKSPACE.value, GoogleworkspaceProvider),
(Provider.ProviderChoices.AZURE.value, AzureProvider),
(Provider.ProviderChoices.KUBERNETES.value, KubernetesProvider),
(Provider.ProviderChoices.M365.value, M365Provider),
@@ -248,6 +252,10 @@ class TestGetProwlerProviderKwargs:
Provider.ProviderChoices.GCP.value,
{"project_ids": ["provider_uid"]},
),
(
Provider.ProviderChoices.GOOGLEWORKSPACE.value,
{},
),
(
Provider.ProviderChoices.KUBERNETES.value,
{"context": "provider_uid"},
+77 -346
View File
@@ -1190,6 +1190,26 @@ class TestProviderViewSet:
"uid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"alias": "OpenStack Project",
},
{
"provider": "googleworkspace",
"uid": "C01234abc",
"alias": "Google Workspace Customer",
},
{
"provider": "googleworkspace",
"uid": "C12345678",
"alias": "Google Workspace All Digits",
},
{
"provider": "googleworkspace",
"uid": "CABCDEF123",
"alias": "Google Workspace Uppercase",
},
{
"provider": "googleworkspace",
"uid": "C12",
"alias": "Google Workspace Minimum Length",
},
]
),
)
@@ -1342,7 +1362,11 @@ class TestProviderViewSet:
response = authenticated_client.post(
reverse("provider-list"), data=provider_json_payload, format="json"
)
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.status_code == status.HTTP_409_CONFLICT
error = response.json()["errors"][0]
assert error["detail"] == "Provider already exists."
assert error["code"] == "conflict"
assert error["source"]["pointer"] == "/data/attributes/uid"
mock_delete_task.reset_mock()
mock_delete_task.return_value = task_mock
@@ -1634,6 +1658,36 @@ class TestProviderViewSet:
"min_length",
"uid",
),
# Google Workspace UID validation - missing 'C' prefix
(
{
"provider": "googleworkspace",
"uid": "01234abc",
"alias": "test",
},
"googleworkspace-uid",
"uid",
),
# Google Workspace UID validation - contains special characters
(
{
"provider": "googleworkspace",
"uid": "C0123-abc",
"alias": "test",
},
"googleworkspace-uid",
"uid",
),
# Google Workspace UID validation - lowercase 'c' prefix
(
{
"provider": "googleworkspace",
"uid": "c12345678",
"alias": "test",
},
"googleworkspace-uid",
"uid",
),
]
),
)
@@ -1807,21 +1861,21 @@ class TestProviderViewSet:
(
"uid.icontains",
"1",
10,
11,
),
("alias", "aws_testing_1", 1),
("alias.icontains", "aws", 2),
("inserted_at", TODAY, 11),
("inserted_at", TODAY, 12),
(
"inserted_at.gte",
"2024-01-01",
11,
12,
),
("inserted_at.lte", "2024-01-01", 0),
(
"updated_at.gte",
"2024-01-01",
11,
12,
),
("updated_at.lte", "2024-01-01", 0),
]
@@ -2437,6 +2491,15 @@ class TestProviderSecretViewSet:
"clouds_yaml_cloud": "mycloud",
},
),
# Google Workspace with service account credentials
(
Provider.ProviderChoices.GOOGLEWORKSPACE.value,
ProviderSecret.TypeChoices.STATIC,
{
"credentials_content": '{"type": "service_account", "project_id": "test-project", "private_key_id": "key123", "private_key": "-----BEGIN PRIVATE KEY-----\\ntest\\n-----END PRIVATE KEY-----\\n", "client_email": "test@test-project.iam.gserviceaccount.com", "client_id": "123456789"}',
"delegated_user": "admin@example.com",
},
),
],
)
def test_provider_secrets_create_valid(
@@ -3747,12 +3810,6 @@ class TestTaskViewSet:
@pytest.mark.django_db
class TestAttackPathsScanViewSet:
@pytest.fixture(autouse=True)
def _clear_throttle_cache(self):
from django.core.cache import cache
cache.clear()
@staticmethod
def _run_payload(query_id="aws-rds", parameters=None):
return {
@@ -4354,6 +4411,8 @@ class TestAttackPathsScanViewSet:
}
}
# TODO: Remove skip once queries/custom and schema endpoints are unblocked
@pytest.mark.skip(reason="Endpoint temporarily blocked")
def test_run_custom_query_returns_graph(
self,
authenticated_client,
@@ -4411,6 +4470,7 @@ class TestAttackPathsScanViewSet:
assert attributes["total_nodes"] == 1
assert attributes["truncated"] is False
@pytest.mark.skip(reason="Endpoint temporarily blocked")
def test_run_custom_query_returns_text_when_accept_text_plain(
self,
authenticated_client,
@@ -4465,6 +4525,7 @@ class TestAttackPathsScanViewSet:
assert "## Relationships (0)" in body
assert "## Summary" in body
@pytest.mark.skip(reason="Endpoint temporarily blocked")
def test_run_custom_query_returns_404_when_no_nodes(
self,
authenticated_client,
@@ -4506,6 +4567,7 @@ class TestAttackPathsScanViewSet:
assert response.status_code == status.HTTP_404_NOT_FOUND
@pytest.mark.skip(reason="Endpoint temporarily blocked")
def test_run_custom_query_returns_400_when_graph_not_ready(
self,
authenticated_client,
@@ -4532,6 +4594,7 @@ class TestAttackPathsScanViewSet:
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert "not available" in response.json()["errors"][0]["detail"]
@pytest.mark.skip(reason="Endpoint temporarily blocked")
def test_run_custom_query_returns_403_for_write_query(
self,
authenticated_client,
@@ -4569,343 +4632,9 @@ class TestAttackPathsScanViewSet:
assert response.status_code == status.HTTP_403_FORBIDDEN
# -- SSRF blocklist (HTTP level) ----------------------------------------------
@pytest.mark.parametrize(
"cypher",
[
"LOAD CSV FROM 'http://169.254.169.254/' AS x RETURN x",
"CALL apoc.load.json('http://evil.com/') YIELD value RETURN value",
"CALL apoc.import.csv([{fileName: 'f'}], [], {}) YIELD node RETURN node",
"CALL apoc.export.csv.all('file.csv', {})",
"CALL apoc.cypher.run('CREATE (n)', {}) YIELD value RETURN value",
"CALL apoc.systemdb.graph() YIELD nodes RETURN nodes",
],
ids=[
"LOAD_CSV",
"apoc.load",
"apoc.import",
"apoc.export",
"apoc.cypher.run",
"apoc.systemdb",
],
)
def test_run_custom_query_rejects_ssrf_patterns(
self,
authenticated_client,
providers_fixture,
scans_fixture,
create_attack_paths_scan,
cypher,
):
provider = providers_fixture[0]
attack_paths_scan = create_attack_paths_scan(
provider,
scan=scans_fixture[0],
graph_data_ready=True,
)
with patch(
"api.v1.views.graph_database.get_database_name",
return_value="db-test",
):
response = authenticated_client.post(
reverse(
"attack-paths-scans-queries-custom",
kwargs={"pk": attack_paths_scan.id},
),
data=self._custom_query_payload(cypher),
content_type=API_JSON_CONTENT_TYPE,
)
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert "blocked" in response.json()["errors"][0]["detail"].lower()
# -- Cross-tenant isolation ---------------------------------------------------
def test_run_custom_query_returns_404_for_foreign_tenant(
self,
authenticated_client,
create_attack_paths_scan,
):
from api.models import Provider, Tenant
foreign_tenant = Tenant.objects.create(name="foreign-tenant")
foreign_provider = Provider.objects.create(
tenant=foreign_tenant,
provider="aws",
uid="123456789999",
)
attack_paths_scan = create_attack_paths_scan(
foreign_provider,
graph_data_ready=True,
)
with patch(
"api.v1.views.graph_database.get_database_name",
return_value="db-test",
):
response = authenticated_client.post(
reverse(
"attack-paths-scans-queries-custom",
kwargs={"pk": attack_paths_scan.id},
),
data=self._custom_query_payload(),
content_type=API_JSON_CONTENT_TYPE,
)
assert response.status_code == status.HTTP_404_NOT_FOUND
def test_cartography_schema_returns_404_for_foreign_tenant(
self,
authenticated_client,
create_attack_paths_scan,
):
from api.models import Provider, Tenant
foreign_tenant = Tenant.objects.create(name="foreign-tenant-schema")
foreign_provider = Provider.objects.create(
tenant=foreign_tenant,
provider="aws",
uid="123456789998",
)
attack_paths_scan = create_attack_paths_scan(
foreign_provider,
graph_data_ready=True,
)
response = authenticated_client.get(
reverse(
"attack-paths-scans-schema",
kwargs={"pk": attack_paths_scan.id},
)
)
assert response.status_code == status.HTTP_404_NOT_FOUND
# -- Authentication / authorization -------------------------------------------
def test_run_custom_query_returns_401_unauthenticated(
self,
providers_fixture,
scans_fixture,
create_attack_paths_scan,
):
from rest_framework.test import APIClient
provider = providers_fixture[0]
attack_paths_scan = create_attack_paths_scan(
provider,
scan=scans_fixture[0],
graph_data_ready=True,
)
unauthenticated = APIClient()
response = unauthenticated.post(
reverse(
"attack-paths-scans-queries-custom",
kwargs={"pk": attack_paths_scan.id},
),
data=self._custom_query_payload(),
content_type=API_JSON_CONTENT_TYPE,
)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
def test_cartography_schema_returns_401_unauthenticated(
self,
providers_fixture,
scans_fixture,
create_attack_paths_scan,
):
from rest_framework.test import APIClient
provider = providers_fixture[0]
attack_paths_scan = create_attack_paths_scan(
provider,
scan=scans_fixture[0],
graph_data_ready=True,
)
unauthenticated = APIClient()
response = unauthenticated.get(
reverse(
"attack-paths-scans-schema",
kwargs={"pk": attack_paths_scan.id},
)
)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
def test_run_custom_query_returns_403_no_manage_scans(
self,
authenticated_client_no_permissions_rbac,
providers_fixture,
scans_fixture,
create_attack_paths_scan,
):
provider = providers_fixture[0]
attack_paths_scan = create_attack_paths_scan(
provider,
scan=scans_fixture[0],
graph_data_ready=True,
)
response = authenticated_client_no_permissions_rbac.post(
reverse(
"attack-paths-scans-queries-custom",
kwargs={"pk": attack_paths_scan.id},
),
data=self._custom_query_payload(),
content_type=API_JSON_CONTENT_TYPE,
)
assert response.status_code == status.HTTP_403_FORBIDDEN
# -- Error leakage ------------------------------------------------------------
def test_run_custom_query_does_not_leak_internals_on_error(
self,
authenticated_client,
providers_fixture,
scans_fixture,
create_attack_paths_scan,
):
from rest_framework.exceptions import APIException
provider = providers_fixture[0]
attack_paths_scan = create_attack_paths_scan(
provider,
scan=scans_fixture[0],
graph_data_ready=True,
)
with (
patch(
"api.v1.views.attack_paths_views_helpers.execute_custom_query",
side_effect=APIException(
"Attack Paths query execution failed due to a database error"
),
),
patch(
"api.v1.views.graph_database.get_database_name",
return_value="db-test",
),
):
response = authenticated_client.post(
reverse(
"attack-paths-scans-queries-custom",
kwargs={"pk": attack_paths_scan.id},
),
data=self._custom_query_payload(),
content_type=API_JSON_CONTENT_TYPE,
)
assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR
body = json.dumps(response.json()).lower()
for forbidden_term in ["neo4j", "bolt://", "syntaxerror", "db-tenant-"]:
assert forbidden_term not in body
# -- Rate limiting (throttle) -------------------------------------------------
def test_run_custom_query_throttled_after_limit(
self,
authenticated_client,
providers_fixture,
scans_fixture,
create_attack_paths_scan,
):
provider = providers_fixture[0]
attack_paths_scan = create_attack_paths_scan(
provider,
scan=scans_fixture[0],
graph_data_ready=True,
)
mock_graph = {
"nodes": [{"id": "n1", "labels": ["Test"], "properties": {}}],
"relationships": [],
"total_nodes": 1,
"truncated": False,
}
url = reverse(
"attack-paths-scans-queries-custom",
kwargs={"pk": attack_paths_scan.id},
)
payload = self._custom_query_payload()
with (
patch(
"api.v1.views.attack_paths_views_helpers.execute_custom_query",
return_value=mock_graph,
),
patch(
"api.v1.views.graph_database.get_database_name",
return_value="db-test",
),
patch(
"api.v1.views.graph_database.clear_cache",
),
):
for i in range(11):
response = authenticated_client.post(
url,
data=payload,
content_type=API_JSON_CONTENT_TYPE,
)
if i < 10:
assert (
response.status_code == status.HTTP_200_OK
), f"Request {i + 1} should succeed with 200 OK, got {response.status_code}"
else:
assert (
response.status_code == status.HTTP_429_TOO_MANY_REQUESTS
), f"Request {i + 1} should be throttled"
# -- Timeout simulation -------------------------------------------------------
def test_run_custom_query_returns_500_on_database_timeout(
self,
authenticated_client,
providers_fixture,
scans_fixture,
create_attack_paths_scan,
):
from rest_framework.exceptions import APIException
provider = providers_fixture[0]
attack_paths_scan = create_attack_paths_scan(
provider,
scan=scans_fixture[0],
graph_data_ready=True,
)
with (
patch(
"api.v1.views.attack_paths_views_helpers.execute_custom_query",
side_effect=APIException(
"Attack Paths query execution failed due to a database error"
),
),
patch(
"api.v1.views.graph_database.get_database_name",
return_value="db-test",
),
):
response = authenticated_client.post(
reverse(
"attack-paths-scans-queries-custom",
kwargs={"pk": attack_paths_scan.id},
),
data=self._custom_query_payload(),
content_type=API_JSON_CONTENT_TYPE,
)
assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR
# -- cartography_schema action ------------------------------------------------
@pytest.mark.skip(reason="Endpoint temporarily blocked")
def test_cartography_schema_returns_urls(
self,
authenticated_client,
@@ -4955,6 +4684,7 @@ class TestAttackPathsScanViewSet:
assert "schema.md" in attributes["schema_url"]
assert "raw.githubusercontent.com" in attributes["raw_schema_url"]
@pytest.mark.skip(reason="Endpoint temporarily blocked")
def test_cartography_schema_returns_404_when_no_metadata(
self,
authenticated_client,
@@ -4989,6 +4719,7 @@ class TestAttackPathsScanViewSet:
assert response.status_code == status.HTTP_404_NOT_FOUND
assert "No cartography schema metadata" in str(response.json())
@pytest.mark.skip(reason="Endpoint temporarily blocked")
def test_cartography_schema_returns_400_when_graph_not_ready(
self,
authenticated_client,
+13 -2
View File
@@ -27,6 +27,9 @@ if TYPE_CHECKING:
from prowler.providers.cloudflare.cloudflare_provider import CloudflareProvider
from prowler.providers.gcp.gcp_provider import GcpProvider
from prowler.providers.github.github_provider import GithubProvider
from prowler.providers.googleworkspace.googleworkspace_provider import (
GoogleworkspaceProvider,
)
from prowler.providers.iac.iac_provider import IacProvider
from prowler.providers.image.image_provider import ImageProvider
from prowler.providers.kubernetes.kubernetes_provider import KubernetesProvider
@@ -83,6 +86,7 @@ def return_prowler_provider(
| CloudflareProvider
| GcpProvider
| GithubProvider
| GoogleworkspaceProvider
| IacProvider
| ImageProvider
| KubernetesProvider
@@ -97,7 +101,7 @@ def return_prowler_provider(
provider (Provider): The provider object containing the provider type and associated secrets.
Returns:
AlibabacloudProvider | AwsProvider | AzureProvider | CloudflareProvider | GcpProvider | GithubProvider | IacProvider | ImageProvider | KubernetesProvider | M365Provider | MongodbatlasProvider | OpenstackProvider | OraclecloudProvider: The corresponding provider class.
AlibabacloudProvider | AwsProvider | AzureProvider | CloudflareProvider | GcpProvider | GithubProvider | GoogleworkspaceProvider | IacProvider | ImageProvider | KubernetesProvider | M365Provider | MongodbatlasProvider | OpenstackProvider | OraclecloudProvider: The corresponding provider class.
Raises:
ValueError: If the provider type specified in `provider.provider` is not supported.
@@ -111,6 +115,12 @@ def return_prowler_provider(
from prowler.providers.gcp.gcp_provider import GcpProvider
prowler_provider = GcpProvider
case Provider.ProviderChoices.GOOGLEWORKSPACE.value:
from prowler.providers.googleworkspace.googleworkspace_provider import (
GoogleworkspaceProvider,
)
prowler_provider = GoogleworkspaceProvider
case Provider.ProviderChoices.AZURE.value:
from prowler.providers.azure.azure_provider import AzureProvider
@@ -263,6 +273,7 @@ def initialize_prowler_provider(
| CloudflareProvider
| GcpProvider
| GithubProvider
| GoogleworkspaceProvider
| IacProvider
| ImageProvider
| KubernetesProvider
@@ -278,7 +289,7 @@ def initialize_prowler_provider(
mutelist_processor (Processor): The mutelist processor object containing the mutelist configuration.
Returns:
AlibabacloudProvider | AwsProvider | AzureProvider | CloudflareProvider | GcpProvider | GithubProvider | IacProvider | ImageProvider | KubernetesProvider | M365Provider | MongodbatlasProvider | OpenstackProvider | OraclecloudProvider: An instance of the corresponding provider class
AlibabacloudProvider | AwsProvider | AzureProvider | CloudflareProvider | GcpProvider | GithubProvider | GoogleworkspaceProvider | IacProvider | ImageProvider | KubernetesProvider | M365Provider | MongodbatlasProvider | OpenstackProvider | OraclecloudProvider: An instance of the corresponding provider class
initialized with the provider's secrets.
"""
prowler_provider = return_prowler_provider(provider)
@@ -191,6 +191,22 @@ from rest_framework_json_api import serializers
},
"required": ["service_account_key"],
},
{
"type": "object",
"title": "Google Workspace Service Account",
"properties": {
"credentials_content": {
"type": "string",
"description": "The service account JSON credentials content for Google Workspace API access with domain-wide delegation enabled.",
},
"delegated_user": {
"type": "string",
"format": "email",
"description": "The email address of the Google Workspace super admin user to impersonate for domain-wide delegation.",
},
},
"required": ["credentials_content", "delegated_user"],
},
{
"type": "object",
"title": "Kubernetes Static Credentials",
+32 -1
View File
@@ -6,6 +6,7 @@ from django.conf import settings
from django.contrib.auth import authenticate
from django.contrib.auth.models import update_last_login
from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError as DjangoValidationError
from django.db import IntegrityError
from drf_spectacular.utils import extend_schema_field
from jwt.exceptions import InvalidKeyError
@@ -959,6 +960,26 @@ class ProviderCreateSerializer(RLSSerializer, BaseWriteSerializer):
},
}
def create(self, validated_data):
try:
return super().create(validated_data)
except DjangoValidationError as e:
if "unique_provider_uids" in str(e):
raise ConflictException(
detail="Provider already exists.",
pointer="/data/attributes/uid",
)
raise
except IntegrityError as e:
# Handle race conditions where the unique constraint is enforced at the DB level
# after validation has already passed.
if "unique_provider_uids" in str(e):
raise ConflictException(
detail="Provider already exists.",
pointer="/data/attributes/uid",
)
raise
class ProviderUpdateSerializer(BaseWriteSerializer):
"""
@@ -1220,7 +1241,7 @@ class AttackPathsQueryRunRequestSerializer(BaseSerializerV1):
class AttackPathsCustomQueryRunRequestSerializer(BaseSerializerV1):
query = serializers.CharField(max_length=10000, min_length=1, trim_whitespace=True)
query = serializers.CharField()
class JSONAPIMeta:
resource_name = "attack-paths-custom-query-run-requests"
@@ -1520,6 +1541,8 @@ class BaseWriteProviderSecretSerializer(BaseWriteSerializer):
serializer = AzureProviderSecret(data=secret)
elif provider_type == Provider.ProviderChoices.GCP.value:
serializer = GCPProviderSecret(data=secret)
elif provider_type == Provider.ProviderChoices.GOOGLEWORKSPACE.value:
serializer = GoogleWorkspaceProviderSecret(data=secret)
elif provider_type == Provider.ProviderChoices.GITHUB.value:
serializer = GithubProviderSecret(data=secret)
elif provider_type == Provider.ProviderChoices.IAC.value:
@@ -1655,6 +1678,14 @@ class GCPServiceAccountProviderSecret(serializers.Serializer):
resource_name = "provider-secrets"
class GoogleWorkspaceProviderSecret(serializers.Serializer):
credentials_content = serializers.CharField()
delegated_user = serializers.EmailField()
class Meta:
resource_name = "provider-secrets"
class MongoDBAtlasProviderSecret(serializers.Serializer):
atlas_public_key = serializers.CharField()
atlas_private_key = serializers.CharField()
+11 -7
View File
@@ -51,13 +51,6 @@ from api.v1.views import (
)
# This helper view is used to block any endpoints that should not be available
# To use it, add a new entry in the `urlpatterns` list, for example (old but real one):
# path(
# "attack-paths-scans/<uuid:pk>/queries/custom",
# _blocked_endpoint,
# name="attack-paths-scans-queries-custom-blocked",
# ),
@csrf_exempt
def _blocked_endpoint(request, *args, **kwargs):
return JsonResponse(
@@ -216,6 +209,17 @@ urlpatterns = [
path("tokens/saml", SAMLTokenValidateView.as_view(), name="token-saml"),
path("tokens/google", GoogleSocialLoginView.as_view(), name="token-google"),
path("tokens/github", GithubSocialLoginView.as_view(), name="token-github"),
# TODO: Remove these blocked endpoints once they are properly tested
path(
"attack-paths-scans/<uuid:pk>/queries/custom",
_blocked_endpoint,
name="attack-paths-scans-queries-custom-blocked",
),
path(
"attack-paths-scans/<uuid:pk>/schema",
_blocked_endpoint,
name="attack-paths-scans-schema-blocked",
),
path("", include(router.urls)),
path("", include(tenants_router.urls)),
path("", include(users_router.urls)),
+1 -6
View File
@@ -408,7 +408,7 @@ class SchemaView(SpectacularAPIView):
def get(self, request, *args, **kwargs):
spectacular_settings.TITLE = "Prowler API"
spectacular_settings.VERSION = "1.20.1"
spectacular_settings.VERSION = "1.21.0"
spectacular_settings.DESCRIPTION = (
"Prowler API specification.\n\nThis file is auto-generated."
)
@@ -2452,11 +2452,6 @@ class AttackPathsScanViewSet(BaseRLSViewSet):
# RBAC required permissions
required_permissions = [Permissions.MANAGE_SCANS]
def get_throttles(self):
if self.action == "run_custom_attack_paths_query":
self.throttle_scope = "attack-paths-custom-query"
return super().get_throttles()
def set_required_permissions(self):
if self.request.method in SAFE_METHODS:
self.required_permissions = []
+1 -4
View File
@@ -113,11 +113,8 @@ REST_FRAMEWORK = {
"rest_framework.throttling.ScopedRateThrottle",
],
"DEFAULT_THROTTLE_RATES": {
"dj_rest_auth": None,
"token-obtain": env("DJANGO_THROTTLE_TOKEN_OBTAIN", default=None),
"attack-paths-custom-query": env(
"DJANGO_THROTTLE_ATTACK_PATHS_CUSTOM_QUERY", default="10/min"
),
"dj_rest_auth": None,
},
}
+7
View File
@@ -543,6 +543,12 @@ def providers_fixture(tenants_fixture):
alias="openstack_testing",
tenant_id=tenant.id,
)
provider12 = Provider.objects.create(
provider="googleworkspace",
uid="C12345678",
alias="googleworkspace_testing",
tenant_id=tenant.id,
)
return (
provider1,
@@ -556,6 +562,7 @@ def providers_fixture(tenants_fixture):
provider9,
provider10,
provider11,
provider12,
)
@@ -1,3 +1,58 @@
"""
Attack Paths scan orchestrator.
Runs the full scan lifecycle for a single provider, called from a Celery task.
The idea is simple: ingest everything into a throwaway Neo4j database, enrich
it with Prowler-specific data, then swap it into the tenant's long-lived
database so queries never see a half-built graph.
Two databases are involved:
- Temporary (db-tmp-scan-<attack_paths_scan_id>): short-lived, single-provider, dropped after sync.
- Tenant (db-tenant-<tenant_uuid>): long-lived, multi-provider, what the API queries against.
Pipeline steps:
1. Resolve the Prowler provider and SDK credentials from the scan ID.
Retrieve or create the AttackPathsScan row. Exit early if the provider
type has no ingestion function (only AWS is supported today).
2. Create a fresh temporary Neo4j database and set up Cartography indexes
plus ProwlerFinding indexes before writing any data.
3. Run the provider-specific Cartography ingestion (e.g. aws.start_aws_ingestion).
This iterates over cloud services and writes the standard Cartography nodes
(AWSAccount, EC2Instance, IAMRole, etc.) and relationships (RESOURCE,
POLICY, STATEMENT, TRUSTS_AWS_PRINCIPAL, ...) into the temp database.
Wrapped in call_within_event_loop because some Cartography modules use async.
4. Run Cartography post-processing: ontology for label propagation and
analysis for derived relationships.
5. Create an Internet singleton node and add CAN_ACCESS relationships to
internet-exposed resources (EC2Instance, LoadBalancer, LoadBalancerV2).
6. Stream Prowler findings from Postgres in batches. Each finding becomes a
ProwlerFinding node linked to its cloud-resource node via HAS_FINDING.
Before that, an _AWSResource label (provider-specific) is added to all
nodes connected to the AWSAccount so finding lookups can use an index.
Stale findings from previous scans are cleaned up.
7. Sync the temp database into the tenant database:
- Drop the old provider subgraph (matched by _provider_id property).
graph_data_ready is set to False for all scans of this provider while
the swap happens so the API doesn't serve partial data.
- Copy nodes and relationships in batches. Every synced node gets a
_ProviderResource label and _provider_id / _provider_element_id
properties for multi-provider isolation.
- Set graph_data_ready back to True.
8. Drop the temporary database, mark the AttackPathsScan as COMPLETED.
On failure the temp database is dropped, the scan is marked FAILED, and the
exception propagates to Celery.
"""
import logging
import time
+11 -6
View File
@@ -336,7 +336,6 @@ class ENSReportGenerator(BaseComplianceReportGenerator):
for req in data.requirements:
if req.status == StatusChoices.MANUAL:
continue
m = get_requirement_metadata(req.id, data.attributes_by_requirement_id)
if m:
marco = getattr(m, "Marco", "Otros")
@@ -365,9 +364,12 @@ class ENSReportGenerator(BaseComplianceReportGenerator):
elements.append(Paragraph(f"{categoria_name}", self.styles["h3"]))
for req in reqs:
status_indicator = (
"" if req["status"] == StatusChoices.PASS else ""
)
if req["status"] == StatusChoices.PASS:
status_indicator = ""
elif req["status"] == StatusChoices.MANUAL:
status_indicator = ""
else:
status_indicator = ""
nivel_badge = f"[{req['nivel'].upper()}]" if req["nivel"] else ""
elements.append(
Paragraph(
@@ -841,11 +843,14 @@ class ENSReportGenerator(BaseComplianceReportGenerator):
elements.append(Spacer(1, 0.15 * inch))
# Status and Nivel badges row
status_color = COLOR_HIGH_RISK # FAIL
status_text = str(req.status).upper()
status_color = (
COLOR_HIGH_RISK if req.status == StatusChoices.FAIL else COLOR_GRAY
)
nivel_color = nivel_colors.get(nivel, COLOR_GRAY)
badges_row1 = [
["State:", "FAIL", "", f"Nivel: {nivel.upper()}"],
["State:", status_text, "", f"Nivel: {nivel.upper()}"],
]
badges_table1 = Table(
badges_row1,
@@ -35,19 +35,27 @@ def _aggregate_requirement_statistics_from_database(
}
"""
requirement_statistics_by_check_id = {}
# TODO: take into account that now the relation is 1 finding == 1 resource, review this when the logic changes
with rls_transaction(tenant_id, using=READ_REPLICA_ALIAS):
aggregated_statistics_queryset = (
Finding.all_objects.filter(
tenant_id=tenant_id, scan_id=scan_id, muted=False
tenant_id=tenant_id,
scan_id=scan_id,
muted=False,
resources__provider__is_deleted=False,
)
.values("check_id")
.annotate(
total_findings=Count(
"id",
distinct=True,
filter=Q(status__in=[StatusChoices.PASS, StatusChoices.FAIL]),
),
passed_findings=Count("id", filter=Q(status=StatusChoices.PASS)),
passed_findings=Count(
"id",
distinct=True,
filter=Q(status=StatusChoices.PASS),
),
)
)
+116 -75
View File
@@ -29,7 +29,7 @@ from tasks.jobs.threatscore_utils import (
_load_findings_for_requirement_checks,
)
from api.models import Finding, StatusChoices
from api.models import Finding, Resource, ResourceFindingMapping, StatusChoices
from prowler.lib.check.models import Severity
matplotlib.use("Agg") # Use non-interactive backend for tests
@@ -39,43 +39,50 @@ matplotlib.use("Agg") # Use non-interactive backend for tests
class TestAggregateRequirementStatistics:
"""Test suite for _aggregate_requirement_statistics_from_database function."""
def _create_finding_with_resource(
self, tenant, scan, uid, check_id, status, severity=Severity.high
):
"""Helper to create a finding linked to a resource (matching scan processing behavior)."""
finding = Finding.objects.create(
tenant_id=tenant.id,
scan=scan,
uid=uid,
check_id=check_id,
status=status,
severity=severity,
impact=severity,
check_metadata={},
raw_result={},
)
resource = Resource.objects.create(
tenant_id=tenant.id,
provider=scan.provider,
uid=f"resource-{uid}",
name=f"resource-{uid}",
region="us-east-1",
service="test",
type="test::resource",
)
ResourceFindingMapping.objects.create(
tenant_id=tenant.id,
finding=finding,
resource=resource,
)
return finding
def test_aggregates_findings_correctly(self, tenants_fixture, scans_fixture):
"""Verify correct pass/total counts per check are aggregated from database."""
tenant = tenants_fixture[0]
scan = scans_fixture[0]
Finding.objects.create(
tenant_id=tenant.id,
scan=scan,
uid="finding-1",
check_id="check_1",
status=StatusChoices.PASS,
severity=Severity.high,
impact=Severity.high,
check_metadata={},
raw_result={},
self._create_finding_with_resource(
tenant, scan, "finding-1", "check_1", StatusChoices.PASS
)
Finding.objects.create(
tenant_id=tenant.id,
scan=scan,
uid="finding-2",
check_id="check_1",
status=StatusChoices.FAIL,
severity=Severity.high,
impact=Severity.high,
check_metadata={},
raw_result={},
self._create_finding_with_resource(
tenant, scan, "finding-2", "check_1", StatusChoices.FAIL
)
Finding.objects.create(
tenant_id=tenant.id,
scan=scan,
uid="finding-3",
check_id="check_2",
status=StatusChoices.PASS,
severity=Severity.medium,
impact=Severity.medium,
check_metadata={},
raw_result={},
self._create_finding_with_resource(
tenant, scan, "finding-3", "check_2", StatusChoices.PASS, Severity.medium
)
result = _aggregate_requirement_statistics_from_database(
@@ -106,27 +113,11 @@ class TestAggregateRequirementStatistics:
tenant = tenants_fixture[0]
scan = scans_fixture[0]
Finding.objects.create(
tenant_id=tenant.id,
scan=scan,
uid="finding-1",
check_id="check_1",
status=StatusChoices.FAIL,
severity=Severity.high,
impact=Severity.high,
check_metadata={},
raw_result={},
self._create_finding_with_resource(
tenant, scan, "finding-1", "check_1", StatusChoices.FAIL
)
Finding.objects.create(
tenant_id=tenant.id,
scan=scan,
uid="finding-2",
check_id="check_1",
status=StatusChoices.FAIL,
severity=Severity.high,
impact=Severity.high,
check_metadata={},
raw_result={},
self._create_finding_with_resource(
tenant, scan, "finding-2", "check_1", StatusChoices.FAIL
)
result = _aggregate_requirement_statistics_from_database(
@@ -142,16 +133,12 @@ class TestAggregateRequirementStatistics:
scan = scans_fixture[0]
for i in range(5):
Finding.objects.create(
tenant_id=tenant.id,
scan=scan,
uid=f"finding-{i}",
check_id="check_1",
status=StatusChoices.PASS if i % 2 == 0 else StatusChoices.FAIL,
severity=Severity.high,
impact=Severity.high,
check_metadata={},
raw_result={},
self._create_finding_with_resource(
tenant,
scan,
f"finding-{i}",
"check_1",
StatusChoices.PASS if i % 2 == 0 else StatusChoices.FAIL,
)
result = _aggregate_requirement_statistics_from_database(
@@ -162,27 +149,43 @@ class TestAggregateRequirementStatistics:
assert result["check_1"]["total"] == 5
def test_mixed_statuses(self, tenants_fixture, scans_fixture):
"""Verify MANUAL status is counted in total but not passed."""
"""Verify MANUAL status is not counted in total or passed."""
tenant = tenants_fixture[0]
scan = scans_fixture[0]
Finding.objects.create(
tenant_id=tenant.id,
scan=scan,
uid="finding-1",
check_id="check_1",
status=StatusChoices.PASS,
severity=Severity.high,
impact=Severity.high,
check_metadata={},
raw_result={},
self._create_finding_with_resource(
tenant, scan, "finding-1", "check_1", StatusChoices.PASS
)
self._create_finding_with_resource(
tenant, scan, "finding-2", "check_1", StatusChoices.MANUAL
)
result = _aggregate_requirement_statistics_from_database(
str(tenant.id), str(scan.id)
)
# MANUAL findings are excluded from the aggregation query
# since it only counts PASS and FAIL statuses
assert result["check_1"]["passed"] == 1
assert result["check_1"]["total"] == 1
def test_excludes_findings_without_resources(self, tenants_fixture, scans_fixture):
"""Verify findings without resources are excluded from aggregation."""
tenant = tenants_fixture[0]
scan = scans_fixture[0]
# Finding WITH resource → should be counted
self._create_finding_with_resource(
tenant, scan, "finding-1", "check_1", StatusChoices.PASS
)
# Finding WITHOUT resource → should be EXCLUDED
Finding.objects.create(
tenant_id=tenant.id,
scan=scan,
uid="finding-2",
check_id="check_1",
status=StatusChoices.MANUAL,
status=StatusChoices.FAIL,
severity=Severity.high,
impact=Severity.high,
check_metadata={},
@@ -193,8 +196,46 @@ class TestAggregateRequirementStatistics:
str(tenant.id), str(scan.id)
)
# MANUAL findings are excluded from the aggregation query
# since it only counts PASS and FAIL statuses
assert result["check_1"]["passed"] == 1
assert result["check_1"]["total"] == 1
def test_multiple_resources_no_double_count(self, tenants_fixture, scans_fixture):
"""Verify a finding with multiple resources is only counted once."""
tenant = tenants_fixture[0]
scan = scans_fixture[0]
finding = Finding.objects.create(
tenant_id=tenant.id,
scan=scan,
uid="finding-1",
check_id="check_1",
status=StatusChoices.PASS,
severity=Severity.high,
impact=Severity.high,
check_metadata={},
raw_result={},
)
# Link two resources to the same finding
for i in range(2):
resource = Resource.objects.create(
tenant_id=tenant.id,
provider=scan.provider,
uid=f"resource-{i}",
name=f"resource-{i}",
region="us-east-1",
service="test",
type="test::resource",
)
ResourceFindingMapping.objects.create(
tenant_id=tenant.id,
finding=finding,
resource=resource,
)
result = _aggregate_requirement_statistics_from_database(
str(tenant.id), str(scan.id)
)
assert result["check_1"]["passed"] == 1
assert result["check_1"]["total"] == 1
+1
View File
@@ -0,0 +1 @@
charts/
+2
View File
@@ -13,6 +13,8 @@ keywords:
- gcp
- kubernetes
maintainers:
- name: Dani
email: andre.gomes@promptlyhealth.com
- name: Mihai
email: mihai.legat@gmail.com
dependencies:
+3 -3
View File
@@ -558,9 +558,9 @@ neo4j:
# Neo4j Configuration (yaml format)
config:
dbms_security_procedures_allowlist: "apoc.*"
dbms_security_procedures_unrestricted: ""
dbms_security_procedures_unrestricted: "apoc.*"
apoc_config:
apoc.export.file.enabled: "false"
apoc.import.file.enabled: "false"
apoc.export.file.enabled: "true"
apoc.import.file.enabled: "true"
apoc.import.file.use_neo4j_config: "true"
+1 -1
View File
@@ -21,7 +21,7 @@ print(
f"{Fore.GREEN}Loading all CSV files from the folder {folder_path_overview} ...\n{Style.RESET_ALL}"
)
cli.show_server_banner = lambda *x: click.echo(
f"{Fore.YELLOW}NOTE:{Style.RESET_ALL} If you are using {Fore.GREEN}{Style.BRIGHT}Prowler SaaS{Style.RESET_ALL} with the S3 integration or that integration \nfrom {Fore.CYAN}{Style.BRIGHT}Prowler Open Source{Style.RESET_ALL} and you want to use your data from your S3 bucket,\nrun: `{orange_color}aws s3 cp s3://<your-bucket>/output/csv ./output --recursive{Style.RESET_ALL}`\nand then run `prowler dashboard` again to load the new files."
f"{Fore.YELLOW}NOTE:{Style.RESET_ALL} If you are using {Fore.GREEN}{Style.BRIGHT}Prowler Cloud{Style.RESET_ALL} with the S3 integration or that integration \nfrom {Fore.CYAN}{Style.BRIGHT}Prowler CLI{Style.RESET_ALL} and you want to use your data from your S3 bucket,\nrun: `{orange_color}aws s3 cp s3://<your-bucket>/output/csv ./output --recursive{Style.RESET_ALL}`\nand then run `prowler dashboard` again to load the new files."
)
# Initialize the app - incorporate css
+1
View File
@@ -315,6 +315,7 @@ The type of resource being audited. This field helps categorize and organize fin
- **Kubernetes**: Use types shown under `KIND` from `kubectl api-resources`.
- **Oracle Cloud Infrastructure**: Use types from [Oracle Cloud Infrastructure documentation](https://docs.public.oneportal.content.oci.oraclecloud.com/en-us/iaas/Content/Search/Tasks/queryingresources_topic-Listing_Supported_Resource_Types.htm).
- **OpenStack**: Use types from [OpenStack Heat resource types](https://docs.openstack.org/heat/latest/template_guide/openstack.html).
- **Alibaba Cloud**: Use types from [Alibaba Cloud ROS resource types](https://www.alibabacloud.com/help/en/ros/developer-reference/list-of-resource-types-by-service).
- **Any other provider**: Use `NotDefined` due to lack of standardized resource types in their SDK or documentation.
#### ResourceGroup
+34
View File
@@ -3406,6 +3406,40 @@ Use existing providers as templates, this will help you to understand better the
- **Use Rules**: Use rules to ensure the code generated by AI is following the way of working in Prowler.
---
## OCSF Field Requirements for Prowler Cloud Integration
When implementing a new provider that supports the `--push-to-cloud` feature, specific OCSF fields must be correctly populated to ensure proper findings ingestion into Prowler Cloud.
### Required OCSF Fields
The following fields in the OCSF output are critical for successful ingestion:
| Field | Requirement | Description |
|-------|-------------|-------------|
| `provider_uid` | Must match the UID used when registering the provider in the API | This identifier links findings to the correct provider in Prowler Cloud |
| `provider` | Must be the provider name | The name of the provider (e.g., `aws`, `azure`, `gcp`, `googleworkspace`) |
| `finding_info.uid` | Must be unique | Each finding must have a unique identifier to avoid duplicates |
| `resources.uid` | Must have a value | The resource UID cannot be empty; it identifies the specific resource being assessed |
### Implementation Reference
These fields are set in the OCSF output generation. See the [OCSF output implementation](https://github.com/prowler-cloud/prowler/blob/master/prowler/lib/outputs/ocsf/ocsf.py) for reference.
### Validation Checklist
Before releasing a new provider with `--push-to-cloud` support:
- [ ] Verify `provider_uid` matches the UID used in the API to register the provider
- [ ] Confirm `provider` field contains the correct provider name
- [ ] Ensure all `finding_info.uid` values are unique across findings
- [ ] Validate that `resources.uid` is populated for every finding
<Tip>
Use `python scripts/validate_ocsf_output.py output/*.ocsf.json` to automate these checks.
</Tip>
## Checklist for New Providers
### CLI Integration Only
@@ -0,0 +1,315 @@
---
title: 'Test Impact Analysis'
---
Test impact analysis (TIA) determines which tests to run based on the files changed in a pull request. Instead of running the full test suite on every pull request, TIA maps changed files to the specific Prowler SDK, API, and end-to-end (E2E) tests that cover them. This approach reduces continuous integration (CI) time and resource usage while maintaining confidence that relevant code paths are tested.
## Architecture
### Components
| Component | Path | Role |
|-----------|------|------|
| Configuration | `.github/test-impact.yml` | Defines ignored, critical, and module path mappings |
| Analysis engine | `.github/scripts/test-impact.py` | Python script that evaluates changed files against the configuration |
| Reusable workflow | `.github/workflows/test-impact-analysis.yml` | GitHub Actions reusable workflow that orchestrates the analysis |
| E2E consumer | `.github/workflows/ui-e2e-tests-v2.yml` | Consumes TIA outputs to run targeted Playwright tests |
### Flow Diagram
```
PR opened/updated
|
v
+-------------------------------+
| tj-actions/changed-files | Gets list of changed files from PR
+-------------------------------+
|
v
+-------------------------------+
| test-impact.py |
| |
| 1. Filter ignored paths | docs/**, *.md, .gitignore, etc.
| 2. Check critical paths | prowler/lib/**, ui/lib/**, .github/workflows/**
| 3. Match modules | Map remaining files to module definitions
| 4. Categorize tests | Split into sdk-tests, api-tests, ui-e2e
+-------------------------------+
|
v
+-------------------------------+
| GitHub Actions Outputs |
| |
| run-all: true/false |
| sdk-tests: "tests/providers/aws/**"
| api-tests: "api/src/backend/api/tests/**"
| ui-e2e: "ui/tests/providers/**"
| modules: "sdk-aws,ui-providers"
| has-tests: true/false |
| has-sdk-tests: true/false |
| has-api-tests: true/false |
| has-ui-e2e: true/false |
+-------------------------------+
|
v
+-------------------------------+
| Consumer Workflows |
| |
| ui-e2e-tests-v2.yml: |
| - Path resolution pipeline |
| - Playwright execution |
+-------------------------------+
```
## Configuration Reference
The configuration lives in `.github/test-impact.yml` and contains three sections.
### `ignored` — Paths That Never Trigger Tests
Files matching these patterns are filtered out before any analysis takes place. This section is intended for non-code files.
```yaml
ignored:
paths:
- docs/**
- "*.md"
- .gitignore
- skills/**
- ui/tests/setups/** # E2E auth setup helpers (not runnable tests)
```
### `critical` — Paths That Trigger All Tests
If any changed file matches a critical path, the system short-circuits and outputs `run-all: true`. All downstream consumers then run their complete test suites.
```yaml
critical:
paths:
- prowler/lib/** # SDK core
- ui/lib/** # UI shared utilities
- ui/playwright.config.ts # Test infrastructure
- .github/workflows/** # CI changes
- .github/test-impact.yml # This config itself
```
### `modules` — Path-to-Test Mappings
Each module maps source file patterns to the tests that cover them.
```yaml
- name: ui-providers # Unique identifier
match: # Source file glob patterns
- ui/components/providers/**
- ui/actions/providers/**
- ui/app/**/providers/**
- ui/tests/providers/** # Test file changes also trigger themselves
tests: [] # SDK/API unit test patterns (empty for UI modules)
e2e: # Playwright E2E test patterns
- ui/tests/providers/**
```
#### Module Schema
| Field | Type | Description |
|-------|------|-------------|
| `name` | `string` | Unique module identifier (for example, `sdk-aws`, `ui-providers`, `api-views`) |
| `match` | `list[glob]` | Source file patterns that trigger this module |
| `tests` | `list[glob]` | Prowler SDK (`tests/`) or API (`api/`) unit test patterns to run |
| `e2e` | `list[glob]` | UI E2E test patterns (`ui/tests/`) to run |
#### Module Categories
- **`sdk-*`:** Provider and lib modules. These only produce `tests` output, not `e2e`.
- **`api-*`:** API views, serializers, filters, and role-based access control (RBAC). These produce `tests` and sometimes `e2e` (API changes can affect UI flows).
- **`ui-*`:** UI feature modules. These only produce `e2e` output, not `tests`.
## Path Resolution Pipeline
The E2E consumer workflow (`.github/workflows/ui-e2e-tests-v2.yml`, lines 202253) transforms the `ui-e2e` output from glob patterns into paths that Playwright can execute. This transformation follows a multi-step shell pipeline.
### Step 1: Check Run Mode
```bash
if [[ "${RUN_ALL_TESTS}" == "true" ]]; then
pnpm run test:e2e # Run everything, skip pipeline
fi
```
### Step 2: Strip the `ui/` Prefix and `**` Suffix
```bash
# "ui/tests/providers/**" -> "tests/providers/"
TEST_PATHS=$(echo "$E2E_TEST_PATHS" | sed 's|ui/||g' | sed 's|\*\*||g')
```
### Step 3: Filter Out Setup Paths
```bash
# Remove auth setup helpers (not runnable test suites)
TEST_PATHS=$(echo "$TEST_PATHS" | grep -v '^tests/setups/')
```
### Step 4: Safety Net for Bare `tests/`
If the pattern `ui/tests/**` was present in the output (from a critical path or a broad module like `ui-shadcn`), it resolves to bare `tests/` after stripping. This would cause Playwright to discover setup files in `tests/setups/`, so it gets expanded instead:
```bash
if echo "$TEST_PATHS" | grep -qx 'tests/'; then
# Expand to specific subdirs, excluding tests/setups/
for dir in tests/*/; do
[[ "$dir" == "tests/setups/" ]] && continue
SPECIFIC_DIRS="${SPECIFIC_DIRS}${dir}"
done
fi
```
### Step 5: Empty Directory Check
Directories that do not contain any `.spec.ts` or `.test.ts` files are skipped. This handles forward-looking patterns where a module is configured but tests have not been written yet.
```bash
if find "$p" -name '*.spec.ts' -o -name '*.test.ts' | head -1 | grep -q .; then
VALID_PATHS="${VALID_PATHS}${p}"
else
echo "Skipping empty test directory: $p"
fi
```
### Step 6: Execute Playwright
```bash
pnpm exec playwright test $TEST_PATHS
# For example: pnpm exec playwright test tests/providers/ tests/scans/
```
## Playwright Project Mapping
Playwright discovers tests by scanning the directories passed to it. The `playwright.config.ts` file defines projects with `testMatch` patterns that control which spec files each project claims:
```
tests/providers/providers.spec.ts -> "providers" project -> depends on admin.auth.setup
tests/scans/scans.spec.ts -> "scans" project -> depends on admin.auth.setup
tests/sign-in-base/*.spec.ts -> "sign-in-base" -> no auth dependency
tests/auth/*.spec.ts -> "auth" -> no auth dependency
tests/sign-up/sign-up.spec.ts -> "sign-up" -> no auth dependency
tests/invitations/invitations.spec.ts -> "invitations" -> depends on admin.auth.setup
```
Auth setup projects (`admin.auth.setup`, `manage-scans.auth.setup`, and others) create authenticated browser state files. Projects that declare them as `dependencies` wait for the setup to complete before running.
When TIA runs only `tests/providers/`, Playwright still automatically runs `admin.auth.setup` because the `providers` project declares it as a dependency.
## Edge Cases and Known Considerations
### Forward-Looking Patterns (Empty Test Directories)
A module can reference `ui/tests/attack-paths/**` before any tests exist there. The empty directory check (step 5) gracefully skips it instead of failing.
### Broad Patterns and the Safety Net
Modules like `ui-shadcn` and `api-views` list every E2E test suite explicitly to avoid using `ui/tests/**`. If a broad pattern does produce bare `tests/`, the safety net expands it to specific subdirectories, excluding `tests/setups/`.
### Setup Files and Auth Dependencies
`ui/tests/setups/**` is listed in the `ignored` section and also filtered in the path resolution pipeline. This double protection ensures setup files are never passed as test targets to Playwright. Auth setups run only when declared as project dependencies.
### Critical Path Triggering Run-All
Changes to `.github/workflows/**` or `.github/test-impact.yml` trigger `run-all: true`. This means editing any workflow file (even unrelated ones) runs the full test suite. This behavior is intentional — CI infrastructure changes should be validated broadly.
### Unmatched Files
Files that do not match any ignored, critical, or module pattern produce no test output. The `has-tests` flag is set to `false` and consumer workflows skip entirely via the `skip-e2e` job.
## Adding New Test Modules
To add tests for a new UI feature (for example, `dashboards`):
1. **Add the module to `.github/test-impact.yml`:**
```yaml
- name: ui-dashboards
match:
- ui/components/dashboards/**
- ui/actions/dashboards/**
- ui/app/**/dashboards/**
- ui/tests/dashboards/**
tests: []
e2e:
- ui/tests/dashboards/**
```
2. **Create the test directory and spec file:**
```
ui/tests/dashboards/dashboards.spec.ts
```
3. **Add a Playwright project in `ui/playwright.config.ts`:**
```typescript
{
name: "dashboards",
testMatch: "dashboards.spec.ts",
dependencies: ["admin.auth.setup"], // if tests need auth
},
```
4. **Register E2E paths in shared UI modules (if applicable):**
If the feature uses shared UI components, add the E2E path to the `ui-shadcn` module so that changes to shared components also trigger dashboard tests:
```yaml
- name: ui-shadcn
match:
- ui/components/shadcn/**
- ui/components/ui/**
e2e:
- ui/tests/dashboards/** # Add here
# ... existing paths
```
5. **Register E2E paths in API modules (if applicable):**
If API changes affect this feature, add the E2E path to the relevant `api-*` module (for example, `api-views`).
## Troubleshooting
### Tests Not Running When Expected
1. Check whether the changed file matches an `ignored` pattern. The script logs `[IGNORED]` to stderr.
2. Verify the file matches a module's `match` pattern. To test locally, run:
```bash
python .github/scripts/test-impact.py path/to/changed/file.ts
```
3. Confirm the module has non-empty `e2e` (for E2E) or `tests` (for unit tests).
4. Check the `has-ui-e2e` output — the consumer workflow gates on this flag.
### Unexpected Auth Setup Errors
Auth setup projects run automatically when a test project declares them as `dependencies`. If auth failures occur:
- **Verify secrets:** Confirm that the `E2E_ADMIN_USER` and `E2E_ADMIN_PASSWORD` secrets are set.
- **Check setup file existence:** Ensure the auth setup file exists in `ui/tests/setups/`.
- **Validate test match patterns:** Ensure the `testMatch` pattern in `playwright.config.ts` correctly matches the setup file.
### "No Tests Found" Errors
This typically means the path resolution pipeline produced valid directories but Playwright could not match any spec files to a project:
- **Check project configuration:** Verify that `playwright.config.ts` has a project with a `testMatch` pattern for the spec files in that directory.
- **Verify file naming:** Confirm the spec file naming matches the expected pattern (for example, `feature.spec.ts`).
### "No Runnable E2E Test Paths After Filtering Setups"
All resolved paths were under `tests/setups/`. This indicates the module's `e2e` patterns only point to setup files, which is a configuration error. The module should be updated to point to actual test directories.
### Debugging Locally
```bash
# See what the analysis engine produces for specific files
python .github/scripts/test-impact.py ui/components/providers/some-file.tsx
# Output goes to stderr (analysis log) and GITHUB_OUTPUT (structured output)
# Without the GITHUB_OUTPUT env var, results print to stderr only
```
@@ -10,7 +10,7 @@ Complete reference guide for all tools available in the Prowler MCP Server. Tool
|----------|------------|------------------------|
| Prowler Hub | 10 tools | No |
| Prowler Documentation | 2 tools | No |
| Prowler Cloud/App | 24 tools | Yes |
| Prowler Cloud/App | 27 tools | Yes |
## Tool Naming Convention
@@ -80,6 +80,14 @@ Tools for managing finding muting, including pattern-based bulk muting (mutelist
- **`prowler_app_update_mute_rule`** - Update a mute rule's name, reason, or enabled status
- **`prowler_app_delete_mute_rule`** - Delete a mute rule from the system
### Attack Paths Analysis
Tools for analyzing privilege escalation chains and security misconfigurations using graph-based analysis. Attack Paths maps relationships between cloud resources, permissions, and security findings to detect how privileges can be escalated and how misconfigurations can be exploited.
- **`prowler_app_list_attack_paths_scans`** - List Attack Paths scans with filtering by provider, provider type, and scan state (available, scheduled, executing, completed, failed, cancelled)
- **`prowler_app_list_attack_paths_queries`** - Discover available Attack Paths queries for a completed scan, including query names, descriptions, and required parameters
- **`prowler_app_run_attack_paths_query`** - Execute an Attack Paths query against a completed scan and retrieve graph results with nodes (cloud resources, findings, virtual nodes) and relationships (access paths, role assumptions, security group memberships)
### Compliance Management
Tools for viewing compliance status and framework details across all cloud providers.
@@ -24,6 +24,7 @@ Full access to Prowler Cloud platform and self-managed Prowler App for:
- **Scan Orchestration**: Trigger on-demand scans and schedule recurring security assessments
- **Resource Inventory**: Search and view detailed information about your audited resources
- **Muting Management**: Create and manage muting lists/rules to suppress non-relevant findings
- **Attack Paths Analysis**: Analyze privilege escalation chains and security misconfigurations through graph-based analysis of cloud resource relationships
### 2. Prowler Hub
@@ -61,6 +62,7 @@ The Prowler MCP Server enables powerful workflows through AI assistants:
- "Show me all critical findings from my AWS production accounts"
- "Register my new AWS account in Prowler and run a scheduled scan every day"
- "List all muted findings and detect what findgings are muted by a not enough good reason in relation to their severity"
- "Run an attack paths query to find EC2 instances exposed to the Internet with access to sensitive S3 buckets"
**Security Research**
- "Explain what the S3 bucket public access Prowler check does"
@@ -14,8 +14,8 @@
<text x="140" y="107" text-anchor="middle" font-size="16" font-weight="700" fill="#fff">1</text>
<text x="140" y="145" text-anchor="middle" font-size="15" font-weight="700" fill="#1a1a2e">Create Management</text>
<text x="140" y="165" text-anchor="middle" font-size="15" font-weight="700" fill="#1a1a2e">Account Role</text>
<rect x="80" y="185" width="120" height="24" rx="12" fill="#E8F0FE"/>
<text x="140" y="201" text-anchor="middle" font-size="11" font-weight="600" fill="#4285F4">Manually in IAM</text>
<rect x="60" y="185" width="160" height="24" rx="12" fill="#E8F0FE"/>
<text x="140" y="201" text-anchor="middle" font-size="11" font-weight="600" fill="#4285F4">Quick Create or Manual</text>
<text x="140" y="232" text-anchor="middle" font-size="12" fill="#5f6368">Allows Prowler to</text>
<text x="140" y="248" text-anchor="middle" font-size="12" fill="#5f6368">discover your org</text>
<text x="140" y="264" text-anchor="middle" font-size="12" fill="#5f6368">structure</text>

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

@@ -46,8 +46,8 @@
<text x="82" y="384" font-size="11" fill="#4285F4" font-weight="600">organizations:ListTagsForResource</text>
<!-- Deploy badge -->
<rect x="145" y="400" width="210" height="28" rx="14" fill="#FFF3E0" stroke="#F9AB00" stroke-width="1.5"/>
<text x="250" y="419" text-anchor="middle" font-size="12" font-weight="700" fill="#E65100">Deploy: MANUALLY in IAM Console</text>
<rect x="115" y="400" width="270" height="28" rx="14" fill="#FFF3E0" stroke="#F9AB00" stroke-width="1.5"/>
<text x="250" y="419" text-anchor="middle" font-size="12" font-weight="700" fill="#E65100">Deploy: Quick Create link or Manual</text>
<!-- ===== Prowler Cloud connector ===== -->
<rect x="490" y="195" width="120" height="36" rx="8" fill="#F5F5F5" stroke="#E0E0E0" stroke-width="1"/>

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 760 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 747 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 KiB

+4 -3
View File
@@ -33,12 +33,13 @@ The supported providers right now are:
| [Github](/user-guide/providers/github/getting-started-github) | Official | Organizations / Repositories | UI, API, CLI |
| [Oracle Cloud](/user-guide/providers/oci/getting-started-oci) | Official | Tenancies / Compartments | UI, API, CLI |
| [Alibaba Cloud](/user-guide/providers/alibabacloud/getting-started-alibabacloud) | Official | Accounts | UI, API, CLI |
| [Cloudflare](/user-guide/providers/cloudflare/getting-started-cloudflare) | Official | Accounts | CLI |
| [Cloudflare](/user-guide/providers/cloudflare/getting-started-cloudflare) | Official | Accounts | UI, API, 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 |
| [OpenStack](/user-guide/providers/openstack/getting-started-openstack) | Official | Projects | UI, API, CLI |
| [LLM](/user-guide/providers/llm/getting-started-llm) | Official | Models | CLI |
| [Image](/user-guide/providers/image/getting-started-image) | Official | Container Images | CLI |
| [Image](/user-guide/providers/image/getting-started-image) | Official | Container Images | CLI, API |
| [Google Workspace](/user-guide/providers/googleworkspace/getting-started-googleworkspace) | Official | Domains | CLI |
| **NHN** | Unofficial | Tenants | CLI |
For more information about the checks and compliance of each provider visit [Prowler Hub](https://hub.prowler.com).
@@ -73,6 +73,7 @@ The following list includes all the AWS checks with configurable variables that
| `ssm_documents_set_as_public` | `trusted_account_ids` | List of Strings |
| `vpc_endpoint_connections_trust_boundaries` | `trusted_account_ids` | List of Strings |
| `vpc_endpoint_services_allowed_principals_trust_boundaries` | `trusted_account_ids` | List of Strings |
| `opensearch_service_domains_not_publicly_accessible` | `trusted_ips` | List of Strings |
## Azure
+1 -1
View File
@@ -99,7 +99,7 @@ def get_table(data):
## S3 Integration
If you are using Prowler SaaS with the S3 integration or that integration from Prowler Open Source and you want to use your data from your S3 bucket, you can run the following command in order to load the dashboard with the new files:
If you are using Prowler Cloud with the S3 integration or that integration from Prowler CLI and you want to use your data from your S3 bucket, you can run the following command in order to load the dashboard with the new files:
```sh
aws s3 cp s3://<your-bucket>/output/csv ./output --recursive
@@ -1,119 +1,130 @@
---
title: 'Cloudflare Authentication in Prowler'
title: "Cloudflare Authentication in Prowler"
---
Prowler for Cloudflare supports the following authentication methods:
import { VersionBadge } from "/snippets/version-badge.mdx"
- [**API Token**](#api-token-recommended) (**Recommended**)
- [**API Key and Email (Legacy)**](#api-key-and-email-legacy)
<VersionBadge version="5.17.0" />
Prowler for Cloudflare supports two authentication methods, both available in Prowler Cloud and Prowler CLI:
- [**API Token**](#api-token-recommended) (**Recommended**) — Scoped, least-privilege access to specific permissions and zones.
- [**API Key and Email**](#api-key-and-email-legacy) (**Legacy**) — Global access to the entire account using the Global API Key.
<Warning>
**Use only one authentication method at a time.** If both API Token and API Key + Email are set, Prowler uses the API Token and logs an error about the conflict.
</Warning>
## Required Permissions
Prowler requires read-only access to your Cloudflare zones and their settings. The following permissions are needed:
Prowler requires read-only access to Cloudflare zones and their settings. The following permissions must be configured when creating the API Token:
| Permission | Description |
|------------|-------------|
| `Zone:Read` | Read access to zone settings and configurations |
| `Zone Settings:Read` | Read access to zone security settings (SSL/TLS, HSTS, etc.) |
| `DNS:Read` | Read access to DNS records (for DNSSEC checks) |
| Resource | Permission | Access | Description |
|----------|------------|--------|-------------|
| `Account` | `Account Settings` | `Read` | Required to list accounts and verify user identity |
| `Zone` | `Zone` | `Read` | Required to list zones, rulesets, bot management, and SSL settings |
| `Zone` | `Zone Settings` | `Read` | Required to read zone security settings (TLS, HSTS, WAF, etc.) |
| `Zone` | `DNS` | `Read` | Required to read DNS records and DNSSEC status |
<Warning>
Ensure your API Token or API Key has access to all zones you want to scan. If permissions are missing, some checks may fail or return incomplete results.
Ensure the API Token has access to all zones targeted for scanning. Missing permissions may cause some checks to fail or return incomplete results.
</Warning>
---
## API Token (Recommended)
API Tokens are the recommended authentication method because they:
User API Tokens are the recommended authentication method because they:
- Can be scoped to specific permissions and zones
- Are more secure than global API keys
- Can be easily rotated without affecting other integrations
### Step 1: Create an API Token
<Note>
Create a **User API Token**, not an Account API Token. User API Tokens are created from the profile settings and offer finer permission control.
</Note>
1. **Log into Cloudflare Dashboard**
- Go to [https://dash.cloudflare.com](https://dash.cloudflare.com) and sign in
### Step 1: Create a User API Token
2. **Navigate to API Tokens**
- Click on your profile icon in the top right corner
- Select **My Profile**
- Click on the **API Tokens** tab
3. **Create a Custom Token**
- Click **Create Token**
- Select **Create Custom Token** (at the bottom)
4. **Configure Token Permissions**
Give your token a descriptive name (e.g., "Prowler Security Scanner") and add the [required permissions](#required-permissions) listed above.
5. **Set Zone Resources**
- Under **Zone Resources**, select either:
- **Include → All zones** (to scan all zones in your account)
1. Log into the [Cloudflare Dashboard](https://dash.cloudflare.com).
2. Click on the profile icon in the top right corner, then select "My Profile".
3. Click on the **API Tokens** tab.
4. Click **Create Token**, then select **Create Custom Token** at the bottom of the page.
5. Configure the token with the following settings:
- **Token name:** A descriptive name (e.g., "Prowler Security Scanner")
- **Permissions:**
- `Account` — `Account Settings` — `Read`
- `Zone` — `Zone` — `Read`
- `Zone` — `Zone Settings` — `Read`
- `Zone` — `DNS` — `Read`
- **Zone Resources:** Select either:
- **Include → All zones** (to scan all zones in the account)
- **Include → Specific zone** (to limit access to specific zones)
6. **Create and Copy Token**
- Click **Continue to summary**
- Review the permissions and click **Create Token**
- **Copy the token immediately** - Cloudflare will only show it once
![Token Permissions](/images/providers/cloudflare-token-permissions.png)
### Step 2: Store the Token Securely
6. Configure the **Account Resources** and **Zone Resources**, and optionally set a **TTL** for the token expiration. Click **Continue to summary**.
Store your API token as an environment variable:
![Token Resources and TTL](/images/providers/cloudflare-token-save.png)
```bash
export CLOUDFLARE_API_TOKEN="your-api-token-here"
```
7. Review the permissions and click **Create Token**.
8. Copy the token immediately.
<Warning>
Never commit API tokens to version control or share them in plain text. Use environment variables or a secrets manager.
Cloudflare only displays the token once. Copy it immediately and store it securely. If lost, a new token must be created.
</Warning>
### Step 2: Provide the Token to Prowler
- **Prowler Cloud:** Paste the token in the credentials form when configuring the Cloudflare provider.
- **Prowler CLI:** Export the token as an environment variable:
```console
export CLOUDFLARE_API_TOKEN="your-api-token-here"
prowler cloudflare
```
---
## API Key and Email (Legacy)
API Keys provide full access to your Cloudflare account. While supported, this method is less secure than API Tokens because it grants broader permissions.
API Keys provide full access to the Cloudflare account. While supported, this method is less secure than API Tokens because it grants broader permissions.
### Step 1: Get Your API Key
### Step 1: Get the Global API Key
1. **Log into Cloudflare Dashboard**
- Go to [https://dash.cloudflare.com](https://dash.cloudflare.com) and sign in
1. Log into the [Cloudflare Dashboard](https://dash.cloudflare.com).
2. Click on the profile icon in the top right corner, then select "My Profile".
3. Click on the **API Tokens** tab.
4. Scroll down to the **API Keys** section.
5. Click **View** next to **Global API Key**.
6. Enter the account password to reveal the key, then copy it.
2. **Navigate to API Tokens**
- Click on your profile icon in the top right corner
- Select **My Profile**
- Click on the **API Tokens** tab
### Step 2: Provide the Credentials to Prowler
3. **View Global API Key**
- Scroll down to the **API Keys** section
- Click **View** next to **Global API Key**
- Enter your password to reveal the key
- Copy the API key
- **Prowler Cloud:** Enter the Global API Key and email in the credentials form when configuring the Cloudflare provider.
- **Prowler CLI:** Export both values as environment variables:
### Step 2: Store Credentials Securely
Store both your API key and email as environment variables:
```bash
```console
export CLOUDFLARE_API_KEY="your-api-key-here"
export CLOUDFLARE_API_EMAIL="your-email@example.com"
prowler cloudflare
```
<Note>
The email must be the same email address used to log into your Cloudflare account.
The email must match the email address used to log into the Cloudflare account.
</Note>
---
## Best Practices
### Security Recommendations
- **Use API Tokens instead of API Keys** — Tokens can be scoped to specific permissions and zones.
- **Use environment variables** — Never hardcode credentials in scripts or commands.
- **Rotate credentials regularly** — Create new tokens periodically and revoke old ones.
- **Use least privilege** — Only grant the minimum permissions needed for scanning.
- **Monitor token usage** — Review the Cloudflare audit log for suspicious activity.
- **Use API Tokens instead of API Keys** - Tokens can be scoped to specific permissions
- **Use environment variables** - Never hardcode credentials in scripts or commands
- **Rotate credentials regularly** - Create new tokens periodically and revoke old ones
- **Use least privilege** - Only grant the minimum permissions needed
- **Monitor token usage** - Review the Cloudflare audit log for suspicious activity
<Warning>
**Use only one authentication method at a time.** If both API Token and API Key + Email are set, Prowler will use the API Token and log an error message.
</Warning>
---
## Troubleshooting
@@ -123,20 +134,15 @@ This error occurs when using API Key authentication without providing the email
### "Authentication error" or "Permission denied"
- Verify your API Token or API Key is correct and not expired
- Check that your token has the [required permissions](#required-permissions)
- Ensure your token has access to the zones you're trying to scan
- Verify the API Token or API Key is correct and not expired.
- Check that the token has the [required permissions](#required-permissions).
- Ensure the token has access to the zones targeted for scanning.
### "Both API Token and API Key and Email credentials are set"
This warning appears when all three environment variables are set:
- `CLOUDFLARE_API_TOKEN`
- `CLOUDFLARE_API_KEY`
- `CLOUDFLARE_API_EMAIL`
This warning appears when all three environment variables are set (`CLOUDFLARE_API_TOKEN`, `CLOUDFLARE_API_KEY`, `CLOUDFLARE_API_EMAIL`). To resolve, unset the credentials that are not needed:
To resolve, unset the credentials you don't want to use:
```bash
```console
# To use API Token only (recommended)
unset CLOUDFLARE_API_KEY
unset CLOUDFLARE_API_EMAIL
@@ -144,3 +150,7 @@ unset CLOUDFLARE_API_EMAIL
# Or to use API Key and Email only
unset CLOUDFLARE_API_TOKEN
```
### "Account not found" Error
This error occurs when a specified `--account-id` is not accessible with the current credentials. Verify the Account ID is correct and that the credentials have access to the target account.
@@ -1,117 +1,165 @@
---
title: 'Getting Started with Cloudflare'
title: 'Getting Started With Cloudflare on Prowler'
---
import { VersionBadge } from "/snippets/version-badge.mdx";
import { VersionBadge } from "/snippets/version-badge.mdx"
<VersionBadge version="5.17.0" />
Prowler for Cloudflare allows you to scan your Cloudflare zones for security misconfigurations, including SSL/TLS settings, DNSSEC, HSTS, and more.
Prowler for Cloudflare scans zones for security misconfigurations, including SSL/TLS settings, DNSSEC, HSTS, WAF rules, DNS records, and more.
## Prerequisites
Before running Prowler with the Cloudflare provider, ensure you have:
Set up authentication for Cloudflare with the [Cloudflare Authentication](/user-guide/providers/cloudflare/authentication) guide before starting either path:
1. A Cloudflare account with at least one zone
2. One of the following authentication methods configured (see [Authentication](/user-guide/providers/cloudflare/authentication)):
- An **API Token** (recommended)
- An **API Key + Email** (legacy)
- Create a Cloudflare User API Token (recommended) or locate the Global API Key
- Grant the required read-only permissions (`Account Settings:Read`, `Zone:Read`, `Zone Settings:Read`, `DNS:Read`)
- Identify the Cloudflare Account ID to use as the provider identifier
## Quick Start
<CardGroup cols={2}>
<Card title="Prowler Cloud" icon="cloud" href="#prowler-cloud">
Onboard Cloudflare using Prowler Cloud
</Card>
<Card title="Prowler CLI" icon="terminal" href="#prowler-cli">
Onboard Cloudflare using Prowler CLI
</Card>
</CardGroup>
## Prowler Cloud
<VersionBadge version="5.19.0" />
### Step 1: Locate the Account ID
1. Log into the [Cloudflare Dashboard](https://dash.cloudflare.com).
2. Select any zone in the target account.
3. On the zone overview page, find the **Account ID** in the right sidebar under the "API" section.
![Cloudflare Account ID](/images/providers/cloudflare-account-id.png)
<Note>
The Account ID is a 32-character hexadecimal string (e.g., `372e67954025e0ba6aaa6d586b9e0b59`). This value acts as the unique identifier for the Cloudflare account in Prowler Cloud.
</Note>
### Step 2: Open Prowler Cloud
1. Go to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler App](/user-guide/tutorials/prowler-app).
2. Navigate to "Configuration" > "Cloud Providers".
![Cloud Providers Page](/images/prowler-app/cloud-providers-page.png)
3. Click "Add Cloud Provider".
![Add a Cloud Provider](/images/prowler-app/add-cloud-provider.png)
4. Select "Cloudflare".
![Select Cloudflare](/images/providers/select-cloudflare-prowler-cloud.png)
5. Add the **Account ID** and an optional alias, then click "Next".
![Add Cloudflare Account ID](/images/providers/cloudflare-account-id-form.png)
### Step 3: Choose and Provide Authentication
After the Account ID is in place, select the authentication method that matches the Cloudflare setup:
![Select Authentication Method](/images/providers/cloudflare-auth-selection.png)
#### User API Token Authentication (Recommended)
1. Select **API Token**.
2. Enter the **User API Token** created in the Cloudflare Dashboard.
![API Token Form](/images/providers/cloudflare-token-form.png)
Use this method for scoped, least-privilege access. Full setup steps are in the [Authentication guide](/user-guide/providers/cloudflare/authentication#api-token-recommended).
#### API Key and Email Authentication (Legacy)
1. Select **API Key + Email**.
2. Enter the **Global API Key**.
3. Enter the **email address** associated with the Cloudflare account.
![API Key and Email Form](/images/providers/cloudflare-api-email-form.png)
For the complete setup workflow, follow the [Authentication guide](/user-guide/providers/cloudflare/authentication#api-key-and-email-legacy).
### Step 4: Launch the Scan
1. Review the summary.
2. Click **Launch Scan** to start auditing Cloudflare.
![Launch Scan](/images/providers/cloudflare-launch-scan.png)
---
## Prowler CLI
<VersionBadge version="5.17.0" />
### Step 1: Set Up Authentication
The recommended method is using an API Token via environment variable:
Choose the matching method from the [Cloudflare Authentication](/user-guide/providers/cloudflare/authentication) guide:
```bash
export CLOUDFLARE_API_TOKEN="your-api-token-here"
```
- **User API Token** (recommended): Set `CLOUDFLARE_API_TOKEN`
- **API Key + Email** (legacy): Set `CLOUDFLARE_API_KEY` and `CLOUDFLARE_API_EMAIL`
Alternatively, use API Key + Email:
### Step 2: Run the First Scan
```bash
export CLOUDFLARE_API_KEY="your-api-key-here"
export CLOUDFLARE_API_EMAIL="your-email@example.com"
```
Run a baseline scan after credentials are configured:
### Step 2: Run Prowler
Run a scan across all your Cloudflare zones:
```bash
```console
prowler cloudflare
```
That's it! Prowler will automatically discover all zones in your account and run security checks against them.
Prowler automatically discovers all zones accessible with the provided credentials and runs security checks against them.
## Authentication
### Step 3: Filter the Scan Scope (Optional)
Prowler reads Cloudflare credentials from environment variables. Set your credentials before running Prowler:
**API Token (Recommended):**
```bash
export CLOUDFLARE_API_TOKEN="your-api-token-here"
prowler cloudflare
```
**API Key + Email (Legacy):**
```bash
export CLOUDFLARE_API_KEY="your-api-key-here"
export CLOUDFLARE_API_EMAIL="your-email@example.com"
prowler cloudflare
```
## Filtering Zones
By default, Prowler scans all zones accessible with your credentials:
```bash
prowler cloudflare
```
#### Filter by Zone
To scan only specific zones, use the `-f`, `--region`, or `--filter-region` argument:
```bash
```console
prowler cloudflare -f example.com
```
You can specify multiple zones:
Multiple zones can be specified:
```bash
```console
prowler cloudflare -f example.com example.org
```
You can also use zone IDs instead of domain names:
Zone IDs are also supported:
```bash
```console
prowler cloudflare -f 023e105f4ecef8ad9ca31a8372d0c353
```
## Filtering Accounts
#### Filter by Account
By default, Prowler scans all accounts accessible with your credentials. If your API Token or API Key has access to multiple Cloudflare accounts, you can restrict the scan to specific accounts using the `--account-id` argument:
To restrict the scan to specific accounts, use the `--account-id` argument:
```bash
```console
prowler cloudflare --account-id 372e67954025e0ba6aaa6d586b9e0b59
```
You can specify multiple account IDs:
Multiple account IDs can be specified:
```bash
```console
prowler cloudflare --account-id 372e67954025e0ba6aaa6d586b9e0b59 9a7806061c88ada191ed06f989cc3dac
```
<Note>
If any of the provided account IDs are not found among the accounts accessible with your credentials, Prowler will raise an error and stop execution.
If any of the provided account IDs are not accessible with the current credentials, Prowler raises an error and stops execution.
</Note>
You can combine account and zone filtering to narrow the scan scope further:
Account and zone filtering can be combined to narrow the scan scope further:
```bash
```console
prowler cloudflare --account-id 372e67954025e0ba6aaa6d586b9e0b59 -f example.com
```
## Configuration
### Step 4: Use a Custom Configuration (Optional)
Prowler uses a configuration file to customize provider behavior. The Cloudflare configuration includes:
@@ -123,10 +171,8 @@ cloudflare:
To use a custom configuration:
```bash
```console
prowler cloudflare --config-file /path/to/config.yaml
```
## Next Steps
- [Authentication](/user-guide/providers/cloudflare/authentication) - Detailed guide on creating API tokens and keys
---
@@ -4,13 +4,11 @@ title: 'Getting Started With OpenStack'
import { VersionBadge } from "/snippets/version-badge.mdx"
<VersionBadge version="5.18.0" />
Prowler supports OpenStack both from the CLI and from Prowler Cloud. This guide walks you through the requirements, how to connect the provider in the UI, and how to run scans from the command line.
Prowler for OpenStack allows you to audit your OpenStack cloud infrastructure for security misconfigurations, including compute instances, networking, identity and access management, storage, and more.
<Warning>
<Note>
Prowler currently supports **public cloud OpenStack providers** (OVH, Infomaniak, Vexxhost, etc.). Support for self-deployed OpenStack environments is not yet available, if you are interested in this feature, please [open an issue](https://github.com/prowler-cloud/prowler/issues/new) or [contact us](https://prowler.com/contact).
</Warning>
</Note>
## Prerequisites
@@ -22,16 +20,47 @@ Before running Prowler with the OpenStack provider, ensure you have:
4. Access to Prowler CLI (see [Installation](/getting-started/installation/prowler-cli)) or an account created in [Prowler Cloud](https://cloud.prowler.com)
<CardGroup cols={2}>
<Card title="Prowler CLI" icon="terminal" href="#prowler-cli">
Run OpenStack security audits with Prowler CLI
<Card title="Prowler Cloud" icon="cloud" href="#prowler-cloud">
Onboard OpenStack using Prowler Cloud
</Card>
<Card title="Authentication Methods" icon="key" href="/user-guide/providers/openstack/authentication">
Learn about OpenStack authentication options
<Card title="Prowler CLI" icon="terminal" href="#prowler-cli">
Onboard OpenStack using Prowler CLI
</Card>
</CardGroup>
## Prowler Cloud
<VersionBadge version="5.19.0" />
### Step 1: Add the Provider
1. Navigate to "Cloud Providers" and click "Add Cloud Provider".
![Providers List](./images/select-provider.png)
2. Select "OpenStack" from the provider list.
3. Enter the "Project ID" from the OpenStack provider.
![Add project ID form](./images/add-provider-id.png)
4. (Optional) Add a friendly alias to identify this project in dashboards.
### Step 2: Provide Credentials
1. Click "Next" to open the credentials form.
2. Paste the full content of the `clouds.yaml` file into the "Clouds YAML Content" field. This file is available in the OpenStack provider's Horizon dashboard (see the [Authentication guide](/user-guide/providers/openstack/authentication) for detailed instructions).
3. Enter the "Cloud Name" — this is the key that identifies the cloud entry inside the `clouds.yaml` file (e.g., `mycloud`).
![Credentials form](./images/add-credentials.png)
### Step 3: Test the Connection and Start Scanning
1. Click "Test connection" to ensure Prowler Cloud can reach the OpenStack API.
![Test connection](./images/test-connection.png)
2. The provider will appear in the list with its current connection status.
3. Launch a scan from the provider row or from the "Scans" page.
---
## Prowler CLI
<VersionBadge version="5.18.0" />
### Step 1: Set Up Authentication
Download the `clouds.yaml` file from your OpenStack provider (see [Authentication guide](/user-guide/providers/openstack/authentication) for detailed instructions) and save it to `~/.config/openstack/clouds.yaml`:
@@ -98,7 +127,7 @@ Run a baseline scan of your OpenStack cloud:
prowler openstack --clouds-yaml-cloud openstack
```
Replace `openstack` with your cloud name if you customized it in the `clouds.yaml` file (e.g., `ovh-production`).
Replace `openstack` with the custom cloud name defined in the `clouds.yaml` file (e.g., `ovh-production`).
Prowler will automatically discover and audit all supported OpenStack services in your project.
Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

@@ -201,3 +201,20 @@ To expand the graph for detailed exploration, click the fullscreen icon in the g
alt="Attack Paths fullscreen mode with graph and node detail side panel"
width="700"
/>
## Using Attack Paths with the MCP Server
Attack Paths capabilities are also available through the [Prowler MCP Server](/getting-started/products/prowler-mcp), enabling interaction with Attack Paths data via AI assistants like Claude Desktop, Cursor, and other MCP clients.
The following MCP tools are available for Attack Paths:
- **`prowler_app_list_attack_paths_scans`** - List and filter Attack Paths scans
- **`prowler_app_list_attack_paths_queries`** - Discover available queries for a completed scan
- **`prowler_app_run_attack_paths_query`** - Execute a query and retrieve graph results with nodes and relationships
These tools enable workflows such as:
- Asking an AI assistant to identify privilege escalation paths in a specific AWS account
- Automating attack path analysis across multiple scans
- Combining attack path data with findings and compliance information for comprehensive security reports
For the complete list of MCP tools, see the [Tools Reference](/getting-started/basic-usage/prowler-mcp-tools#attack-paths-analysis).
@@ -24,6 +24,11 @@ Advanced Mutelist enables users to create powerful, pattern-based muting rules u
## Prerequisites
<Note>
Advanced Mutelist requires the **Manage Account** permission. See [RBAC Administrative Permissions](/user-guide/tutorials/prowler-app-rbac#rbac-administrative-permissions) for details.
</Note>
Before muting findings, ensure:
- Valid access to Prowler App with appropriate permissions
@@ -72,10 +77,10 @@ If the YAML configuration is invalid, an error message will be displayed
2. Navigate to the Findings page to verify muted findings
![Check muted findings](/images/mutelist-ui-9.png)
<Note>
The Advanced Mutelist configuration takes effect on subsequent scans. Existing findings are not retroactively muted.
<Warning>
The Advanced Mutelist configuration takes effect on **subsequent scans only**. Existing findings from previous scans are **not** retroactively muted. You must run a new scan after saving your YAML configuration to see its effect. Similarly, removing a pattern from the YAML configuration will only stop muting new findings generated by subsequent scans.
</Note>
</Warning>
## YAML Configuration Examples
Below are ready-to-use examples for different cloud providers. For detailed syntax and logic explanation, see [CLI Mutelist documentation](/user-guide/cli/tutorials/mutelist#how-the-mutelist-works).
@@ -416,6 +421,10 @@ Mutelist:
Description: "Mute findings for dev/test environments in alpha project"
```
### Priority: Advanced vs. Simple Mutelist
When both Advanced Mutelist (YAML) and [Simple Mutelist](/user-guide/tutorials/prowler-app-simple-mutelist) rules match the same finding, the **Advanced Mutelist takes higher priority**. The finding will be muted with the reason "Muted by mutelist". If a finding is not matched by the Advanced Mutelist but matches a Simple Mutelist rule, the Simple rule's custom justification is used instead.
### Best Practices
1. **Start Small**: Begin with specific resources and gradually expand
@@ -23,6 +23,11 @@ Simple Mutelist creates rules based on the finding's unique identifier (UID). Fo
</Note>
<Note>
Simple Mutelist requires the **Manage Scans** permission. See [RBAC Administrative Permissions](/user-guide/tutorials/prowler-app-rbac#rbac-administrative-permissions) for details.
</Note>
## Accessing the Mutelist Page
To access the Mutelist page:
@@ -85,10 +90,10 @@ To toggle a mute rule without deleting it:
3. Locate the mute rule
4. Use the toggle switch in the "Enabled" column to enable or disable the rule
<Note>
Disabled mute rules remain in the system but do not affect findings. Findings associated with disabled rules will appear as unmuted in subsequent scans.
<Warning>
Disabling a mute rule does not retroactively unmute existing findings that were already marked as muted. Those findings retain their muted status as point-in-time historical records. Only **new findings** generated by subsequent scans will appear as unmuted.
</Note>
</Warning>
### Editing Mute Rules
@@ -112,7 +117,7 @@ To permanently remove a mute rule:
5. Confirm the deletion
<Warning>
Deleting a mute rule is permanent. The finding will appear as unmuted in subsequent scans. To temporarily unmute a finding without losing the rule, disable the rule instead of deleting it.
Deleting a mute rule is permanent and cannot be undone. Existing findings that were already muted retain their muted status as historical records — only **new findings** from subsequent scans will appear as unmuted. To temporarily stop muting new findings without losing the rule, disable the rule instead of deleting it.
</Warning>
@@ -124,9 +129,13 @@ Simple Mutelist creates mute rules based on a finding's unique identifier (UID).
- **Historical findings** with the same UID are also muted
- **Future findings** from subsequent scans are automatically muted if they match the UID
### Bulk Muting and Grouping
When muting multiple findings at once, a single mute rule is created containing all selected finding UIDs. However, once a rule is created, **additional findings cannot be added to an existing rule**. To mute new findings, create a separate mute rule.
### Uniqueness Constraint
Each finding UID can only have one mute rule. Attempting to create a duplicate mute rule for the same finding displays an error message indicating the rule already exists.
Each finding UID can only belong to one **enabled** mute rule at a time. Attempting to create a mute rule that includes a finding UID already covered by another enabled rule displays a conflict error. If you need to reorganize mute rules, disable or delete the existing rule first, then create a new one.
## Simple Mutelist vs. Advanced Mutelist
@@ -134,8 +143,12 @@ Each finding UID can only have one mute rule. Attempting to create a duplicate m
| ------------------------ | ----------------------------------------- | ------------------------------------------------------ |
| **Configuration method** | Point-and-click interface | YAML configuration file |
| **Muting scope** | Individual finding UIDs | Patterns based on checks, regions, resources, and tags |
| **When muting applies** | Immediately (current + historical findings) | On subsequent scans only (not retroactive) |
| **Unmuting behavior** | Disabling/deleting a rule only affects new findings from subsequent scans | Removing a pattern stops muting on the next scan |
| **Adding findings later** | Not supported — must create a new rule | Automatic — any finding matching the pattern is muted |
| **Regular expressions** | Not supported | Fully supported |
| **Bulk operations** | Checkbox selection in Findings table | YAML wildcards and patterns |
| **Priority** | Applied after Advanced Mutelist | Highest priority |
| **Best for** | Quick, ad-hoc muting of specific findings | Complex, policy-driven muting rules |
### When to Use Simple Mutelist
@@ -170,6 +183,14 @@ If an error indicates a mute rule already exists for a finding:
3. Edit the existing rule's justification if needed, or
4. Delete the existing rule and create a new one
### Finding Still Appears Muted After Disabling or Deleting a Rule
If a finding still appears as muted after disabling or deleting its mute rule:
1. This is expected behavior — existing findings retain their muted status as historical records
2. Run a new scan to generate new findings that will reflect the updated rule state
3. New findings with the same UID will appear with their actual status (PASS/FAIL) instead of muted
### Finding Still Appears Unmuted
If a muted finding still appears unmuted:
@@ -27,7 +27,7 @@ This feature is **exclusively available in Prowler Cloud**. For CLI-based multi-
Before using the AWS Organizations wizard, you need to deploy **two IAM roles** in your AWS environment. The onboarding follows this sequence:
<Frame>
<img src="/images/organizations/onboarding-flow.svg" alt="Onboarding flow: 1. Create Management Account Role, 2. Deploy StackSet, 3. Run the Wizard, 4. Launch Scans" />
<img src="/images/organizations/onboarding-flow.svg" alt="Onboarding flow: 1. Create Management Account Role (Quick Create or Manual), 2. Deploy StackSet, 3. Run the Wizard, 4. Launch Scans" />
</Frame>
## Key Concepts
@@ -46,11 +46,11 @@ Prowler requires **two separate IAM roles** deployed in different places, each w
| Role | Where it lives | What it does | How to deploy it |
|------|---------------|--------------|------------------|
| **ProwlerScan** (management account) | Your management (root) account only | Discovers the Organization structure **and** scans the management account. Has additional Organizations discovery permissions. | **Manually** in the IAM Console ([Step 1](#step-1-create-the-management-account-role)). Cannot be deployed via StackSet. |
| **ProwlerScan** (management account) | Your management (root) account only | Discovers the Organization structure **and** scans the management account. Has additional Organizations discovery permissions. | Via **Quick Create** link or **manually** in the IAM Console ([Step 1](#step-1-create-the-management-account-role)). Cannot be deployed via StackSet. |
| **ProwlerScan** (member accounts) | Every member account | Scans the account for security findings. | Via **CloudFormation StackSet** ([Step 2](#step-2-deploy-the-cloudformation-stackset)). Automated across all accounts. |
<Frame caption="Both roles share the same name `ProwlerScan`. The management account role includes additional Organization discovery permissions.">
<img src="/images/organizations/two-roles-architecture.svg" alt="Two Roles Architecture: ProwlerScan in management account (discovery + scanning) and ProwlerScan in member accounts (scanning only)" />
<img src="/images/organizations/two-roles-architecture.svg" alt="Two Roles Architecture: ProwlerScan in management account (Quick Create or Manual, discovery + scanning) and ProwlerScan in member accounts (via StackSet, scanning only)" />
</Frame>
<Note>
@@ -76,14 +76,31 @@ Your AWS environment must have [AWS Organizations](https://docs.aws.amazon.com/o
The first role you need to create is the **management account role**. This role allows Prowler to discover your Organization structure — listing accounts, OUs, and hierarchy.
<Warning>
**This role must be created manually.** Organizational CloudFormation StackSets do not deploy to the management account itself — this is an AWS limitation, not a Prowler one. StackSets with service-managed permissions only target member accounts. Similarly, the Prowler Quick Create link only deploys the role to member accounts.
**StackSets do not deploy to the management account.** Organizational CloudFormation StackSets with service-managed permissions only target member accounts — this is an AWS limitation, not a Prowler one. You must create the management account role separately, either via the Quick Create link ([Option A](#option-a-quick-create-link-fastest)) or manually ([Option B](#option-b-create-the-role-manually)).
</Warning>
<Note>
**The role must be named `ProwlerScan`** — the same name as the role deployed to member accounts via StackSet. Prowler expects a consistent role name across all accounts in the Organization. If you use a different name, connection tests and scans will fail for the management account.
</Note>
### Create the IAM Role
### Option A: Quick Create Link (Fastest)
The Prowler wizard provides a one-click link that opens the AWS Console with the CloudFormation template pre-configured. This creates a **CloudFormation Stack** (not a StackSet) that deploys the ProwlerScan role with Organizations permissions enabled in your management account.
<Tip>
**[Open Quick Create Stack in AWS Console →](https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/quickcreate?templateURL=https%3A%2F%2Fprowler-cloud-public.s3.eu-west-1.amazonaws.com%2Fpermissions%2Ftemplates%2Faws%2Fcloudformation%2Fprowler-scan-role.yml&stackName=Prowler&param_EnableOrganizations=true)**
Opens the CloudFormation Console with the Prowler scan role template and `EnableOrganizations=true` pre-filled. You will need to enter the **ExternalId** parameter manually — copy it from the Prowler wizard ([Step 4](#step-4-authenticate-with-your-management-account)).
</Tip>
1. Click **[Open Quick Create Stack in AWS Console →](https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/quickcreate?templateURL=https%3A%2F%2Fprowler-cloud-public.s3.eu-west-1.amazonaws.com%2Fpermissions%2Ftemplates%2Faws%2Fcloudformation%2Fprowler-scan-role.yml&stackName=Prowler&param_EnableOrganizations=true)** or use the **Create Stack in Management Account** button in the Prowler wizard (which also pre-fills the ExternalId).
2. Enter the **ExternalId** parameter if not pre-filled.
3. Check **"I acknowledge that AWS CloudFormation might create IAM resources with custom names"** and click **Create stack**.
4. Wait for the stack to reach **CREATE_COMPLETE** status.
Take note of the **Role ARN** from the stack's **Outputs** tab — you will need it in the wizard.
### Option B: Create the Role Manually
1. Sign in to the [AWS IAM Console](https://console.aws.amazon.com/iam/) in your **management account**.
@@ -181,43 +198,26 @@ The StackSet uses **service-managed permissions**, which means AWS Organizations
**Trusted access required:** CloudFormation StackSets must have trusted access enabled in your management account. Verify this in the AWS Console under **AWS Organizations > Settings > Trusted access for AWS CloudFormation StackSets**.
</Note>
### Option A: Using the Prowler Quick Create Link (Recommended)
The Prowler wizard provides a one-click link that opens the AWS Console with everything pre-configured.
<Warning>
**The Quick Create link creates a Stack, not a StackSet.** The link in the Prowler wizard creates a CloudFormation **Stack** that deploys the ProwlerScan role in your management account only ([Step 1](#step-1-create-the-management-account-role)). To deploy the role across **member accounts**, you must create a StackSet manually as described below. AWS does not support Quick Create links for StackSets.
</Warning>
<Tip>
**[Open Quick Create in AWS Console →](https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/quickcreate?templateURL=https%3A%2F%2Fprowler-cloud-public.s3.eu-west-1.amazonaws.com%2Fpermissions%2Ftemplates%2Faws%2Fcloudformation%2Fprowler-scan-role.yml&stackName=Prowler)**
**[Open StackSets Console →](https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacksets/create)**
Opens the CloudFormation Console with the Prowler scan role template and parameters pre-configured. You can also find this link in the Prowler wizard during [Step 4: Authentication](#step-4-authenticate-with-your-management-account).
Opens the CloudFormation StackSets creation page directly. You will need to paste the template URL and ExternalId manually.
</Tip>
1. Review the pre-filled parameters:
- **Template URL**: Points to the official [Prowler scan role template](https://prowler-cloud-public.s3.eu-west-1.amazonaws.com/permissions/templates/aws/cloudformation/prowler-scan-role.yml) hosted on Prowler's public S3 bucket.
- **ExternalId**: Pre-filled with your tenant's External ID when clicking the link from the Prowler Cloud wizard. If you open this link directly, you will need to enter the External ID manually.
{/* TODO: screenshot of AWS Console Quick Create page showing pre-filled parameters */}
2. Under **Deployment targets**, select:
- **Deploy to organization** to deploy to all accounts, or
- **Deploy to organizational units (OUs)** and specify the OU IDs you want to cover.
3. Review the settings and click **Create StackSet**. AWS will begin deploying the ProwlerScan role to every target account.
### Option B: Manual StackSet Deployment
If you prefer full control over the deployment:
1. Open the [AWS CloudFormation Console](https://console.aws.amazon.com/cloudformation/) in your management account.
2. Go to **StackSets > Create StackSet**.
3. Choose **Service-managed permissions**.
4. Use this template URL:
1. Click the link above or navigate to **CloudFormation > StackSets > Create StackSet** in your management account.
2. Choose **Service-managed permissions**.
3. Select **Amazon S3 URL** as the template source and paste the following URL:
```
https://prowler-cloud-public.s3.eu-west-1.amazonaws.com/permissions/templates/aws/cloudformation/prowler-scan-role.yml
```
5. Set the **ExternalId** parameter to the External ID shown in the Prowler wizard.
6. Choose your deployment targets (entire organization or specific OUs).
7. Select the AWS regions where you want the role deployed.
8. Click **Create StackSet**.
4. Set the **ExternalId** parameter to the External ID shown in the Prowler wizard.
5. Choose your deployment targets (entire organization or specific OUs).
6. Select the AWS regions where you want the role deployed.
7. Click **Create StackSet**.
### Verify StackSet Deployment
@@ -281,32 +281,42 @@ Click **Next** to proceed to the authentication phase.
## Step 4: Authenticate with Your Management Account
### Copy the External ID
The wizard's **Authentication Details** page guides you through three actions: deploying the roles in AWS, entering the management account Role ARN, and confirming the deployment.
The wizard displays a **Prowler External ID** — auto-generated and unique to your tenant. Click the copy icon to copy it. If you haven't already configured the trust policy on your management account role ([Step 1](#step-1-create-the-management-account-role)), do so now using this External ID.
### External ID
The wizard displays a **Prowler External ID** at the top — auto-generated and unique to your tenant. Click the copy icon to copy it. You will need this External ID for both the management account Stack and the member accounts StackSet.
### Deploy the Roles
The wizard provides two deployment actions:
1. **Create Stack in Management Account** — opens a Quick Create link that deploys the ProwlerScan role with `EnableOrganizations=true` in your management account ([Step 1](#step-1-create-the-management-account-role)). The External ID is pre-filled.
2. **Open StackSets Console** — links to the CloudFormation StackSets console where you create a StackSet for member accounts ([Step 2](#step-2-deploy-the-cloudformation-stackset)). Copy the template URL shown in the wizard and paste the External ID manually.
<Frame>
<img src="/images/organizations/authentication-details.png" alt="Authentication Details form showing External ID, Role ARN field, and StackSet confirmation checkbox" />
<img src="/images/organizations/authentication-details.png" alt="Authentication Details form showing External ID, two deployment buttons (Create Stack in Management Account and Open StackSets Console), Management Account Role ARN field, and deployment confirmation checkbox" />
</Frame>
### Enter the Role ARN
### Enter the Management Account Role ARN
Paste the **Role ARN** of the management account role you created in [Step 1](#step-1-create-the-management-account-role) into the **Role ARN** field.
Paste the **Role ARN** of the management account role you created in [Step 1](#step-1-create-the-management-account-role) into the **Management Account Role ARN** field.
The ARN follows this format:
```
arn:aws:iam::<account-id>:role/<role-name>
arn:aws:iam::<account-id>:role/ProwlerScan
```
For example: `arn:aws:iam::123456789012:role/ProwlerScan`
<Frame>
<img src="/images/organizations/role-arn-field.png" alt="Role ARN field in the Authentication Details form" />
<img src="/images/organizations/role-arn-field.png" alt="Management Account Role ARN field in the Authentication Details form" />
</Frame>
### Confirm and Discover
1. Check the box: **"The StackSet has been successfully deployed in AWS"**.
1. Check the box: **"The Stack and StackSet have been successfully deployed in AWS"**.
2. Click **Authenticate**.
Here's what happens behind the scenes:
@@ -314,8 +324,6 @@ Here's what happens behind the scenes:
- An asynchronous discovery is triggered to query your AWS Organization structure.
- You will see a **"Gathering AWS Accounts..."** spinner — this typically takes **30 seconds to 2 minutes** depending on your organization size.
{/* TODO: screenshot of the Authentication Details form with the spinner */}
## Step 5: Select Accounts to Scan
### Understanding the Tree View
@@ -364,8 +372,6 @@ Some accounts may appear as **blocked** (grayed out, not selectable). This happe
Hover over the blocked account to see the specific reason.
{/* TODO: screenshot of the tree view with account selection, showing active, already-connected, and blocked accounts */}
## Step 6: Test Connections
### How Connection Testing Works
@@ -452,8 +458,6 @@ Scans are only launched for accounts that are accessible (passed connection test
- Results populate the **Overview** and **Findings** pages.
- Prowler runs an **automatic sync every 6 hours** to detect new accounts added to your Organization or accounts that have been removed. New accounts are onboarded automatically based on the parent OU configuration.
{/* TODO: screenshot of the Launch Scan step */}
## Billing Impact
Each AWS account you connect through the Organizations wizard counts as one **provider** in your Prowler Cloud subscription.
+40
View File
@@ -2,6 +2,45 @@
All notable changes to the **Prowler SDK** are documented in this file.
## [5.20.0] (Prowler UNRELEASED)
### 🚀 Added
- `entra_conditional_access_policy_approved_client_app_required_for_mobile` check for m365 provider [(#10216)](https://github.com/prowler-cloud/prowler/pull/10216)
- `entra_conditional_access_policy_compliant_device_hybrid_joined_device_mfa_required` check for M365 provider [(#10197)](https://github.com/prowler-cloud/prowler/pull/10197)
- Add `trusted_ips` configurable option to `opensearch_service_domains_not_publicly_accessible` check to reduce false positives on IP-restricted policies [(#8631)](https://github.com/prowler-cloud/prowler/pull/8631)
- `guardduty_delegated_admin_enabled_all_regions` check for AWS provider [(#9867)](https://github.com/prowler-cloud/prowler/pull/9867)
- OpenStack object storage service with 7 checks [(#10258)](https://github.com/prowler-cloud/prowler/pull/10258)
- Add AWS Organizations OU metadata (OU ID, OU path) to ASFF, OCSF and CSV outputs [(#10283)](https://github.com/prowler-cloud/prowler/pull/10283)
### 🔄 Changed
- Update Kubernetes API server checks metadata to new format [(#9674)](https://github.com/prowler-cloud/prowler/pull/9674)
- Update Kubernetes Controller Manager service metadata to new format [(#9675)](https://github.com/prowler-cloud/prowler/pull/9675)
- Update Kubernetes Core service metadata to new format [(#9676)](https://github.com/prowler-cloud/prowler/pull/9676)
- Update Kubernetes Kubelet service metadata to new format [(#9677)](https://github.com/prowler-cloud/prowler/pull/9677)
- Update Kubernetes RBAC service metadata to new format [(#9678)](https://github.com/prowler-cloud/prowler/pull/9678)
- Update Kubernetes Scheduler service metadata to new format [(#9679)](https://github.com/prowler-cloud/prowler/pull/9679)
- Update MongoDB Atlas Organizations service metadata to new format [(#9658)](https://github.com/prowler-cloud/prowler/pull/9658)
- Update MongoDB Atlas clusters service metadata to new format [(#9657)](https://github.com/prowler-cloud/prowler/pull/9657)
- Update GitHub Repository service metadata to new format [(#9659)](https://github.com/prowler-cloud/prowler/pull/9659)
- Update GitHub Organization service metadata to new format [(#10273)](https://github.com/prowler-cloud/prowler/pull/10273)
- Update Oracle Cloud Compute Engine service metadata to new format [(#9371)](https://github.com/prowler-cloud/prowler/pull/9371)
- Update Oracle Cloud Database service metadata to new format [(#9372)](https://github.com/prowler-cloud/prowler/pull/9372)
- Update Oracle Cloud File Storage service metadata to new format [(#9374)](https://github.com/prowler-cloud/prowler/pull/9374)
- Update Oracle Cloud Integration service metadata to new format [(#9376)](https://github.com/prowler-cloud/prowler/pull/9376)
- Update Oracle Cloud KMS service metadata to new format [(#9377)](https://github.com/prowler-cloud/prowler/pull/9377)
- Update Oracle Cloud Network service metadata to new format [(#9378)](https://github.com/prowler-cloud/prowler/pull/9378)
- Update Oracle Cloud Object Storage service metadata to new format [(#9379)](https://github.com/prowler-cloud/prowler/pull/9379)
- Update Oracle Cloud Events service metadata to new format [(#9373)](https://github.com/prowler-cloud/prowler/pull/9373)
- Update Oracle Cloud Identity service metadata to new format [(#9375)](https://github.com/prowler-cloud/prowler/pull/9375)
- Update Alibaba Cloud services metadata to new format [(#10289)](https://github.com/prowler-cloud/prowler/pull/10289)
- Update M365 Admin Center service metadata to new format [(#9680)](https://github.com/prowler-cloud/prowler/pull/9680)
- Update M365 Defender service metadata to new format [(#9681)](https://github.com/prowler-cloud/prowler/pull/9681)
- Update M365 Purview service metadata to new format [(#9092)](https://github.com/prowler-cloud/prowler/pull/9092)
---
## [5.19.0] (Prowler v5.19.0)
### 🚀 Added
@@ -72,6 +111,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
### 🐞 Fixed
- Google Workspace provider `test_connection()` missing `provider_id` parameter for API integration [(#10247)](https://github.com/prowler-cloud/prowler/pull/10247)
- Update AWS checks metadata URLs to replace deprecated Trend Micro CloudOne Conformity (EOL July 2026) with Vision One and remove docs.prowler.com references [(#10068)](https://github.com/prowler-cloud/prowler/pull/10068)
- Standardize resource_id values across Azure checks to use actual Azure resource IDs and prevent duplicate resource entries [(#9994)](https://github.com/prowler-cloud/prowler/pull/9994)
- VPC endpoint service collection filtering third-party services that caused AccessDenied errors on `DescribeVpcEndpointServicePermissions` [(#10152)](https://github.com/prowler-cloud/prowler/pull/10152)
@@ -616,6 +616,7 @@
"Checks": [
"defenderxdr_endpoint_privileged_user_exposed_credentials",
"entra_admin_users_phishing_resistant_mfa_enabled",
"entra_conditional_access_policy_approved_client_app_required_for_mobile",
"entra_conditional_access_policy_app_enforced_restrictions",
"entra_managed_device_required_for_authentication",
"entra_managed_device_required_for_mfa_registration",
@@ -671,6 +672,7 @@
],
"Checks": [
"entra_admin_portals_access_restriction",
"entra_conditional_access_policy_approved_client_app_required_for_mobile",
"entra_conditional_access_policy_app_enforced_restrictions",
"entra_policy_guest_users_access_restrictions",
"sharepoint_external_sharing_restricted"
@@ -692,6 +694,7 @@
"entra_admin_users_mfa_enabled",
"entra_admin_users_sign_in_frequency_enabled",
"entra_all_apps_conditional_access_coverage",
"entra_conditional_access_policy_approved_client_app_required_for_mobile",
"entra_break_glass_account_fido2_security_key_registered",
"entra_identity_protection_sign_in_risk_enabled",
"entra_managed_device_required_for_authentication",
+1 -1
View File
@@ -38,7 +38,7 @@ class _MutableTimestamp:
timestamp = _MutableTimestamp(datetime.today())
timestamp_utc = _MutableTimestamp(datetime.now(timezone.utc))
prowler_version = "5.19.1"
prowler_version = "5.20.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"
+5
View File
@@ -72,6 +72,11 @@ aws:
# trusted_account_ids : ["123456789012", "098765432109", "678901234567"]
trusted_account_ids: []
# AWS OpenSearch Configuration (opensearch_service_domains_not_publicly_accessible)
# Trusted IP addresses or CIDR ranges that should not be considered as public access, e.g.
# trusted_ips: ["1.2.3.4", "10.0.0.0/8"]
trusted_ips: []
# AWS Cloudwatch Configuration
# aws.cloudwatch_log_group_retention_policy_specific_days_enabled --> by default is 365 days
log_group_retention_days: 365
+4
View File
@@ -76,6 +76,8 @@ class ASFF(Output):
ProductArn=f"arn:{finding.partition}:securityhub:{finding.region}::product/prowler/prowler",
ProductFields=ProductFields(
ProwlerResourceName=finding.resource_uid,
ProwlerAccountOrganizationalUnitId=finding.account_ou_uid,
ProwlerAccountOrganizationalUnitName=finding.account_ou_name,
),
GeneratorId="prowler-" + finding.metadata.CheckID,
AwsAccountId=finding.account_uid,
@@ -242,6 +244,8 @@ class ProductFields(BaseModel):
ProviderName: str = "Prowler"
ProviderVersion: str = prowler_version
ProwlerResourceName: str
ProwlerAccountOrganizationalUnitId: Optional[str] = None
ProwlerAccountOrganizationalUnitName: Optional[str] = None
class Severity(BaseModel):
+2
View File
@@ -82,6 +82,8 @@ class CSV(Output):
finding_dict["ADDITIONAL_URLS"] = unroll_list(
finding.metadata.AdditionalURLs
)
finding_dict["ACCOUNT_OU_UID"] = finding.account_ou_uid
finding_dict["ACCOUNT_OU_NAME"] = finding.account_ou_name
self._data.append(finding_dict)
except Exception as error:
logger.error(
+8
View File
@@ -39,6 +39,8 @@ class Finding(BaseModel):
account_email: Optional[str] = None
account_organization_uid: Optional[str] = None
account_organization_name: Optional[str] = None
account_ou_uid: Optional[str] = None
account_ou_name: Optional[str] = None
metadata: CheckMetadata
account_tags: dict = Field(default_factory=dict)
uid: str
@@ -155,6 +157,12 @@ class Finding(BaseModel):
output_data["account_tags"] = get_nested_attribute(
provider, "organizations_metadata.account_tags"
)
output_data["account_ou_uid"] = get_nested_attribute(
provider, "organizations_metadata.account_ou_id"
)
output_data["account_ou_name"] = get_nested_attribute(
provider, "organizations_metadata.account_ou_name"
)
output_data["partition"] = get_nested_attribute(
provider, "identity.partition"
)
+2 -1
View File
@@ -194,7 +194,8 @@ class OCSF(Output):
org=Organization(
uid=finding.account_organization_uid,
name=finding.account_organization_name,
# TODO: add the org unit id and name
ou_uid=finding.account_ou_uid,
ou_name=finding.account_ou_name,
),
provider=finding.provider,
region=finding.region,
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "actiontrail_multi_region_enabled",
"CheckTitle": "ActionTrail are configured to export copies of all Log entries",
"CheckType": [
"Unusual logon",
"Cloud threat detection"
],
"CheckTitle": "ActionTrail is configured to export copies of all log entries across all regions",
"CheckType": [],
"ServiceName": "actiontrail",
"SubServiceName": "",
"ResourceIdTemplate": "acs:actiontrail::account-id:trail",
"ResourceIdTemplate": "",
"Severity": "critical",
"ResourceType": "AlibabaCloudActionTrail",
"ResourceType": "ALIYUN::ACTIONTRAIL::Trail",
"ResourceGroup": "monitoring",
"Description": "**ActionTrail** is a web service that records API calls for your account and delivers log files to you.\n\nThe recorded information includes the identity of the API caller, the time of the API call, the source IP address of the API caller, the request parameters, and the response elements returned by the Alibaba Cloud service. ActionTrail provides a history of API calls for an account, including API calls made via the Management Console, SDKs, and command line tools.",
"Risk": "The API call history produced by ActionTrail enables **security analysis**, **resource change tracking**, and **compliance auditing**.\n\nEnsuring that a **multi-region trail** exists will detect unexpected activities occurring in otherwise unused regions. Global Service Logging should be enabled by default to capture events generated on Alibaba Cloud global services, ensuring the recording of management operations performed on all resources in an Alibaba Cloud account.",
"Description": "**Alibaba Cloud ActionTrail** is a service that records API calls made to your account and delivers log files containing the identity of the API caller, the time and source IP of the call, the request parameters, and the response elements returned by the service. Ensuring that a **multi-region trail** exists guarantees that management operations performed across all regions and global services are captured, enabling detection of unexpected activities in otherwise unused regions.",
"Risk": "Without a **multi-region trail** enabled, API calls made in regions outside the primary trail's scope will not be recorded. This creates blind spots in **security analysis**, **resource change tracking**, and **compliance auditing**, potentially allowing unauthorized or malicious activity to go undetected across your Alibaba Cloud account.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/28829.htm",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ActionTrail/enable-multi-region-trails.html"
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ActionTrail/enable-multi-region-trails.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun actiontrail CreateTrail --Name <trail_name> --OssBucketName <oss_bucket_for_actiontrail> --RoleName aliyunactiontraildefaultrole --SlsProjectArn <sls_project_arn_for_actiontrail> --SlsWriteRoleArn <sls_role_arn_for_actiontrail> --EventRW <api_type_for_actiontrail>",
"NativeIaC": "",
"Other": "",
"Other": "1. Log on to the **ActionTrail Console**\n2. Click on **Trails** in the left navigation pane\n3. Click **Add new trail**\n4. Enter a trail name in the `Trail name` box\n5. Set **Yes** for `Apply Trail to All Regions`\n6. Specify an OSS bucket name in the `OSS bucket` box\n7. Specify an SLS project name in the `SLS project` box\n8. Click **Create**",
"Terraform": "resource \"alicloud_actiontrail_trail\" \"example\" {\n trail_name = \"multi-region-trail\"\n trail_region = \"All\"\n sls_project_arn = \"acs:log:cn-hangzhou:123456789:project/actiontrail-project\"\n sls_write_role_arn = data.alicloud_ram_roles.actiontrail.roles.0.arn\n}"
},
"Recommendation": {
"Text": "1. Log on to the **ActionTrail Console**\n2. Click on **Trails** in the left navigation pane\n3. Click **Add new trail**\n4. Enter a trail name in the `Trail name` box\n5. Set **Yes** for `Apply Trail to All Regions`\n6. Specify an OSS bucket name in the `OSS bucket` box\n7. Specify an SLS project name in the `SLS project` box\n8. Click **Create**",
"Text": "Enable a multi-region trail in ActionTrail to ensure all API calls across all regions are recorded and delivered to a centralized OSS bucket and SLS project for security analysis and compliance auditing.",
"Url": "https://hub.prowler.com/check/actiontrail_multi_region_enabled"
}
},
@@ -1,32 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "actiontrail_oss_bucket_not_publicly_accessible",
"CheckTitle": "The OSS used to store ActionTrail logs is not publicly accessible",
"CheckType": [
"Sensitive file tampering"
],
"CheckTitle": "The OSS bucket used to store ActionTrail logs is not publicly accessible",
"CheckType": [],
"ServiceName": "actiontrail",
"SubServiceName": "",
"ResourceIdTemplate": "acs:oss::account-id:bucket-name",
"ResourceIdTemplate": "",
"Severity": "critical",
"ResourceType": "AlibabaCloudOSSBucket",
"ResourceType": "ALIYUN::ACTIONTRAIL::Trail",
"ResourceGroup": "storage",
"Description": "**ActionTrail** logs a record of every API call made in your Alibaba Cloud account. These log files are stored in an **OSS bucket**.\n\nIt is recommended that the **Access Control List (ACL)** of the OSS bucket, which ActionTrail logs to, prevents public access to the ActionTrail logs.",
"Risk": "Allowing **public access** to ActionTrail log content may aid an adversary in identifying weaknesses in the affected account's use or configuration.\n\nExposed audit logs can reveal sensitive information about your infrastructure, API usage patterns, and security configurations.",
"Description": "**Alibaba Cloud ActionTrail** logs a record of every API call made in your account and stores these log files in an **OSS bucket**. It is recommended that the **Access Control List (ACL)** of the OSS bucket used by ActionTrail is set to `private` to prevent unauthorized public access to sensitive audit log data.",
"Risk": "Allowing **public access** to the OSS bucket containing ActionTrail logs may expose sensitive information about your infrastructure, API usage patterns, and security configurations. An adversary could use this information to identify weaknesses in the affected account, leading to potential **data breaches**, **privilege escalation**, and **compliance violations**.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://help.aliyun.com/document_detail/31954.html",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ActionTrail/trail-bucket-publicly-accessible.html"
"https://www.alibabacloud.com/help/doc-detail/31954.htm",
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ActionTrail/trail-bucket-publicly-accessible.html"
],
"Remediation": {
"Code": {
"CLI": "ossutil set-acl oss://<bucketName> private -b",
"NativeIaC": "",
"Other": "",
"Other": "1. Log on to the **OSS Console**\n2. Right-click on the bucket and select **Basic Settings**\n3. In the Access Control List pane, click **Configure**\n4. The Bucket ACL tab shows three types of grants: `Private`, `Public Read`, `Public Read/Write`\n5. Ensure **Private** is set for the bucket\n6. Click **Save** to save the ACL",
"Terraform": "resource \"alicloud_oss_bucket_public_access_block\" \"actiontrail\" {\n bucket = alicloud_oss_bucket.actiontrail.bucket\n block_public_access = true\n}"
},
"Recommendation": {
"Text": "1. Log on to the **OSS Console**\n2. Right-click on the bucket and select **Basic Settings**\n3. In the Access Control List pane, click **Configure**\n4. The Bucket ACL tab shows three types of grants: `Private`, `Public Read`, `Public Read/Write`\n5. Ensure **Private** is set for the bucket\n6. Click **Save** to save the ACL",
"Text": "Set the ACL of the OSS bucket used to store ActionTrail logs to private to prevent unauthorized public access to sensitive audit log data.",
"Url": "https://hub.prowler.com/check/actiontrail_oss_bucket_not_publicly_accessible"
}
},
@@ -1,32 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "cs_kubernetes_cloudmonitor_enabled",
"CheckTitle": "CloudMonitor is set to Enabled on Kubernetes Engine Clusters",
"CheckType": [
"Threat detection during container runtime"
],
"CheckTitle": "Kubernetes cluster has CloudMonitor enabled",
"CheckType": [],
"ServiceName": "cs",
"SubServiceName": "",
"ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "AlibabaCloudKubernetesCluster",
"ResourceType": "ALIYUN::CS::ManagedKubernetesCluster",
"ResourceGroup": "container",
"Description": "The monitoring service in **Kubernetes Engine clusters** depends on the Alibaba Cloud **CloudMonitor** agent to access additional system resources and application services in virtual machine instances.\n\nThe monitor can access metrics about CPU utilization, disk traffic metrics, network traffic, and disk IO information, which help monitor signals and build operations in your Kubernetes Engine clusters.",
"Risk": "Without **CloudMonitor** enabled, you lack visibility into system metrics and custom metrics. System metrics measure the cluster's infrastructure, such as CPU or memory usage.\n\nWith CloudMonitor, a monitor controller is created that periodically connects to each node and collects metrics about its Pods and containers, then sends the metrics to CloudMonitor server.",
"Description": "**Alibaba Cloud CloudMonitor** agent provides visibility into system metrics for **Kubernetes Engine clusters**, including CPU utilization, disk traffic, network traffic, and disk IO information. Without the CloudMonitor agent enabled, operators lack critical observability into node and pod health, making it difficult to detect performance degradation or anomalous resource consumption. Enabling CloudMonitor ensures that a monitor controller is created to periodically connect to each node, collect metrics about its Pods and containers, and send them to the **CloudMonitor** server for analysis and alerting.",
"Risk": "Without **CloudMonitor** enabled on Kubernetes Engine clusters, there is no automated collection of system-level metrics such as CPU, memory, disk, and network usage. This lack of visibility can delay detection of **resource exhaustion**, **node failures**, and **abnormal workload behavior**, increasing the risk of undetected **availability** and **performance** issues. In a security context, the absence of monitoring data impairs the ability to identify **denial-of-service conditions** or **cryptojacking** activities running on cluster nodes.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://help.aliyun.com/document_detail/125508.html",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/enable-cloud-monitor.html"
"https://www.alibabacloud.com/help/en/ack/",
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/enable-cloud-monitor.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun cs GET /clusters/[cluster_id]/nodepools to verify nodepools.kubernetes_config.cms_enabled is set to true for all node pools.",
"CLI": "aliyun cs GET /clusters/<cluster_id>/nodepools --header 'Content-Type=application/json' | jq '.nodepools[].kubernetes_config.cms_enabled'",
"NativeIaC": "",
"Other": "",
"Other": "1. Log on to the **ACK Console**.\n2. Select the target cluster and click its name to open the cluster detail page.\n3. Select **Nodes** on the left column and click the **Monitor** link on the Actions column of the selected node.\n4. Verify that OS Metrics data exists in the CloudMonitor page.\n5. To enable: Click **Create Kubernetes Cluster** and set `CloudMonitor Agent` to **Enabled** under creation options.",
"Terraform": ""
},
"Recommendation": {
"Text": "1. Log on to the **ACK Console**\n2. Select the target cluster and click its name to open the cluster detail page\n3. Select **Nodes** on the left column and click the **Monitor** link on the Actions column of the selected node\n4. Verify that OS Metrics data exists in the CloudMonitor page\n5. To enable: Click **Create Kubernetes Cluster** and set `CloudMonitor Agent` to **Enabled** under creation options",
"Text": "Enable the **CloudMonitor** agent during cluster creation by setting `CloudMonitor Agent` to **Enabled**. For existing clusters, verify that `cms_enabled` is set to `true` for all node pools.",
"Url": "https://hub.prowler.com/check/cs_kubernetes_cloudmonitor_enabled"
}
},
@@ -1,32 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "cs_kubernetes_cluster_check_recent",
"CheckTitle": "Cluster Check triggered within configured period for Kubernetes Clusters",
"CheckType": [
"Threat detection during container runtime"
],
"CheckTitle": "Kubernetes cluster health check has been triggered within the configured period",
"CheckType": [],
"ServiceName": "cs",
"SubServiceName": "",
"ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "AlibabaCloudKubernetesCluster",
"ResourceType": "ALIYUN::CS::ManagedKubernetesCluster",
"ResourceGroup": "container",
"Description": "**Kubernetes Engine's cluster check** feature helps you verify the system nodes and components healthy status.\n\nWhen you trigger the checking, the process validates the health state of each node in your cluster and also the cluster configuration (`kubelet`, `docker daemon`, `kernel`, and network `iptables` configuration). If there are consecutive health check failures, the diagnose reports to admin for further repair.",
"Risk": "Kubernetes Engine uses the node's health status to determine if a node needs to be repaired. A cluster health check includes: cloud resource healthy status including **VPC/VSwitch**, **SLB**, and every **ECS node** status in the cluster; the `kubelet`, `docker daemon`, `kernel`, `iptables` configurations on every node.\n\nWithout regular cluster checks, potential issues may go undetected and could lead to **cluster instability** or **security vulnerabilities**.",
"Description": "**Alibaba Cloud Kubernetes Engine** provides a cluster health check feature that validates the health state of each node and verifies the cluster configuration, including `kubelet`, `docker daemon`, `kernel`, and network `iptables` settings. Running these checks regularly ensures that cloud resources such as **VPC/VSwitch**, **SLB**, and every **ECS node** are functioning correctly. If consecutive health check failures are detected, diagnostic reports are generated for administrators to take corrective action.",
"Risk": "Without regular cluster health checks within the configured period, potential issues such as **node failures**, **misconfigured network rules**, or **degraded system components** may go undetected. This increases the risk of **cluster instability**, **service outages**, and **security vulnerabilities** that could be exploited by attackers. Delayed detection of unhealthy nodes or misconfigured components can also impact the **integrity** and **availability** of workloads running on the cluster.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://help.aliyun.com/document_detail/114882.html",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/cluster-check.html"
"https://www.alibabacloud.com/help/en/ack/",
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/cluster-check.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun cs GET /clusters/[cluster_id]/checks to verify cluster checks are being run regularly. Trigger a check if needed.",
"CLI": "aliyun cs GET /clusters/<cluster_id>/checks --header 'Content-Type=application/json'",
"NativeIaC": "",
"Other": "",
"Other": "1. Log on to the **ACK Console**.\n2. Select the target cluster and open the **More** pop-menu for advanced options.\n3. Select **Global Check** and click the **Start** button to trigger the checking.\n4. Verify the checking time and details in Global Check.\n5. It is recommended to trigger cluster checks at least once within the configured period.",
"Terraform": ""
},
"Recommendation": {
"Text": "1. Log on to the **ACK Console**\n2. Select the target cluster and open the **More** pop-menu for advanced options\n3. Select **Global Check** and click the **Start** button to trigger the checking\n4. Verify the checking time and details in Global Check\n5. It is recommended to trigger cluster checks at least once within the configured period (default: weekly)",
"Text": "Trigger a cluster health check regularly within the configured period to ensure all nodes and system components are healthy. Use the **Global Check** feature in the ACK Console or the `aliyun cs` CLI to verify and trigger checks.",
"Url": "https://hub.prowler.com/check/cs_kubernetes_cluster_check_recent"
}
},
@@ -1,32 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "cs_kubernetes_cluster_check_weekly",
"CheckTitle": "Cluster Check triggered at least once per week for Kubernetes Clusters",
"CheckType": [
"Threat detection during container runtime"
],
"CheckTitle": "Kubernetes cluster health check has been triggered at least once per week",
"CheckType": [],
"ServiceName": "cs",
"SubServiceName": "",
"ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "AlibabaCloudKubernetesCluster",
"ResourceType": "ALIYUN::CS::ManagedKubernetesCluster",
"ResourceGroup": "container",
"Description": "**Kubernetes Engine's cluster check** feature helps you verify the system nodes and components healthy status.\n\nWhen you trigger the checking, the process validates the health state of each node in your cluster and also the cluster configuration (`kubelet`, `docker daemon`, `kernel`, and network `iptables` configuration). If there are consecutive health check failures, the diagnose reports to admin for further repair.",
"Risk": "Kubernetes Engine uses the node's health status to determine if a node needs to be repaired. A cluster health check includes: cloud resource healthy status including **VPC/VSwitch**, **SLB**, and every **ECS node** status in the cluster; the `kubelet`, `docker daemon`, `kernel`, `iptables` configurations on every node.\n\nWithout regular cluster checks, potential issues may go undetected and could lead to **cluster instability** or **security vulnerabilities**.",
"Description": "**Alibaba Cloud Kubernetes Engine** provides a cluster health check feature that validates the health state of each node and verifies the cluster configuration, including `kubelet`, `docker daemon`, `kernel`, and network `iptables` settings. Running these checks at least once per week ensures that cloud resources such as **VPC/VSwitch**, **SLB**, and every **ECS node** are functioning correctly. If consecutive health check failures are detected, diagnostic reports are generated for administrators to take corrective action.",
"Risk": "Without weekly cluster health checks, potential issues such as **node failures**, **misconfigured network rules**, or **degraded system components** may go undetected for extended periods. This increases the risk of **cluster instability**, **service outages**, and **security vulnerabilities** that could be exploited by attackers. Delayed detection of unhealthy nodes or misconfigured components can also impact the **integrity** and **availability** of workloads running on the cluster.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://help.aliyun.com/document_detail/114882.html",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/cluster-check.html"
"https://www.alibabacloud.com/help/en/ack/",
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/cluster-check.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun cs GET /clusters/[cluster_id]/checks to verify cluster checks are being run regularly. Trigger a check if needed.",
"CLI": "aliyun cs GET /clusters/<cluster_id>/checks --header 'Content-Type=application/json'",
"NativeIaC": "",
"Other": "",
"Other": "1. Log on to the **ACK Console**.\n2. Select the target cluster and open the **More** pop-menu for advanced options.\n3. Select **Global Check** and click the **Start** button to trigger the checking.\n4. Verify the checking time and details in Global Check.\n5. Trigger cluster checks at least once per week.",
"Terraform": ""
},
"Recommendation": {
"Text": "1. Log on to the **ACK Console**\n2. Select the target cluster and open the **More** pop-menu for advanced options\n3. Select **Global Check** and click the **Start** button to trigger the checking\n4. Verify the checking time and details in Global Check\n5. It is recommended to trigger cluster checks at least once per week",
"Text": "Trigger a cluster health check at least once per week to ensure all nodes and system components are healthy. Use the **Global Check** feature in the ACK Console or the `aliyun cs` CLI to verify and trigger checks.",
"Url": "https://hub.prowler.com/check/cs_kubernetes_cluster_check_weekly"
}
},
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "cs_kubernetes_dashboard_disabled",
"CheckTitle": "Kubernetes web UI / Dashboard is not enabled",
"CheckType": [
"Threat detection during container runtime",
"Unusual logon"
],
"CheckTitle": "Kubernetes web UI (Dashboard) is disabled on Kubernetes Engine clusters",
"CheckType": [],
"ServiceName": "cs",
"SubServiceName": "",
"ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "AlibabaCloudKubernetesCluster",
"ResourceType": "ALIYUN::CS::ManagedKubernetesCluster",
"ResourceGroup": "container",
"Description": "**Dashboard** is a web-based Kubernetes user interface that can be used to deploy containerized applications to a Kubernetes cluster, troubleshoot your containerized application, and manage the cluster itself.\n\nYou should disable the **Kubernetes Web UI (Dashboard)** when running on Kubernetes Engine. The Dashboard is backed by a highly privileged Kubernetes Service Account. It is recommended to use the **ACK User Console** instead to avoid privilege escalation via a compromised dashboard.",
"Risk": "The **Kubernetes Dashboard** is backed by a highly privileged Service Account. If the Dashboard is compromised, it could allow an attacker to gain **full control** over the cluster and potentially **escalate privileges**.\n\nAttackers who gain access to the Dashboard can deploy malicious workloads, exfiltrate secrets, and compromise the entire cluster.",
"Description": "**Alibaba Cloud Kubernetes Engine** clusters should not have the **Kubernetes Dashboard** (web UI) enabled. The Dashboard is backed by a highly privileged Kubernetes Service Account that can perform administrative operations across the cluster. It is recommended to use the **ACK Console** instead, which provides fine-grained access control through RAM policies and RBAC integration, avoiding the risk of privilege escalation through a compromised dashboard.",
"Risk": "The **Kubernetes Dashboard** is backed by a highly privileged Service Account that grants broad access to cluster resources. If the Dashboard is compromised through a vulnerability or unauthorized access, an attacker could gain **full control** over the cluster, deploy malicious workloads, exfiltrate **secrets**, and **escalate privileges**. This directly impacts the **confidentiality**, **integrity**, and **availability** of all workloads and data within the cluster.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/86494.html",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/disable-kubernetes-dashboard.html"
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/disable-kubernetes-dashboard.html"
],
"Remediation": {
"Code": {
"CLI": "Use kubectl to delete the dashboard deployment: kubectl delete deployment kubernetes-dashboard -n kube-system",
"CLI": "kubectl delete deployment kubernetes-dashboard -n kube-system",
"NativeIaC": "",
"Other": "",
"Other": "1. Log on to the **ACK Console**.\n2. Select the target cluster and select the `kube-system` namespace in the Namespace pop-menu.\n3. Input `dashboard` in the deploy filter bar.\n4. Make sure there is no result after the filter.\n5. If dashboard exists, delete the deployment by selecting **Delete** in the More pop-menu.",
"Terraform": ""
},
"Recommendation": {
"Text": "1. Log on to the **ACK Console**\n2. Select the target cluster and select the `kube-system` namespace in the Namespace pop-menu\n3. Input `dashboard` in the deploy filter bar\n4. Make sure there is no result after the filter\n5. If dashboard exists, delete the deployment by selecting **Delete** in the More pop-menu",
"Text": "Delete the Kubernetes Dashboard deployment from the `kube-system` namespace using `kubectl` or the ACK Console. Use the **ACK Console** for cluster management instead of the Kubernetes Dashboard.",
"Url": "https://hub.prowler.com/check/cs_kubernetes_dashboard_disabled"
}
},
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "cs_kubernetes_eni_multiple_ip_enabled",
"CheckTitle": "ENI multiple IP mode support for Kubernetes Cluster",
"CheckType": [
"Threat detection during container runtime",
"Suspicious network connection"
],
"CheckTitle": "Kubernetes cluster has ENI multiple IP mode enabled",
"CheckType": [],
"ServiceName": "cs",
"SubServiceName": "",
"ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "AlibabaCloudKubernetesCluster",
"ResourceType": "ALIYUN::CS::ManagedKubernetesCluster",
"ResourceGroup": "container",
"Description": "Alibaba Cloud **ENI (Elastic Network Interface)** supports assigning ranges of internal IP addresses as aliases to a single virtual machine's ENI network interfaces.\n\nWith **ENI multiple IP mode**, Kubernetes Engine clusters can allocate IP addresses from a CIDR block known to **Terway** network plugin. This makes your cluster more scalable and allows better interaction with other Alibaba Cloud products.",
"Risk": "Without **ENI multiple IP mode** (provided by Terway), pods share the node's network interface in a less scalable way.\n\nUsing ENI multiple IPs allows pod IPs to be reserved within the network ahead of time, preventing conflict with other compute resources, and allows firewall controls for Pods to be applied separately from their nodes.",
"Description": "**Alibaba Cloud Elastic Network Interface (ENI)** supports assigning multiple IP addresses to a single virtual machine's network interface. With **ENI multiple IP mode** provided by the **Terway** network plugin, Kubernetes Engine clusters can allocate pod IP addresses from the VPC CIDR block, enabling better scalability and native integration with other Alibaba Cloud services. This mode allows pods to have their own security group associations, providing granular network-level access control independently from their host nodes.",
"Risk": "Without **ENI multiple IP mode** (provided by the **Terway** network plugin), pods share the node's network interface in a less scalable manner and cannot have independent security group associations. This limits the ability to apply **granular firewall controls** at the pod level, increasing the risk of **lateral movement** if a pod is compromised. The inability to isolate pod networking from node networking weakens **network segmentation** and reduces the overall **security posture** of the cluster.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/en/ack/ack-managed-and-ack-dedicated/user-guide/associate-multiple-security-groups-with-an-eni",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/enable-multi-ip-mode.html"
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/enable-multi-ip-mode.html"
],
"Remediation": {
"Code": {
"CLI": "Terway network plugin must be selected during cluster creation to support ENI multiple IP mode.",
"CLI": "aliyun cs GET /clusters/<cluster_id> --header 'Content-Type=application/json' | jq '.parameters.Network'",
"NativeIaC": "",
"Other": "",
"Other": "1. When creating a new cluster in the **ACK Console**, select **Terway** in the `Network Plugin` option to enable ENI multiple IP mode support.\n2. Note that existing clusters using **Flannel** cannot be migrated to **Terway**.",
"Terraform": ""
},
"Recommendation": {
"Text": "When creating a new cluster, select **Terway** in the `Network Plugin` option to enable ENI multiple IP mode support.\n\n**Note:** Existing clusters using Flannel cannot be migrated to Terway.",
"Text": "Select the **Terway** network plugin during cluster creation to enable ENI multiple IP mode. Existing clusters using **Flannel** cannot be migrated to Terway and must be recreated.",
"Url": "https://hub.prowler.com/check/cs_kubernetes_eni_multiple_ip_enabled"
}
},
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "cs_kubernetes_log_service_enabled",
"CheckTitle": "Log Service is set to Enabled on Kubernetes Engine Clusters",
"CheckType": [
"Threat detection during container runtime"
],
"CheckTitle": "Kubernetes cluster has Log Service enabled",
"CheckType": [],
"ServiceName": "cs",
"SubServiceName": "",
"ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "AlibabaCloudKubernetesCluster",
"ResourceType": "ALIYUN::CS::ManagedKubernetesCluster",
"ResourceGroup": "container",
"Description": "**Log Service** is a complete real-time data logging service on Alibaba Cloud supporting collection, shipping, search, storage, and analysis for logs.\n\nLog Service can automatically collect, process, and store your container and audit logs in a dedicated, persistent datastore. Container logs are collected from your containers, audit logs from the `kube-apiserver` or deployed ingress, and events about cluster activity such as the deletion of Pods or Secrets.",
"Risk": "Without **Log Service** enabled, you lose visibility into container and system logs. The per-node logging agent collects: `kube-apiserver` audit logs, ingress visiting logs, and standard output/error logs from containerized processes.\n\nLack of logging makes **incident investigation**, **compliance auditing**, and **security monitoring** significantly more difficult.",
"Description": "**Alibaba Cloud Log Service** is a real-time data logging service that supports collection, shipping, search, storage, and analysis for container and audit logs in **Kubernetes Engine clusters**. When enabled, Log Service automatically collects `kube-apiserver` audit logs, ingress visiting logs, and standard output/error logs from containerized processes. These logs are stored in a dedicated, persistent datastore and are essential for operational visibility, security monitoring, and compliance auditing.",
"Risk": "Without **Log Service** enabled, there is no centralized collection of container logs, audit trails, or cluster events. This severely impairs the ability to perform **incident investigation**, **compliance auditing**, and **security monitoring**. Attackers could operate undetected within the cluster, as there would be no audit trail of API server calls, pod lifecycle events, or container output. The lack of logging directly impacts the **confidentiality** and **integrity** of the cluster by removing a critical detection and forensic capability.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://help.aliyun.com/document_detail/91406.html",
"https://help.aliyun.com/document_detail/86532.html",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/enable-log-service.html"
"https://www.alibabacloud.com/help/en/ack/",
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/enable-log-service.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun cs GET /clusters/[cluster_id] to verify AuditProjectName is set. When creating a new cluster, set Enable Log Service to Enabled.",
"CLI": "aliyun cs GET /clusters/<cluster_id> --header 'Content-Type=application/json' | jq '.meta_data' | jq -r 'fromjson | .AuditProjectName'",
"NativeIaC": "",
"Other": "",
"Other": "1. Log on to the **ACK Console**.\n2. Select the target cluster and click its name to open the cluster detail page.\n3. Select **Cluster Auditing** on the left column and check if the audit page is shown.\n4. To enable: When creating a new cluster, set `Enable Log Service` to **Enabled**.",
"Terraform": ""
},
"Recommendation": {
"Text": "1. Log on to the **ACK Console**\n2. Select the target cluster and click its name to open the cluster detail page\n3. Select **Cluster Auditing** on the left column and check if the audit page is shown\n4. To enable: When creating a new cluster, set `Enable Log Service` to **Enabled**",
"Text": "Enable **Log Service** during cluster creation by setting `Enable Log Service` to **Enabled**. For existing clusters, verify that `AuditProjectName` is configured in the cluster metadata.",
"Url": "https://hub.prowler.com/check/cs_kubernetes_log_service_enabled"
}
},
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "cs_kubernetes_network_policy_enabled",
"CheckTitle": "Network policy is enabled on Kubernetes Engine Clusters",
"CheckType": [
"Threat detection during container runtime",
"Suspicious network connection"
],
"CheckTitle": "Kubernetes cluster has Network policy enabled",
"CheckType": [],
"ServiceName": "cs",
"SubServiceName": "",
"ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "AlibabaCloudKubernetesCluster",
"ResourceType": "ALIYUN::CS::ManagedKubernetesCluster",
"ResourceGroup": "container",
"Description": "A **Network Policy** is a specification of how groups of pods are allowed to communicate with each other and other network endpoints.\n\n`NetworkPolicy` resources use labels to select pods and define rules which specify what traffic is allowed. By default, pods are non-isolated and accept traffic from any source. Pods become isolated by having a NetworkPolicy that selects them.",
"Risk": "Without **Network Policies**, all pods in a Kubernetes cluster can communicate with each other freely. This open communication model allows an attacker who compromises a single pod to potentially move **laterally** within the cluster and access sensitive services or data.\n\nNetwork Policies are essential for implementing **defense in depth** and **least privilege** networking.",
"Description": "**Alibaba Cloud Kubernetes Engine** clusters should have **Network Policy** support enabled through the **Terway** network plugin. A `NetworkPolicy` is a Kubernetes specification that defines how groups of pods are allowed to communicate with each other and other network endpoints using label-based selection rules. By default, pods are non-isolated and accept traffic from any source; applying NetworkPolicy resources restricts traffic to only explicitly allowed connections, enforcing the principle of least privilege at the network level.",
"Risk": "Without **Network Policies**, all pods in a Kubernetes cluster can communicate with each other freely, creating an unrestricted flat network. This allows an attacker who compromises a single pod to move **laterally** within the cluster, accessing sensitive services, databases, and secrets without restriction. The absence of network segmentation undermines **defense in depth** and increases the blast radius of any compromise, directly impacting the **confidentiality** and **integrity** of workloads.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://help.aliyun.com/document_detail/97621.html",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/enable-network-policy-support.html"
"https://www.alibabacloud.com/help/en/ack/",
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/enable-network-policy-support.html"
],
"Remediation": {
"Code": {
"CLI": "Network Policy support (Terway) must be selected during cluster creation.",
"CLI": "aliyun cs GET /clusters/<cluster_id> --header 'Content-Type=application/json' | jq '.parameters.Network'",
"NativeIaC": "",
"Other": "",
"Other": "1. When creating a new cluster in the **ACK Console**, select **Terway** in the `Network Plugin` option to enable Network Policy support.\n2. Note that existing clusters using **Flannel** cannot be migrated to **Terway**.",
"Terraform": ""
},
"Recommendation": {
"Text": "Only the **Terway** network plugin supports the Network Policy feature. When creating a new cluster, select **Terway** in the `Network Plugin` option.\n\n**Note:** Existing clusters using Flannel cannot be migrated to Terway.",
"Text": "Select the **Terway** network plugin during cluster creation to enable Network Policy support. Existing clusters using **Flannel** cannot be migrated to Terway and must be recreated.",
"Url": "https://hub.prowler.com/check/cs_kubernetes_network_policy_enabled"
}
},
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "cs_kubernetes_private_cluster_enabled",
"CheckTitle": "Kubernetes Cluster is created with Private cluster enabled",
"CheckType": [
"Threat detection during container runtime",
"Unusual logon"
],
"CheckTitle": "Kubernetes cluster is created with private cluster enabled",
"CheckType": [],
"ServiceName": "cs",
"SubServiceName": "",
"ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "AlibabaCloudKubernetesCluster",
"ResourceType": "ALIYUN::CS::ManagedKubernetesCluster",
"ResourceGroup": "container",
"Description": "A **private cluster** is a cluster that makes your master inaccessible from the public internet.\n\nIn a private cluster, nodes do not have public IP addresses, so your workloads run in an environment that is isolated from the internet. Nodes and masters communicate with each other privately using **VPC peering**.",
"Risk": "Exposing the **API server endpoint** to the public internet increases the attack surface of your cluster. Attackers can attempt to probe for vulnerabilities, perform **brute force attacks**, or exploit misconfigurations if the API server is publicly accessible.\n\nUsing a private cluster significantly reduces network security risks.",
"Description": "**Alibaba Cloud Kubernetes Engine** clusters should be configured as **private clusters** to ensure that the API server endpoint is not accessible from the public internet. In a private cluster, nodes do not have public IP addresses and all communication between nodes and the master occurs privately through **VPC peering**. This significantly reduces the attack surface by eliminating direct internet exposure of the cluster's control plane and worker nodes.",
"Risk": "Exposing the **API server endpoint** to the public internet increases the attack surface of the cluster, allowing attackers to probe for vulnerabilities, perform **brute force attacks** against authentication, or exploit misconfigurations. A publicly accessible API server can be targeted by automated scanning tools and botnets, increasing the risk of unauthorized access. This directly impacts the **confidentiality** and **integrity** of the cluster by potentially allowing attackers to execute commands, deploy malicious workloads, or exfiltrate sensitive data.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://help.aliyun.com/document_detail/100380.html",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/private-cluster.html"
"https://www.alibabacloud.com/help/en/ack/",
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/private-cluster.html"
],
"Remediation": {
"Code": {
"CLI": "Public access settings cannot be easily changed for existing clusters. Ensure Public Access is disabled during creation.",
"CLI": "aliyun cs GET /clusters/<cluster_id> --header 'Content-Type=application/json' | jq '.external_loadbalancer_id'",
"NativeIaC": "",
"Other": "",
"Other": "1. Log on to the **ACK Console**.\n2. Select the target cluster name and go to the cluster detail page.\n3. Check if there is no `API Server Public Network Endpoint` under Cluster Information.\n4. When creating a new cluster, make sure **Public Access** is not enabled.",
"Terraform": ""
},
"Recommendation": {
"Text": "1. Log on to the **ACK Console**\n2. Select the target cluster name and go to the cluster detail page\n3. Check if there is no `API Server Public Network Endpoint` under Cluster Information\n4. When creating a new cluster, make sure **Public Access** is not enabled",
"Text": "Disable **Public Access** during cluster creation to ensure the API server is not exposed to the public internet. For existing clusters, remove the public endpoint if one was configured.",
"Url": "https://hub.prowler.com/check/cs_kubernetes_private_cluster_enabled"
}
},
@@ -1,34 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "cs_kubernetes_rbac_enabled",
"CheckTitle": "Role-based access control (RBAC) authorization is Enabled on Kubernetes Engine Clusters",
"CheckType": [
"Threat detection during container runtime",
"Abnormal account"
],
"CheckTitle": "Kubernetes cluster has RBAC authorization enabled",
"CheckType": [],
"ServiceName": "cs",
"SubServiceName": "",
"ResourceIdTemplate": "acs:cs:region:account-id:cluster/{cluster-id}",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "AlibabaCloudKubernetesCluster",
"ResourceType": "ALIYUN::CS::ManagedKubernetesCluster",
"ResourceGroup": "container",
"Description": "In Kubernetes, authorizers interact by granting a permission if any authorizer grants the permission. The legacy authorizer in Kubernetes Engine grants broad, statically defined permissions.\n\nTo ensure that **RBAC** limits permissions correctly, you must disable the legacy authorizer. RBAC has significant security advantages, helps ensure that users only have access to specific cluster resources within their own namespace, and is now stable in Kubernetes.",
"Risk": "In Kubernetes, **RBAC** is used to grant permissions to resources at the cluster and namespace level. RBAC allows you to define roles with rules containing a set of permissions.\n\nWithout RBAC, legacy authorization mechanisms like **ABAC** grant **overly broad permissions**, increasing the risk of unauthorized access and privilege escalation.",
"Description": "**Alibaba Cloud Kubernetes Engine** clusters should have **Role-Based Access Control (RBAC)** enabled to enforce fine-grained authorization for cluster resources. RBAC allows administrators to define roles with specific permissions at both the cluster and namespace level, ensuring that users and service accounts only have access to the resources they need. The legacy **ABAC** (Attribute-Based Access Control) authorizer grants broad, statically defined permissions and should be disabled in favor of RBAC for improved security.",
"Risk": "Without **RBAC** enabled, Kubernetes clusters may rely on legacy authorization mechanisms such as **ABAC**, which grant **overly broad permissions** that cannot be scoped to specific namespaces or resource types. This increases the risk of **unauthorized access** and **privilege escalation**, where a compromised service account or user could gain access to sensitive resources across the entire cluster. The lack of granular access control directly impacts the **confidentiality** and **integrity** of workloads and secrets stored in the cluster.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://help.aliyun.com/document_detail/87656.html",
"https://help.aliyun.com/document_detail/119596.html",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/enable-rbac-authorization.html"
"https://www.alibabacloud.com/help/en/ack/",
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ACK/enable-rbac-authorization.html"
],
"Remediation": {
"Code": {
"CLI": "RBAC is enabled by default on new ACK clusters. Verify cluster authorization configuration.",
"CLI": "aliyun cs GET /clusters/<cluster_id> --header 'Content-Type=application/json' | jq '.parameters.KubernetesVersion'",
"NativeIaC": "",
"Other": "",
"Other": "1. Log on to the **ACK Console**.\n2. Navigate to **Clusters** -> **Authorizations** page.\n3. Select the target RAM sub-account and configure the RBAC roles on specific clusters or namespaces.\n4. Ensure **RBAC** is enabled and legacy ABAC authorization is disabled.",
"Terraform": ""
},
"Recommendation": {
"Text": "1. Log on to the **ACK Console**\n2. Navigate to **Clusters** -> **Authorizations** page\n3. Select the target RAM sub-account and configure the RBAC roles on specific clusters or namespaces\n4. Ensure **RBAC** is enabled and legacy ABAC authorization is disabled",
"Text": "Ensure **RBAC** is enabled on all Kubernetes Engine clusters and that legacy **ABAC** authorization is disabled. Configure RBAC roles and bindings through the ACK Console Authorizations page to enforce least-privilege access.",
"Url": "https://hub.prowler.com/check/cs_kubernetes_rbac_enabled"
}
},
@@ -1,32 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "ecs_attached_disk_encrypted",
"CheckTitle": "Virtual Machines disk are encrypted",
"CheckType": [
"Sensitive file tampering"
],
"CheckTitle": "ECS attached disk is encrypted",
"CheckType": [],
"ServiceName": "ecs",
"SubServiceName": "",
"ResourceIdTemplate": "acs:ecs:region:account-id:disk/{disk-id}",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "AlibabaCloudECSDisk",
"ResourceType": "ALIYUN::ECS::Disk",
"ResourceGroup": "storage",
"Description": "**ECS cloud disk encryption** protects your data at rest. The cloud disk data encryption feature automatically encrypts data when data is transferred from ECS instances to disks, and decrypts data when read from disks.\n\nEnsure that disks are encrypted when they are created with the creation of VM instances.",
"Risk": "**Unencrypted disks** attached to ECS instances pose a security risk as they may contain sensitive data that could be accessed if the disk is compromised or accessed by unauthorized parties.\n\nData at rest without encryption is vulnerable to **unauthorized access** if storage media is lost, stolen, or improperly decommissioned.",
"Description": "**Alibaba Cloud ECS cloud disk encryption** protects data at rest by automatically encrypting data when it is transferred from ECS instances to disks and decrypting it when read. Ensuring that all attached disks are encrypted helps prevent unauthorized access to sensitive data stored on the disk. This check verifies that **disk encryption** is enabled on all ECS disks attached to instances, using **KMS** (Key Management Service) for key management.",
"Risk": "**Unencrypted disks** attached to ECS instances pose a significant security risk, as sensitive data could be exposed if the disk is compromised, improperly decommissioned, or accessed by unauthorized parties. Data at rest without encryption is vulnerable to **unauthorized access**, impacting **confidentiality** and potentially leading to **data breaches** or **regulatory non-compliance**.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/59643.htm",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ECS/encrypt-vm-instance-disks.html"
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ECS/encrypt-vm-instance-disks.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ecs CreateDisk --DiskName <disk_name> --Size <size> --Encrypted true --KmsKeyId <kms_key_id>",
"NativeIaC": "",
"Other": "",
"Other": "1. Log on to the **ECS Console** > **Instances & Images** > **Images**.\n2. Select the **Custom Image** tab and select the target image.\n3. Click **Copy Image** and check the **Encrypt** box.\n4. Select a key and click **OK**.\n5. For data disks, go to **Instances** > **Create Instance**, in the Storage section click **Add Disk**, select **Disk Encryption**, and choose a key.\n\n**Note:** You cannot directly convert unencrypted disks to encrypted disks.",
"Terraform": "resource \"alicloud_ecs_disk\" \"encrypted\" {\n zone_id = \"cn-hangzhou-a\"\n disk_name = \"encrypted-disk\"\n category = \"cloud_efficiency\"\n size = 20\n encrypted = true\n kms_key_id = alicloud_kms_key.example.id\n}"
},
"Recommendation": {
"Text": "**Encrypt a system disk when copying an image:**\n1. Log on to the **ECS Console** > **Instances & Images** > **Images**\n2. Select the **Custom Image** tab and select target image\n3. Click **Copy Image** and check the **Encrypt** box\n4. Select a key and click **OK**\n\n**Encrypt a data disk when creating an instance:**\n1. Log on to the **ECS Console** > **Instances & Images** > **Instances** > **Create Instance**\n2. In the Storage section, click **Add Disk**\n3. Select **Disk Encryption** and choose a key\n\n**Note:** You cannot directly convert unencrypted disks to encrypted disks.",
"Text": "Enable encryption on all ECS disks to protect data at rest. Use KMS-managed keys for encryption. Note that existing unencrypted disks cannot be directly converted; data must be migrated to new encrypted disks.",
"Url": "https://hub.prowler.com/check/ecs_attached_disk_encrypted"
}
},
@@ -1,35 +1,29 @@
{
"Provider": "alibabacloud",
"CheckID": "ecs_instance_endpoint_protection_installed",
"CheckTitle": "The endpoint protection for all Virtual Machines is installed",
"CheckType": [
"Suspicious process",
"Webshell",
"Unusual logon",
"Sensitive file tampering",
"Malicious software"
],
"CheckTitle": "ECS instance has endpoint protection installed",
"CheckType": [],
"ServiceName": "ecs",
"SubServiceName": "",
"ResourceIdTemplate": "acs:ecs:region:account-id:instance/{instance-id}",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "AlibabaCloudECSInstance",
"ResourceType": "ALIYUN::ECS::Instance",
"ResourceGroup": "compute",
"Description": "Installing **endpoint protection systems** (like **Security Center** for Alibaba Cloud) provides real-time protection capability that helps identify and remove viruses, spyware, and other malicious software.\n\nConfigurable alerts notify when known malicious software attempts to install itself or run on ECS instances.",
"Risk": "ECS instances without **endpoint protection** are vulnerable to **malware**, **viruses**, and other security threats.\n\nEndpoint protection provides real-time monitoring and protection capabilities essential for detecting and preventing security incidents.",
"Description": "**Alibaba Cloud Security Center** provides endpoint protection for ECS instances, offering real-time detection and removal of viruses, spyware, and other malicious software. This check verifies that the **Security Center agent** is installed and active on all ECS instances, ensuring configurable alerts notify administrators when known malicious software attempts to install itself or execute on the instance.",
"Risk": "ECS instances without **endpoint protection** are vulnerable to **malware**, **viruses**, **webshells**, and other security threats that can compromise **confidentiality**, **integrity**, and **availability**. Without real-time monitoring, security incidents may go undetected, allowing attackers to maintain persistent access and exfiltrate sensitive data.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ECS/enable-endpoint-protection.html"
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ECS/enable-endpoint-protection.html"
],
"Remediation": {
"Code": {
"CLI": "Logon to Security Center Console > Select Settings > Click Agent > Select virtual machines without Security Center agent > Click Install",
"CLI": "aliyun sas InstallBackupClient --Uuid <instance_uuid>",
"NativeIaC": "",
"Other": "",
"Other": "1. Log on to the **Security Center Console**.\n2. Select **Settings**.\n3. Click **Agent**.\n4. On the Agent tab, select the virtual machines without Security Center agent installed.\n5. Click **Install**.",
"Terraform": ""
},
"Recommendation": {
"Text": "1. Log on to the **Security Center Console**\n2. Select **Settings**\n3. Click **Agent**\n4. On the Agent tab, select the virtual machines without Security Center agent installed\n5. Click **Install**",
"Text": "Install the Alibaba Cloud **Security Center** agent on all ECS instances to enable real-time endpoint protection, malware detection, and vulnerability scanning.",
"Url": "https://hub.prowler.com/check/ecs_instance_endpoint_protection_installed"
}
},
@@ -1,32 +1,29 @@
{
"Provider": "alibabacloud",
"CheckID": "ecs_instance_latest_os_patches_applied",
"CheckTitle": "The latest OS Patches for all Virtual Machines are applied",
"CheckType": [
"Malicious software",
"Web application threat detection"
],
"CheckTitle": "ECS instance has latest OS patches applied",
"CheckType": [],
"ServiceName": "ecs",
"SubServiceName": "",
"ResourceIdTemplate": "acs:ecs:region:account-id:instance/{instance-id}",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "AlibabaCloudECSInstance",
"ResourceType": "ALIYUN::ECS::Instance",
"ResourceGroup": "compute",
"Description": "Windows and Linux virtual machines should be kept updated to address specific bugs or flaws, improve OS or application's general stability, and fix **security vulnerabilities**.\n\nThe Alibaba Cloud **Security Center** checks for the latest updates in Linux and Windows systems.",
"Risk": "**Unpatched systems** are vulnerable to known security exploits and may be compromised by attackers.\n\nKeeping systems updated with the latest patches is critical for maintaining security and preventing **exploitation of known vulnerabilities**.",
"Description": "**Alibaba Cloud Security Center** checks for the latest updates in Linux and Windows systems running on ECS instances. Keeping virtual machines updated with the latest OS patches addresses specific bugs, improves general stability, and fixes **security vulnerabilities**. This check verifies that all known vulnerabilities detected by Security Center have been patched on each ECS instance.",
"Risk": "**Unpatched systems** are vulnerable to known security exploits and can be compromised by attackers leveraging publicly disclosed vulnerabilities. Failure to apply patches in a timely manner increases the risk of **unauthorized access**, **malware infection**, and **data breaches**, impacting the **confidentiality**, **integrity**, and **availability** of the system.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ECS/apply-latest-os-patches.html"
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ECS/apply-latest-os-patches.html"
],
"Remediation": {
"Code": {
"CLI": "Logon to Security Center Console > Select Vulnerabilities > Apply all patches for vulnerabilities",
"CLI": "aliyun sas FixCheckWarnings --CheckIds <check_ids>",
"NativeIaC": "",
"Other": "",
"Other": "1. Log on to the **Security Center Console**.\n2. Select **Vulnerabilities** in the left-side navigation pane.\n3. Review all detected vulnerabilities.\n4. Apply all available patches for the reported vulnerabilities.\n5. Verify that vulnerabilities are resolved after patching.",
"Terraform": ""
},
"Recommendation": {
"Text": "1. Log on to the **Security Center Console**\n2. Select **Vulnerabilities**\n3. Ensure all vulnerabilities are fixed\n4. Apply all patches for vulnerabilities",
"Text": "Regularly review and apply OS patches on all ECS instances using the **Alibaba Cloud Security Center** vulnerability management feature to maintain a strong security posture.",
"Url": "https://hub.prowler.com/check/ecs_instance_latest_os_patches_applied"
}
},
@@ -1,32 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "ecs_instance_no_legacy_network",
"CheckTitle": "Legacy networks does not exist",
"CheckType": [
"Suspicious network connection"
],
"CheckTitle": "ECS instance does not use legacy network",
"CheckType": [],
"ServiceName": "ecs",
"SubServiceName": "",
"ResourceIdTemplate": "acs:ecs:region:account-id:instance/{instance-id}",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "AlibabaCloudECSInstance",
"ResourceType": "ALIYUN::ECS::Instance",
"ResourceGroup": "compute",
"Description": "In order to prevent use of **legacy networks**, ECS instances should not have a legacy network configured.\n\nLegacy networks have a single network IPv4 prefix range and a single gateway IP address for the whole network. With legacy networks, you cannot create subnetworks or switch from legacy to auto or custom subnet networks.",
"Risk": "**Legacy networks** can have an impact on high network traffic ECS instances and are subject to a **single point of failure**.\n\nThey also lack the security isolation and network segmentation capabilities provided by **VPCs**.",
"Description": "**Alibaba Cloud ECS instances** should use **VPC (Virtual Private Cloud)** networks instead of legacy classic networks. Legacy networks have a single IPv4 prefix range and a single gateway IP address for the whole network, preventing the creation of subnetworks or migration to auto/custom subnet networks. This check verifies that no ECS instances are configured with a legacy network type.",
"Risk": "**Legacy networks** lack the security isolation and network segmentation capabilities provided by **VPCs**, creating a **single point of failure** for high-traffic instances. Without proper network segmentation, lateral movement by attackers becomes easier, impacting **confidentiality** and **integrity** of workloads sharing the same flat network.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/87190.htm",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-VPC/legacy-network-usage.html"
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-VPC/legacy-network-usage.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ecs CreateInstance --InstanceName <instance_name> --ImageId <image_id> --InstanceType <instance_type> --VSwitchId <vswitch_id>",
"NativeIaC": "",
"Other": "",
"Other": "1. Log on to the **ECS Console**.\n2. In the left-side navigation pane, choose **Instance & Image** > **Instances**.\n3. Click **Create Instance**.\n4. Specify the basic instance information and click **Next: Networking**.\n5. Select **VPC** as the Network Type and choose an appropriate VSwitch.",
"Terraform": ""
},
"Recommendation": {
"Text": "1. Log on to the **ECS Console**\n2. In the left-side navigation pane, choose **Instance & Image** > **Instances**\n3. Click **Create Instance**\n4. Specify the basic instance information required and click **Next: Networking**\n5. Select the Network Type of **VPC**",
"Text": "Migrate all ECS instances from legacy classic networks to **VPC** networks. Create new instances within a VPC and migrate workloads from legacy network instances.",
"Url": "https://hub.prowler.com/check/ecs_instance_no_legacy_network"
}
},
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "ecs_securitygroup_restrict_rdp_internet",
"CheckTitle": "RDP access is restricted from the internet",
"CheckType": [
"Unusual logon",
"Suspicious network connection"
],
"CheckTitle": "Security group restricts RDP access from the internet",
"CheckType": [],
"ServiceName": "ecs",
"SubServiceName": "",
"ResourceIdTemplate": "acs:ecs:region:account-id:security-group/{security-group-id}",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "AlibabaCloudECSSecurityGroup",
"ResourceType": "ALIYUN::ECS::SecurityGroup",
"ResourceGroup": "network",
"Description": "**Security groups** provide stateful filtering of ingress/egress network traffic to Alibaba Cloud resources.\n\nIt is recommended that no security group allows unrestricted ingress access to port **3389 (RDP)**.",
"Risk": "Removing unfettered connectivity to remote console services, such as **RDP**, reduces a server's exposure to risk.\n\nUnrestricted RDP access from the internet (`0.0.0.0/0`) exposes systems to **brute force attacks**, **credential stuffing**, and **exploitation of RDP vulnerabilities**.",
"Description": "**Alibaba Cloud ECS security groups** provide stateful filtering of ingress and egress network traffic to cloud resources. This check verifies that no security group allows unrestricted ingress access to port **3389** (RDP) from the internet (`0.0.0.0/0` or `::/0`). Restricting RDP access to trusted IP addresses significantly reduces the attack surface of ECS instances.",
"Risk": "Unrestricted **RDP access** from the internet (`0.0.0.0/0`) exposes systems to **brute force attacks**, **credential stuffing**, and **exploitation of RDP vulnerabilities** such as BlueKeep. This can lead to **unauthorized access**, **data exfiltration**, and full system compromise, impacting **confidentiality**, **integrity**, and **availability**.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/25387.htm",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ECS/unrestricted-rdp-access.html"
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ECS/unrestricted-rdp-access.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ecs RevokeSecurityGroup --SecurityGroupId <security_group_id> --IpProtocol tcp --PortRange 3389/3389 --SourceCidrIp 0.0.0.0/0",
"NativeIaC": "",
"Other": "",
"Other": "1. Log on to the **ECS Console**.\n2. In the left-side navigation pane, choose **Network & Security** > **Security Groups**.\n3. Find the target security group and click **Add Rules**.\n4. Locate the rule allowing port `3389` from `0.0.0.0/0`.\n5. Modify the Source IP range to a specific trusted IP or CIDR block.\n6. Click **Save**.",
"Terraform": "resource \"alicloud_security_group_rule\" \"deny_rdp_internet\" {\n type = \"ingress\"\n ip_protocol = \"tcp\"\n port_range = \"3389/3389\"\n security_group_id = alicloud_security_group.example.id\n cidr_ip = \"10.0.0.0/8\" # Restrict to internal network\n policy = \"accept\"\n}"
},
"Recommendation": {
"Text": "1. Log on to the **ECS Console**\n2. In the left-side navigation pane, choose **Network & Security** > **Security Groups**\n3. Find the Security Group you want to modify\n4. Modify Source IP range to specific IP instead of `0.0.0.0/0`\n5. Click **Save**",
"Text": "Restrict RDP (port **3389**) access in security groups to only trusted IP addresses or CIDR blocks. Remove any rules allowing access from `0.0.0.0/0` or `::/0`.",
"Url": "https://hub.prowler.com/check/ecs_securitygroup_restrict_rdp_internet"
}
},
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "ecs_securitygroup_restrict_ssh_internet",
"CheckTitle": "SSH access is restricted from the internet",
"CheckType": [
"Unusual logon",
"Suspicious network connection"
],
"CheckTitle": "Security group restricts SSH access from the internet",
"CheckType": [],
"ServiceName": "ecs",
"SubServiceName": "",
"ResourceIdTemplate": "acs:ecs:region:account-id:security-group/{security-group-id}",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "AlibabaCloudECSSecurityGroup",
"ResourceType": "ALIYUN::ECS::SecurityGroup",
"ResourceGroup": "network",
"Description": "**Security groups** provide stateful filtering of ingress/egress network traffic to Alibaba Cloud resources.\n\nIt is recommended that no security group allows unrestricted ingress access to port **22 (SSH)**.",
"Risk": "Removing unfettered connectivity to remote console services, such as **SSH**, reduces a server's exposure to risk.\n\nUnrestricted SSH access from the internet (`0.0.0.0/0`) exposes systems to **brute force attacks**, **credential stuffing**, and **exploitation of SSH vulnerabilities**.",
"Description": "**Alibaba Cloud ECS security groups** provide stateful filtering of ingress and egress network traffic to cloud resources. This check verifies that no security group allows unrestricted ingress access to port **22** (SSH) from the internet (`0.0.0.0/0` or `::/0`). Restricting SSH access to trusted IP addresses significantly reduces the attack surface of ECS instances.",
"Risk": "Unrestricted **SSH access** from the internet (`0.0.0.0/0`) exposes systems to **brute force attacks**, **credential stuffing**, and **exploitation of SSH vulnerabilities**. This can lead to **unauthorized access**, **data exfiltration**, and full system compromise, impacting **confidentiality**, **integrity**, and **availability**.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/25387.htm",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ECS/unrestricted-ssh-access.html"
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ECS/unrestricted-ssh-access.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ecs RevokeSecurityGroup --SecurityGroupId <security_group_id> --IpProtocol tcp --PortRange 22/22 --SourceCidrIp 0.0.0.0/0",
"NativeIaC": "",
"Other": "",
"Other": "1. Log on to the **ECS Console**.\n2. In the left-side navigation pane, choose **Network & Security** > **Security Groups**.\n3. Find the target security group and click **Add Rules**.\n4. Locate the rule allowing port `22` from `0.0.0.0/0`.\n5. Modify the Source IP range to a specific trusted IP or CIDR block.\n6. Click **Save**.",
"Terraform": "resource \"alicloud_security_group_rule\" \"deny_ssh_internet\" {\n type = \"ingress\"\n ip_protocol = \"tcp\"\n port_range = \"22/22\"\n security_group_id = alicloud_security_group.example.id\n cidr_ip = \"10.0.0.0/8\" # Restrict to internal network\n policy = \"accept\"\n}"
},
"Recommendation": {
"Text": "1. Log on to the **ECS Console**\n2. In the left-side navigation pane, choose **Network & Security** > **Security Groups**\n3. Find the Security Group you want to modify\n4. Modify Source IP range to specific IP instead of `0.0.0.0/0`\n5. Click **Save**",
"Text": "Restrict SSH (port **22**) access in security groups to only trusted IP addresses or CIDR blocks. Remove any rules allowing access from `0.0.0.0/0` or `::/0`.",
"Url": "https://hub.prowler.com/check/ecs_securitygroup_restrict_ssh_internet"
}
},
@@ -1,32 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "ecs_unattached_disk_encrypted",
"CheckTitle": "Unattached disks are encrypted",
"CheckType": [
"Sensitive file tampering"
],
"CheckTitle": "ECS unattached disk is encrypted",
"CheckType": [],
"ServiceName": "ecs",
"SubServiceName": "",
"ResourceIdTemplate": "acs:ecs:region:account-id:disk/{disk-id}",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "AlibabaCloudECSDisk",
"ResourceType": "ALIYUN::ECS::Disk",
"ResourceGroup": "storage",
"Description": "**Cloud disk encryption** protects your data at rest. The cloud disk data encryption feature automatically encrypts data when data is transferred from ECS instances to disks, and decrypts data when read from disks.",
"Risk": "**Unencrypted unattached disks** pose a security risk as they may contain sensitive data that could be accessed if the disk is compromised or accessed by unauthorized parties.\n\nUnattached disks are especially vulnerable as they may be forgotten or not monitored, increasing the risk of **unauthorized access**.",
"Description": "**Alibaba Cloud ECS cloud disk encryption** protects data at rest by automatically encrypting data when it is transferred from ECS instances to disks and decrypting it when read. This check verifies that unattached (detached) disks have encryption enabled, since unattached disks may still contain sensitive data from previous workloads and are especially vulnerable if not properly managed.",
"Risk": "**Unencrypted unattached disks** pose a significant security risk as they may contain sensitive data that could be accessed if the disk is compromised or accessed by unauthorized parties. Unattached disks are especially vulnerable as they may be overlooked in security monitoring, increasing the risk of **unauthorized access** and **data breaches** that impact **confidentiality**.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/59643.htm",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-ECS/encrypt-unattached-disks.html"
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-ECS/encrypt-unattached-disks.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ecs CreateDisk --DiskName <disk_name> --Size <size> --Encrypted true --KmsKeyId <kms_key_id>",
"NativeIaC": "",
"Other": "",
"Other": "1. Log on to the **ECS Console**.\n2. In the left-side navigation pane, choose **Storage & Snapshots** > **Disk**.\n3. In the upper-right corner of the Disks page, click **Create Disk**.\n4. In the Disk section, check the **Disk Encryption** box and select a key from the drop-down list.\n\n**Note:** After a data disk is created, you can only encrypt it by manually copying data from the unencrypted disk to a new encrypted disk.",
"Terraform": "resource \"alicloud_ecs_disk\" \"encrypted\" {\n zone_id = \"cn-hangzhou-a\"\n disk_name = \"encrypted-disk\"\n category = \"cloud_efficiency\"\n size = 20\n encrypted = true\n kms_key_id = alicloud_kms_key.example.id\n}"
},
"Recommendation": {
"Text": "1. Log on to the **ECS Console**\n2. In the left-side navigation pane, choose **Storage & Snapshots** > **Disk**\n3. In the upper-right corner of the Disks page, click **Create Disk**\n4. In the Disk section, check the **Disk Encryption** box and select a key from the drop-down list\n\n**Note:** After a data disk is created, you can only encrypt the data disk by manually copying data from the unencrypted disk to a new encrypted disk.",
"Text": "Ensure all unattached ECS disks are encrypted. Create new encrypted disks and migrate data from unencrypted disks, then delete the unencrypted originals.",
"Url": "https://hub.prowler.com/check/ecs_unattached_disk_encrypted"
}
},
@@ -2,32 +2,29 @@
"Provider": "alibabacloud",
"CheckID": "oss_bucket_logging_enabled",
"CheckTitle": "Logging is enabled for OSS buckets",
"CheckType": [
"Sensitive file tampering",
"Cloud threat detection"
],
"CheckType": [],
"ServiceName": "oss",
"SubServiceName": "",
"ResourceIdTemplate": "acs:oss::account-id:bucket-name",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "AlibabaCloudOSSBucket",
"ResourceType": "ALIYUN::OSS::Bucket",
"ResourceGroup": "storage",
"Description": "**OSS Bucket Access Logging** generates a log that contains access records for each request made to your OSS bucket.\n\nAn access log record contains details about the request, such as the request type, the resources specified in the request, and the time and date the request was processed. It is recommended that bucket access logging be enabled on OSS buckets.",
"Risk": "By enabling **OSS bucket logging** on target OSS buckets, it is possible to capture all events which may affect objects within target buckets.\n\nConfiguring logs to be placed in a separate bucket allows access to log information useful in **security** and **incident response** workflows.",
"Description": "**Alibaba Cloud OSS Bucket Access Logging** generates a log record for each request made to your OSS bucket, containing details such as the request type, the resources specified, and the time and date the request was processed. Enabling bucket access logging on all OSS buckets ensures that access patterns are recorded and available for security analysis and incident response workflows.",
"Risk": "Without **OSS bucket logging** enabled, access events affecting objects within target buckets are not captured. This limits the ability to perform **security analysis**, **incident response**, and **forensic investigations**, as there is no record of who accessed or modified stored data.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/31900.htm",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-OSS/enable-bucket-access-logging.html"
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-OSS/enable-bucket-access-logging.html"
],
"Remediation": {
"Code": {
"CLI": "ossutil logging --method put oss://<bucket-name> --target-bucket <target-bucket> --target-prefix <prefix>",
"NativeIaC": "",
"Other": "",
"Other": "1. Log on to the **OSS Console**\n2. In the bucket-list pane, click on a target OSS bucket\n3. Under **Log**, click **Configure**\n4. Click the **Enabled** checkbox\n5. Select `Target Bucket` from the list\n6. Enter a `Target Prefix`\n7. Click **Save**",
"Terraform": "resource \"alicloud_oss_bucket_logging\" \"example\" {\n bucket = alicloud_oss_bucket.example.bucket\n target_bucket = alicloud_oss_bucket.log_bucket.bucket\n target_prefix = \"log/\"\n}"
},
"Recommendation": {
"Text": "1. Log on to the **OSS Console**\n2. In the bucket-list pane, click on a target OSS bucket\n3. Under **Log**, click **Configure**\n4. Click the **Enabled** checkbox\n5. Select `Target Bucket` from the list\n6. Enter a `Target Prefix`\n7. Click **Save**",
"Text": "Enable access logging on all OSS buckets and configure logs to be stored in a separate dedicated bucket for security analysis and compliance auditing.",
"Url": "https://hub.prowler.com/check/oss_bucket_logging_enabled"
}
},
@@ -2,32 +2,29 @@
"Provider": "alibabacloud",
"CheckID": "oss_bucket_not_publicly_accessible",
"CheckTitle": "OSS bucket is not anonymously or publicly accessible",
"CheckType": [
"Sensitive file tampering",
"Cloud threat detection"
],
"CheckType": [],
"ServiceName": "oss",
"SubServiceName": "",
"ResourceIdTemplate": "acs:oss::account-id:bucket-name",
"ResourceIdTemplate": "",
"Severity": "critical",
"ResourceType": "AlibabaCloudOSSBucket",
"ResourceType": "ALIYUN::OSS::Bucket",
"ResourceGroup": "storage",
"Description": "A bucket is a container used to store objects in **Object Storage Service (OSS)**. All objects in OSS are stored in buckets.\n\nIt is recommended that the access policy on OSS buckets does not allow **anonymous** and/or **public access**.",
"Risk": "Allowing **anonymous** and/or **public access** grants permissions to anyone to access bucket content. Such access might not be desired if you are storing any sensitive data.\n\nPublic buckets can lead to **data breaches**, **unauthorized data access**, and **compliance violations**.",
"Description": "**Alibaba Cloud Object Storage Service (OSS)** buckets store objects that may contain sensitive data. It is recommended that the access policy on OSS buckets does not allow **anonymous** or **public access**, ensuring that only authorized identities can interact with bucket contents. The bucket ACL should be set to `private` to prevent unintended data exposure.",
"Risk": "Allowing **anonymous** or **public access** to OSS buckets grants permissions to anyone on the internet to read or modify bucket content. This can lead to **data breaches**, **unauthorized data exfiltration**, **data tampering**, and **compliance violations**, particularly when buckets contain sensitive or regulated information.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/31896.htm",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-OSS/publicly-accessible-oss-bucket.html"
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-OSS/publicly-accessible-oss-bucket.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun oss PutBucketAcl --bucket <bucket-name> --acl private",
"NativeIaC": "",
"Other": "",
"Other": "**Set Bucket ACL to Private:**\n1. Log on to the **OSS Console**\n2. In the bucket-list pane, click on a target OSS bucket\n3. Click on **Basic Setting** in the top middle of the console\n4. Under ACL section, click on **Configure**\n5. Click **Private** and click **Save**\n\n**For Bucket Policy:**\n1. Click **Bucket**, and then click the name of the target bucket\n2. Click the **Files** tab and click **Authorize**\n3. In the Authorize dialog, choose `Anonymous Accounts (*)` for Accounts and choose `None` for Authorized Operation\n4. Click **OK**",
"Terraform": "resource \"alicloud_oss_bucket_public_access_block\" \"example\" {\n bucket = alicloud_oss_bucket.example.bucket\n block_public_access = true\n}"
},
"Recommendation": {
"Text": "**Set Bucket ACL to Private:**\n1. Log on to the **OSS Console**\n2. In the bucket-list pane, click on a target OSS bucket\n3. Click on **Basic Setting** in the top middle of the console\n4. Under ACL section, click on **Configure**\n5. Click **Private** and click **Save**\n\n**For Bucket Policy:**\n1. Click **Bucket**, and then click the name of the target bucket\n2. Click the **Files** tab and click **Authorize**\n3. In the Authorize dialog, choose `Anonymous Accounts (*)` for Accounts and choose `None` for Authorized Operation\n4. Click **OK**",
"Text": "Set the OSS bucket ACL to private and configure bucket policies to deny anonymous or public access, ensuring only authorized identities can access stored objects.",
"Url": "https://hub.prowler.com/check/oss_bucket_not_publicly_accessible"
}
},
@@ -1,32 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "oss_bucket_secure_transport_enabled",
"CheckTitle": "Secure transfer required is set to Enabled",
"CheckType": [
"Sensitive file tampering"
],
"CheckTitle": "Secure transfer required is enabled for OSS buckets",
"CheckType": [],
"ServiceName": "oss",
"SubServiceName": "",
"ResourceIdTemplate": "acs:oss::account-id:bucket-name",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "AlibabaCloudOSSBucket",
"ResourceType": "ALIYUN::OSS::Bucket",
"ResourceGroup": "storage",
"Description": "Enable **data encryption in transit**. The secure transfer enhances the security of OSS buckets by only allowing requests to the storage account via a secure connection.\n\nFor example, when calling REST APIs to access storage accounts, the connection must use **HTTPS**. Any requests using HTTP will be rejected.",
"Risk": "Without **secure transfer enforcement**, OSS buckets may accept HTTP requests, which are not encrypted in transit.\n\nThis exposes data to potential **interception** and **man-in-the-middle attacks**, compromising data confidentiality and integrity.",
"Description": "**Alibaba Cloud OSS** buckets should enforce **secure transfer** by requiring all requests to use HTTPS. A bucket policy that denies requests with `acs:SecureTransport` set to `false` ensures that data in transit is encrypted, rejecting any unencrypted HTTP connections to the storage endpoint.",
"Risk": "Without **secure transfer enforcement**, OSS buckets accept HTTP requests that transmit data in plaintext. This exposes stored data to potential **interception**, **man-in-the-middle attacks**, and **eavesdropping**, compromising data **confidentiality** and **integrity** during transit.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/85111.htm",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-OSS/enable-secure-transfer.html"
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-OSS/enable-secure-transfer.html"
],
"Remediation": {
"Code": {
"CLI": "",
"CLI": "aliyun oss PutBucketPolicy --bucket <bucket-name> --policy '{\"Version\":\"1\",\"Statement\":[{\"Effect\":\"Deny\",\"Principal\":[\"*\"],\"Action\":[\"oss:*\"],\"Resource\":[\"acs:oss:*:*:<bucket-name>\",\"acs:oss:*:*:<bucket-name>/*\"],\"Condition\":{\"Bool\":{\"acs:SecureTransport\":\"false\"}}}]}'",
"NativeIaC": "",
"Other": "",
"Other": "1. Log on to the **OSS Console**\n2. In the bucket-list pane, click on a target OSS bucket\n3. Click on **Files** in the top middle of the console\n4. Click on **Authorize**\n5. Configure: `Whole Bucket`, `*`, `None` (Authorized Operation) and `http` (Conditions: Access Method) to deny HTTP access\n6. Click **Save**",
"Terraform": "resource \"alicloud_oss_bucket\" \"example\" {\n bucket = \"example-bucket\"\n \n policy = jsonencode({\n \"Version\": \"1\",\n \"Statement\": [{\n \"Effect\": \"Deny\",\n \"Principal\": [\"*\"],\n \"Action\": [\"oss:*\"],\n \"Resource\": [\"acs:oss:*:*:example-bucket\", \"acs:oss:*:*:example-bucket/*\"],\n \"Condition\": {\n \"Bool\": {\n \"acs:SecureTransport\": \"false\"\n }\n }\n }]\n })\n}"
},
"Recommendation": {
"Text": "1. Log on to the **OSS Console**\n2. In the bucket-list pane, click on a target OSS bucket\n3. Click on **Files** in the top middle of the console\n4. Click on **Authorize**\n5. Configure: `Whole Bucket`, `*`, `None` (Authorized Operation) and `http` (Conditions: Access Method) to deny HTTP access\n6. Click **Save**",
"Text": "Enforce secure transfer on OSS buckets by applying a bucket policy that denies all requests not using HTTPS, ensuring data in transit is always encrypted.",
"Url": "https://hub.prowler.com/check/oss_bucket_secure_transport_enabled"
}
},
@@ -2,32 +2,29 @@
"Provider": "alibabacloud",
"CheckID": "ram_no_root_access_key",
"CheckTitle": "No root account access key exists",
"CheckType": [
"Unusual logon",
"Cloud threat detection"
],
"CheckType": [],
"ServiceName": "ram",
"SubServiceName": "",
"ResourceIdTemplate": "acs:ram::account-id:root",
"ResourceIdTemplate": "",
"Severity": "critical",
"ResourceType": "AlibabaCloudRAMAccessKey",
"ResourceType": "ALIYUN::RAM::User",
"ResourceGroup": "IAM",
"Description": "Ensure no **root account access key** exists. Access keys provide programmatic access to a given Alibaba Cloud account.\n\nIt is recommended that all access keys associated with the root account be removed.",
"Risk": "The **root account** is the most privileged user in an Alibaba Cloud account. Access Keys provide programmatic access to a given Alibaba Cloud account.\n\nRemoving access keys associated with the root account limits vectors by which the account can be compromised and encourages the creation and use of **role-based accounts** that are least privileged.",
"Description": "**Alibaba Cloud RAM** access keys provide programmatic access to a given account. The **root account** is the most privileged user in an Alibaba Cloud account and should not have access keys associated with it. It is recommended that all access keys associated with the root account be removed to limit vectors by which the account can be compromised and encourage the creation of **role-based accounts** that follow the principle of least privilege.",
"Risk": "The **root account** has unrestricted access to all resources and services within the Alibaba Cloud account. If access keys for the root account are compromised, an attacker gains **full administrative control** over the entire account, including the ability to create, modify, or delete any resource. This poses a critical risk to the **confidentiality**, **integrity**, and **availability** of all cloud resources and data.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/102600.htm",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/remove-root-access-keys.html"
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/remove-root-access-keys.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ram DeleteAccessKey --UserAccessKeyId <access_key_ID>",
"NativeIaC": "",
"Other": "",
"Other": "1. Log on to the **RAM Console** by using your Alibaba Cloud account (root account).\n2. Move the pointer over the account icon in the upper-right corner and click **AccessKey**.\n3. Click **Continue to manage AccessKey**.\n4. On the Security Management page, find the target access keys and click **Delete** to delete the target access keys permanently.",
"Terraform": ""
},
"Recommendation": {
"Text": "1. Log on to the **RAM Console** by using your Alibaba Cloud account (root account)\n2. Move the pointer over the account icon in the upper-right corner and click **AccessKey**\n3. Click **Continue to manage AccessKey**\n4. On the Security Management page, find the target access keys and click **Delete** to delete the target access keys permanently",
"Text": "Remove all access keys associated with the root account to reduce the attack surface and encourage the use of role-based accounts with least privilege.",
"Url": "https://hub.prowler.com/check/ram_no_root_access_key"
}
},
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "ram_password_policy_lowercase",
"CheckTitle": "RAM password policy requires at least one lowercase letter",
"CheckType": [
"Unusual logon",
"Abnormal account"
],
"CheckTitle": "RAM password policy has lowercase letter requirement",
"CheckType": [],
"ServiceName": "ram",
"SubServiceName": "",
"ResourceIdTemplate": "acs:ram::account-id:password-policy",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "AlibabaCloudRAMPasswordPolicy",
"ResourceType": "ALIYUN::RAM::SecurityPreference",
"ResourceGroup": "IAM",
"Description": "**RAM password policies** can be used to ensure password complexity.\n\nIt is recommended that the password policy require at least one **lowercase letter**.",
"Risk": "Enhancing complexity of a password policy increases account resiliency against **brute force logon attempts**.\n\nWeak passwords without character variety are more susceptible to dictionary attacks and automated password cracking tools.",
"Description": "**Alibaba Cloud RAM** password policies can be used to enforce password complexity requirements. It is recommended that the password policy require at least one **lowercase letter** to increase the character diversity of passwords. This enhances account resiliency against **brute force logon attempts** and dictionary attacks.",
"Risk": "Without requiring **lowercase letters** in the password policy, users may create passwords with limited character diversity. Weak passwords without sufficient character variety are more susceptible to **dictionary attacks** and automated password cracking tools, potentially compromising the **confidentiality** of user accounts and the resources they have access to.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/116413.htm",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/lowercase-letter-password-policy.html"
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/lowercase-letter-password-policy.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ram SetPasswordPolicy --RequireLowercaseCharacters true",
"NativeIaC": "",
"Other": "",
"Other": "1. Log on to the **RAM Console**.\n2. Choose **Settings**.\n3. In the Password section, click **Modify**.\n4. In the Charset section, select **Lower case**.\n5. Click **OK**.",
"Terraform": "resource \"alicloud_ram_password_policy\" \"example\" {\n require_lowercase_characters = true\n}"
},
"Recommendation": {
"Text": "1. Log on to the **RAM Console**\n2. Choose **Settings**\n3. In the Password section, click **Modify**\n4. In the Charset section, select **Lower case**\n5. Click **OK**",
"Text": "Configure the RAM password policy to require at least one lowercase letter to improve password complexity.",
"Url": "https://hub.prowler.com/check/ram_password_policy_lowercase"
}
},
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "ram_password_policy_max_login_attempts",
"CheckTitle": "RAM password policy temporarily blocks logon after 5 incorrect logon attempts within an hour",
"CheckType": [
"Unusual logon",
"Abnormal account"
],
"CheckTitle": "RAM password policy temporarily blocks logon after 5 incorrect attempts within an hour",
"CheckType": [],
"ServiceName": "ram",
"SubServiceName": "",
"ResourceIdTemplate": "acs:ram::account-id:password-policy",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "AlibabaCloudRAMPasswordPolicy",
"ResourceType": "ALIYUN::RAM::SecurityPreference",
"ResourceGroup": "IAM",
"Description": "**RAM password policies** can temporarily block logon after several incorrect logon attempts within an hour.\n\nIt is recommended that the password policy is set to temporarily block logon after **5 incorrect logon attempts** within an hour.",
"Risk": "Temporarily blocking logon for incorrect password input increases account resiliency against **brute force logon attempts**.\n\nThis control helps prevent automated password guessing attacks from succeeding.",
"Description": "**Alibaba Cloud RAM** password policies can temporarily block logon after several incorrect logon attempts within an hour. It is recommended that the password policy is set to temporarily block logon after **5 incorrect logon attempts** within an hour to protect accounts against automated **brute force logon attempts** and credential stuffing attacks.",
"Risk": "Without an account lockout policy, attackers can make unlimited **brute force logon attempts** against RAM user accounts without any throttling. This significantly increases the risk of password compromise, potentially leading to unauthorized access to cloud resources and a breach of **confidentiality** and **integrity** of the account's data and services.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/116413.htm",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/max-login-attempts-password-policy.html"
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/max-login-attempts-password-policy.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ram SetPasswordPolicy --MaxLoginAttemps 5",
"NativeIaC": "",
"Other": "",
"Other": "1. Log on to the **RAM Console**.\n2. Choose **Settings**.\n3. In the Password section, click **Modify**.\n4. In the `Max Attempts` field, check the box next to **Enable** and enter `5`.\n5. Click **OK**.",
"Terraform": "resource \"alicloud_ram_password_policy\" \"example\" {\n max_login_attemps = 5\n}"
},
"Recommendation": {
"Text": "1. Log on to the **RAM Console**\n2. Choose **Settings**\n3. In the Password section, click **Modify**\n4. In the `Max Attempts` field, check the box next to **Enable** and enter `5`\n5. Click **OK**",
"Text": "Configure the RAM password policy to temporarily block logon after 5 incorrect attempts within an hour.",
"Url": "https://hub.prowler.com/check/ram_password_policy_max_login_attempts"
}
},
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "ram_password_policy_max_password_age",
"CheckTitle": "RAM password policy expires passwords in 365 days or greater",
"CheckType": [
"Unusual logon",
"Abnormal account"
],
"CheckTitle": "RAM password policy expires passwords within 365 days or less",
"CheckType": [],
"ServiceName": "ram",
"SubServiceName": "",
"ResourceIdTemplate": "acs:ram::account-id:password-policy",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "AlibabaCloudRAMPasswordPolicy",
"ResourceType": "ALIYUN::RAM::SecurityPreference",
"ResourceGroup": "IAM",
"Description": "**RAM password policies** can require passwords to be expired after a given number of days.\n\nIt is recommended that the password policy expire passwords after **365 days** or greater.",
"Risk": "Too frequent password changes are more harmful than beneficial. They offer no containment benefits and enforce bad habits, since they encourage users to choose variants of older passwords.\n\nThe CIS now recommends an **annual password reset** as a balanced approach.",
"Description": "**Alibaba Cloud RAM** password policies can require passwords to be expired after a given number of days. It is recommended that the password policy expire passwords after **365 days** or less to ensure periodic credential rotation. The CIS benchmark recommends an **annual password reset** as a balanced approach that avoids forcing overly frequent changes while still ensuring compromised credentials have a limited lifespan.",
"Risk": "Without a maximum password age policy, compromised passwords can remain valid **indefinitely**, giving attackers persistent access to cloud resources. While overly frequent password changes can encourage users to choose weak variants, a reasonable maximum age of **365 days** ensures that any compromised credentials are eventually invalidated, reducing the window of opportunity for unauthorized access and protecting the **confidentiality** and **integrity** of account data.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/116413.htm",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/require-password-expiration-policy.html"
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/require-password-expiration-policy.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ram SetPasswordPolicy --MaxPasswordAge 365",
"NativeIaC": "",
"Other": "",
"Terraform": "resource \"alicloud_ram_password_policy\" \"example\" {\n max_password_age = 90\n}"
"Other": "1. Log on to the **RAM Console**.\n2. Choose **Settings**.\n3. In the Password section, click **Modify**.\n4. Check the box under `Max Age`, enter `365` or a smaller number.\n5. Click **OK**.",
"Terraform": "resource \"alicloud_ram_password_policy\" \"example\" {\n max_password_age = 365\n}"
},
"Recommendation": {
"Text": "1. Log on to the **RAM Console**\n2. Choose **Settings**\n3. In the Password section, click **Modify**\n4. Check the box under `Max Age`, enter `365` or a greater number up to `1095`\n5. Click **OK**",
"Text": "Configure the RAM password policy to expire passwords within 365 days or less.",
"Url": "https://hub.prowler.com/check/ram_password_policy_max_password_age"
}
},
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "ram_password_policy_minimum_length",
"CheckTitle": "RAM password policy requires minimum length of 14 or greater",
"CheckType": [
"Unusual logon",
"Abnormal account"
],
"CheckTitle": "RAM password policy requires a minimum length of 14 or greater",
"CheckType": [],
"ServiceName": "ram",
"SubServiceName": "",
"ResourceIdTemplate": "acs:ram::account-id:password-policy",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "AlibabaCloudRAMPasswordPolicy",
"ResourceType": "ALIYUN::RAM::SecurityPreference",
"ResourceGroup": "IAM",
"Description": "**RAM password policies** can be used to ensure password complexity.\n\nIt is recommended that the password policy require a minimum of **14 or greater characters** for any password.",
"Risk": "Enhancing complexity of a password policy increases account resiliency against **brute force logon attempts**.\n\nLonger passwords provide exponentially more security against automated password cracking.",
"Description": "**Alibaba Cloud RAM** password policies can be used to enforce password complexity requirements. It is recommended that the password policy require a minimum of **14 or greater characters** for any password. Longer passwords provide exponentially more security against automated password cracking, as the keyspace increases dramatically with each additional character.",
"Risk": "Allowing short passwords significantly reduces the effort required for **brute force attacks** to succeed. Passwords shorter than **14 characters** can be cracked in a fraction of the time compared to longer passwords, potentially compromising the **confidentiality** of user accounts. This can lead to unauthorized access to cloud resources and sensitive data, affecting the overall **integrity** and **availability** of the environment.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/116413.htm",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/require-14-characters-password-policy.html"
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/require-14-characters-password-policy.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ram SetPasswordPolicy --MinimumPasswordLength 14",
"NativeIaC": "",
"Other": "",
"Other": "1. Log on to the **RAM Console**.\n2. Choose **Settings**.\n3. In the Password section, click **Modify**.\n4. In the Length section, enter `14` or a greater number.\n5. Click **OK**.",
"Terraform": "resource \"alicloud_ram_password_policy\" \"example\" {\n minimum_password_length = 14\n}"
},
"Recommendation": {
"Text": "1. Log on to the **RAM Console**\n2. Choose **Settings**\n3. In the Password section, click **Modify**\n4. In the Length section, enter `14` or a greater number\n5. Click **OK**",
"Text": "Configure the RAM password policy to require a minimum password length of 14 characters or greater.",
"Url": "https://hub.prowler.com/check/ram_password_policy_minimum_length"
}
},
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "ram_password_policy_number",
"CheckTitle": "RAM password policy require at least one number",
"CheckType": [
"Unusual logon",
"Abnormal account"
],
"CheckTitle": "RAM password policy requires at least one number",
"CheckType": [],
"ServiceName": "ram",
"SubServiceName": "",
"ResourceIdTemplate": "acs:ram::account-id:password-policy",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "AlibabaCloudRAMPasswordPolicy",
"ResourceType": "ALIYUN::RAM::SecurityPreference",
"ResourceGroup": "IAM",
"Description": "**RAM password policies** can be used to ensure password complexity.\n\nIt is recommended that the password policy require at least one **number**.",
"Risk": "Enhancing complexity of a password policy increases account resiliency against **brute force logon attempts**.\n\nWeak passwords without numeric characters are more susceptible to dictionary attacks.",
"Description": "**Alibaba Cloud RAM** password policies can be used to enforce password complexity requirements. It is recommended that the password policy require at least one **numeric character** to increase the character diversity of passwords. This enhances account resiliency against **brute force logon attempts** and dictionary attacks by expanding the keyspace.",
"Risk": "Without requiring **numeric characters** in the password policy, users may create passwords composed only of alphabetic characters. Such passwords are more susceptible to **dictionary attacks** and automated cracking tools, potentially compromising the **confidentiality** of user accounts and the cloud resources they protect.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/116413.htm",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/require-number-password-policy.html"
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/require-number-password-policy.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ram SetPasswordPolicy --RequireNumbers true",
"NativeIaC": "",
"Other": "",
"Other": "1. Log on to the **RAM Console**.\n2. Choose **Settings**.\n3. In the Password section, click **Modify**.\n4. In the Charset section, select **Number**.\n5. Click **OK**.",
"Terraform": "resource \"alicloud_ram_password_policy\" \"example\" {\n require_numbers = true\n}"
},
"Recommendation": {
"Text": "1. Log on to the **RAM Console**\n2. Choose **Settings**\n3. In the Password section, click **Modify**\n4. In the Charset section, select **Number**\n5. Click **OK**",
"Text": "Configure the RAM password policy to require at least one numeric character to improve password complexity.",
"Url": "https://hub.prowler.com/check/ram_password_policy_number"
}
},
@@ -2,32 +2,29 @@
"Provider": "alibabacloud",
"CheckID": "ram_password_policy_password_reuse_prevention",
"CheckTitle": "RAM password policy prevents password reuse",
"CheckType": [
"Unusual logon",
"Abnormal account"
],
"CheckType": [],
"ServiceName": "ram",
"SubServiceName": "",
"ResourceIdTemplate": "acs:ram::account-id:password-policy",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "AlibabaCloudRAMPasswordPolicy",
"ResourceType": "ALIYUN::RAM::SecurityPreference",
"ResourceGroup": "IAM",
"Description": "It is recommended that the **password policy** prevent the reuse of passwords.\n\nThis ensures users cannot cycle back to previously compromised passwords.",
"Risk": "Preventing **password reuse** increases account resiliency against brute force logon attempts.\n\nIf a password is compromised and later reused, attackers with knowledge of old credentials can regain access.",
"Description": "**Alibaba Cloud RAM** password policies can be configured to prevent the reuse of previously used passwords. It is recommended that the password policy prevent the reuse of passwords to ensure users cannot cycle back to previously compromised credentials. This increases account resiliency against **brute force logon attempts** and reduces the risk of credential reuse attacks.",
"Risk": "Without **password reuse prevention**, users may cycle back to previously compromised passwords. If a password was compromised in the past and is later reused, attackers with knowledge of old credentials can regain access to the account, threatening the **confidentiality** and **integrity** of cloud resources. This significantly weakens the overall security posture of the Alibaba Cloud environment.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/116413.htm",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/prevent-password-reuse-password-policy.html"
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/prevent-password-reuse-password-policy.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ram SetPasswordPolicy --PasswordReusePrevention 5",
"NativeIaC": "",
"Other": "",
"Terraform": "resource \"alicloud_ram_password_policy\" \"example\" {\n password_reuse_prevention = 24\n}"
"Other": "1. Log on to the **RAM Console**.\n2. Choose **Settings**.\n3. In the Password section, click **Modify**.\n4. In the `Do Not repeat History` section field, enter `5`.\n5. Click **OK**.",
"Terraform": "resource \"alicloud_ram_password_policy\" \"example\" {\n password_reuse_prevention = 5\n}"
},
"Recommendation": {
"Text": "1. Log on to the **RAM Console**\n2. Choose **Settings**\n3. In the Password section, click **Modify**\n4. In the `Do Not repeat History` section field, enter `5`\n5. Click **OK**",
"Text": "Configure the RAM password policy to prevent the reuse of at least the last 5 passwords.",
"Url": "https://hub.prowler.com/check/ram_password_policy_password_reuse_prevention"
}
},
@@ -1,33 +1,30 @@
{
"Provider": "alibabacloud",
"CheckID": "ram_password_policy_symbol",
"CheckTitle": "RAM password policy require at least one symbol",
"CheckType": [
"Unusual logon",
"Abnormal account"
],
"CheckTitle": "RAM password policy requires at least one symbol",
"CheckType": [],
"ServiceName": "ram",
"SubServiceName": "",
"ResourceIdTemplate": "acs:ram::account-id:password-policy",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "AlibabaCloudRAMPasswordPolicy",
"ResourceType": "ALIYUN::RAM::SecurityPreference",
"ResourceGroup": "IAM",
"Description": "**RAM password policies** can be used to ensure password complexity.\n\nIt is recommended that the password policy require at least one **symbol**.",
"Risk": "Enhancing complexity of a password policy increases account resiliency against **brute force logon attempts**.\n\nSpecial characters significantly increase the keyspace that attackers must search.",
"Description": "**Alibaba Cloud RAM** password policies can be used to enforce password complexity requirements. It is recommended that the password policy require at least one **special character (symbol)** to increase the character diversity of passwords. Special characters significantly increase the keyspace that attackers must search, enhancing account resiliency against **brute force logon attempts**.",
"Risk": "Without requiring **symbols** in the password policy, users may create passwords composed only of alphanumeric characters. Such passwords have a reduced keyspace and are more susceptible to **brute force attacks** and automated password cracking tools, potentially compromising the **confidentiality** of user accounts and the cloud resources they protect.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://www.alibabacloud.com/help/doc-detail/116413.htm",
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/require-symbol-password-policy.html"
"https://www.trendmicro.com/trendaivisiononecloudriskmanagement/knowledge-base/alibaba-cloud/AlibabaCloud-RAM/require-symbol-password-policy.html"
],
"Remediation": {
"Code": {
"CLI": "aliyun ram SetPasswordPolicy --RequireSymbols true",
"NativeIaC": "",
"Other": "",
"Other": "1. Log on to the **RAM Console**.\n2. Choose **Settings**.\n3. In the Password section, click **Modify**.\n4. In the Charset section, select **Symbol**.\n5. Click **OK**.",
"Terraform": "resource \"alicloud_ram_password_policy\" \"example\" {\n require_symbols = true\n}"
},
"Recommendation": {
"Text": "1. Log on to the **RAM Console**\n2. Choose **Settings**\n3. In the Password section, click **Modify**\n4. In the Charset section, select **Symbol**\n5. Click **OK**",
"Text": "Configure the RAM password policy to require at least one symbol to improve password complexity.",
"Url": "https://hub.prowler.com/check/ram_password_policy_symbol"
}
},

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