Merge branch 'master' into PROWLER-182-kubernetes-add-and-connect-the-provider

This commit is contained in:
StylusFrost authored and GitHub committed 2025-11-07 13:01:18 +01:00
commit 693a2e9694
271 files changed
+6709 -1330

No files matched your search

+7 -2
View File
@@ -14,7 +14,11 @@ UI_PORT=3000
AUTH_SECRET="N/c6mnaS5+SWq81+819OrzQZlmx1Vxtp/orjttJSmw8="
# Google Tag Manager ID
NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID=""
#### Code Review Configuration ####
# Enable Claude Code standards validation on pre-push hook
# Set to 'true' to validate changes against AGENTS.md standards via Claude Code
# Set to 'false' to skip validation
CODE_REVIEW_ENABLED=true
#### Prowler API Configuration ####
PROWLER_API_VERSION="stable"
@@ -132,4 +136,5 @@ LANGCHAIN_PROJECT=""
# Example with one source:
RSS_FEED_SOURCES='[{"id":"prowler-releases","name":"Prowler Releases","type":"github_releases","url":"https://github.com/prowler-cloud/prowler/releases.atom","enabled":true}]'
# Example with multiple sources (no trailing comma after last item):
# RSS_FEED_SOURCES='[{"id":"prowler-releases","name":"Prowler Releases","type":"github_releases","url":"https://github.com/prowler-cloud/prowler/releases.atom","enabled":true},{"id":"prowler-blog","name":"Prowler Blog","type":"blog","url":"https://prowler.com/blog/rss","enabled":false}]'
# RSS_FEED_SOURCES='[{"id":"prowler-releases","name":"Prowler Releases","type":"github_releases","url":"https://github.com/prowler-cloud/prowler/releases.atom","enabled":true},{"id":"prowler-blog","name":"Prowler Blog","type":"blog","url":"https://prowler.com/blog/rss","enabled":false}]'
@@ -12,7 +12,7 @@ inputs:
required: false
default: ''
step-outcome:
description: 'Outcome of a step to determine status (success/failure) - automatically sets STATUS_EMOJI, STATUS_TEXT, and STATUS_COLOR env vars'
description: 'Outcome of a step to determine status (success/failure) - automatically sets STATUS_TEXT and STATUS_COLOR env vars'
required: false
default: ''
outputs:
@@ -27,16 +27,14 @@ runs:
shell: bash
run: |
if [[ "${{ inputs.step-outcome }}" == "success" ]]; then
echo "STATUS_EMOJI=[✓]" >> $GITHUB_ENV
echo "STATUS_TEXT=succeeded" >> $GITHUB_ENV
echo "STATUS_COLOR=6aa84f" >> $GITHUB_ENV
echo "STATUS_TEXT=Completed" >> $GITHUB_ENV
echo "STATUS_COLOR=#6aa84f" >> $GITHUB_ENV
elif [[ "${{ inputs.step-outcome }}" == "failure" ]]; then
echo "STATUS_EMOJI=[✗]" >> $GITHUB_ENV
echo "STATUS_TEXT=failed" >> $GITHUB_ENV
echo "STATUS_COLOR=fc3434" >> $GITHUB_ENV
echo "STATUS_TEXT=Failed" >> $GITHUB_ENV
echo "STATUS_COLOR=#fc3434" >> $GITHUB_ENV
else
# No outcome provided - pending/in progress state
echo "STATUS_COLOR=dbab09" >> $GITHUB_ENV
echo "STATUS_COLOR=#dbab09" >> $GITHUB_ENV
fi
- name: Send Slack notification (new message)
+7
View File
@@ -22,6 +22,13 @@ Please add a detailed description of how to review this PR.
- [ ] Review if is needed to change the [Readme.md](https://github.com/prowler-cloud/prowler/blob/master/README.md)
- [ ] Ensure new entries are added to [CHANGELOG.md](https://github.com/prowler-cloud/prowler/blob/master/prowler/CHANGELOG.md), if applicable.
#### UI
- [ ] All issue/task requirements work as expected on the UI
- [ ] Screenshots/Video of the functionality flow (if applicable) - Mobile (X < 640px)
- [ ] Screenshots/Video of the functionality flow (if applicable) - Table (640px > X < 1024px)
- [ ] Screenshots/Video of the functionality flow (if applicable) - Desktop (X > 1024px)
- [ ] Ensure new entries are added to [CHANGELOG.md](https://github.com/prowler-cloud/prowler/blob/master/ui/CHANGELOG.md), if applicable.
#### API
- [ ] Verify if API specs need to be regenerated.
- [ ] Check if version updates are required (e.g., specs, Poetry, etc.).
@@ -1,4 +1,17 @@
{
"channel": "${{ env.SLACK_CHANNEL_ID }}",
"text": "${{ env.STATUS_EMOJI }} ${{ env.COMPONENT }} container release ${{ env.RELEASE_TAG }} push ${{ env.STATUS_TEXT }} <${{ env.GITHUB_SERVER_URL }}/${{ env.GITHUB_REPOSITORY }}/actions/runs/${{ env.GITHUB_RUN_ID }}|View run>"
"attachments": [
{
"color": "${{ env.STATUS_COLOR }}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Status:*\n${{ env.STATUS_TEXT }}\n\n${{ env.COMPONENT }} container release ${{ env.RELEASE_TAG }} push ${{ env.STATUS_TEXT }}\n\n<${{ env.GITHUB_SERVER_URL }}/${{ env.GITHUB_REPOSITORY }}/actions/runs/${{ env.GITHUB_RUN_ID }}|View run>"
}
}
]
}
]
}
@@ -1,4 +1,17 @@
{
"channel": "${{ env.SLACK_CHANNEL_ID }}",
"text": "${{ env.COMPONENT }} container release ${{ env.RELEASE_TAG }} push started... <${{ env.GITHUB_SERVER_URL }}/${{ env.GITHUB_REPOSITORY }}/actions/runs/${{ env.GITHUB_RUN_ID }}|View run>"
"attachments": [
{
"color": "${{ env.STATUS_COLOR }}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Status:*\nStarted\n\n${{ env.COMPONENT }} container release ${{ env.RELEASE_TAG }} push started...\n\n<${{ env.GITHUB_SERVER_URL }}/${{ env.GITHUB_REPOSITORY }}/actions/runs/${{ env.GITHUB_RUN_ID }}|View run>"
}
}
]
}
]
}
+3 -6
View File
@@ -5,16 +5,10 @@ on:
branches:
- 'master'
- 'v5.*'
paths:
- 'api/**'
- '.github/workflows/api-code-quality.yml'
pull_request:
branches:
- 'master'
- 'v5.*'
paths:
- 'api/**'
- '.github/workflows/api-code-quality.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -45,6 +39,9 @@ jobs:
id: check-changes
uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0
with:
files: |
api/**
.github/workflows/api-code-quality.yml
files_ignore: |
api/docs/**
api/README.md
+2 -2
View File
@@ -45,12 +45,12 @@ jobs:
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Initialize CodeQL
uses: github/codeql-action/init@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
with:
languages: ${{ matrix.language }}
config-file: ./.github/codeql/api-codeql-config.yml
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
with:
category: '/language:${{ matrix.language }}'
+1 -6
View File
@@ -5,16 +5,10 @@ on:
branches:
- 'master'
- 'v5.*'
paths:
- 'api/**'
- '.github/workflows/api-container-checks.yml'
pull_request:
branches:
- 'master'
- 'v5.*'
paths:
- 'api/**'
- '.github/workflows/api-container-checks.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -64,6 +58,7 @@ jobs:
id: check-changes
uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0
with:
files: api/**
files_ignore: |
api/docs/**
api/README.md
+3 -6
View File
@@ -5,16 +5,10 @@ on:
branches:
- 'master'
- 'v5.*'
paths:
- 'api/**'
- '.github/workflows/api-security.yml'
pull_request:
branches:
- 'master'
- 'v5.*'
paths:
- 'api/**'
- '.github/workflows/api-security.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -45,6 +39,9 @@ jobs:
id: check-changes
uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0
with:
files: |
api/**
.github/workflows/api-security.yml
files_ignore: |
api/docs/**
api/README.md
+3 -6
View File
@@ -5,16 +5,10 @@ on:
branches:
- 'master'
- 'v5.*'
paths:
- 'api/**'
- '.github/workflows/api-tests.yml'
pull_request:
branches:
- 'master'
- 'v5.*'
paths:
- 'api/**'
- '.github/workflows/api-tests.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -85,6 +79,9 @@ jobs:
id: check-changes
uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0
with:
files: |
api/**
.github/workflows/api-tests.yml
files_ignore: |
api/docs/**
api/README.md
+1 -1
View File
@@ -38,7 +38,7 @@ jobs:
- name: Backport PR
if: steps.label_check.outputs.label_check == 'success'
uses: sorenlouv/backport-github-action@ad888e978060bc1b2798690dd9d03c4036560947 # v9.5.1
uses: sorenlouv/backport-github-action@516854e7c9f962b9939085c9a92ea28411d1ae90 # v10.2.0
with:
github_token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
auto_backport_label_prefix: ${{ env.BACKPORT_LABEL_PREFIX }}
+1 -1
View File
@@ -26,6 +26,6 @@ jobs:
steps:
- name: Check PR title format
uses: agenthunt/conventional-commit-checker-action@9e552d650d0e205553ec7792d447929fc78e012b # v2.0.0
uses: agenthunt/conventional-commit-checker-action@f1823f632e95a64547566dcd2c7da920e67117ad # v2.0.1
with:
pr-title-regex: '^(feat|fix|docs|style|refactor|perf|test|chore|build|ci|revert)(\([^)]+\))?!?: .+'
+1 -1
View File
@@ -28,6 +28,6 @@ jobs:
fetch-depth: 0
- name: Scan for secrets with TruffleHog
uses: trufflesecurity/trufflehog@ad6fc8fb446b8fafbf7ea8193d2d6bfd42f45690 # v3.90.11
uses: trufflesecurity/trufflehog@b84c3d14d189e16da175e2c27fa8136603783ffc # v3.90.12
with:
extra_args: '--results=verified,unknown'
+3 -1
View File
@@ -40,4 +40,6 @@ jobs:
GH_TOKEN: ${{ github.token }}
run: |
echo "Adding 'community' label to PR #$PR_NUMBER"
gh pr edit "$PR_NUMBER" --add-label community
gh api /repos/${{ github.repository }}/issues/${{ github.event.number }}/labels \
-X POST \
-f labels[]='community'
+1 -6
View File
@@ -5,16 +5,10 @@ on:
branches:
- 'master'
- 'v5.*'
paths:
- 'mcp_server/**'
- '.github/workflows/mcp-container-checks.yml'
pull_request:
branches:
- 'master'
- 'v5.*'
paths:
- 'mcp_server/**'
- '.github/workflows/mcp-container-checks.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -63,6 +57,7 @@ jobs:
id: check-changes
uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0
with:
files: mcp_server/**
files_ignore: |
mcp_server/README.md
mcp_server/CHANGELOG.md
+1 -1
View File
@@ -83,7 +83,7 @@ jobs:
- name: Update PR comment with changelog status
if: github.event.pull_request.head.repo.full_name == github.repository
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
with:
issue-number: ${{ github.event.pull_request.number }}
comment-id: ${{ steps.find-comment.outputs.comment-id }}
+1 -1
View File
@@ -97,7 +97,7 @@ jobs:
body-includes: '<!-- conflict-checker-comment -->'
- name: Create or update comment
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
with:
comment-id: ${{ steps.find-comment.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
+71 -81
View File
@@ -47,7 +47,7 @@ jobs:
git config --global user.name 'prowler-bot'
git config --global user.email '179230569+prowler-bot@users.noreply.github.com'
- name: Parse version and read changelogs
- name: Parse version and determine branch
run: |
# Validate version format (reusing pattern from sdk-bump-version.yml)
if [[ $PROWLER_VERSION =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
@@ -64,66 +64,83 @@ jobs:
BRANCH_NAME="v${MAJOR_VERSION}.${MINOR_VERSION}"
echo "BRANCH_NAME=${BRANCH_NAME}" >> "${GITHUB_ENV}"
# Function to extract the latest version from changelog
extract_latest_version() {
local changelog_file="$1"
if [ -f "$changelog_file" ]; then
# Extract the first version entry (most recent) from changelog
# Format: ## [version] (1.2.3) or ## [vversion] (v1.2.3)
local version=$(grep -m 1 '^## \[' "$changelog_file" | sed 's/^## \[\(.*\)\].*/\1/' | sed 's/^v//' | tr -d '[:space:]')
echo "$version"
else
echo ""
fi
}
# Read actual versions from changelogs (source of truth)
UI_VERSION=$(extract_latest_version "ui/CHANGELOG.md")
API_VERSION=$(extract_latest_version "api/CHANGELOG.md")
SDK_VERSION=$(extract_latest_version "prowler/CHANGELOG.md")
MCP_VERSION=$(extract_latest_version "mcp_server/CHANGELOG.md")
echo "UI_VERSION=${UI_VERSION}" >> "${GITHUB_ENV}"
echo "API_VERSION=${API_VERSION}" >> "${GITHUB_ENV}"
echo "SDK_VERSION=${SDK_VERSION}" >> "${GITHUB_ENV}"
echo "MCP_VERSION=${MCP_VERSION}" >> "${GITHUB_ENV}"
if [ -n "$UI_VERSION" ]; then
echo "Read UI version from changelog: $UI_VERSION"
else
echo "Warning: No UI version found in ui/CHANGELOG.md"
fi
if [ -n "$API_VERSION" ]; then
echo "Read API version from changelog: $API_VERSION"
else
echo "Warning: No API version found in api/CHANGELOG.md"
fi
if [ -n "$SDK_VERSION" ]; then
echo "Read SDK version from changelog: $SDK_VERSION"
else
echo "Warning: No SDK version found in prowler/CHANGELOG.md"
fi
if [ -n "$MCP_VERSION" ]; then
echo "Read MCP version from changelog: $MCP_VERSION"
else
echo "Warning: No MCP version found in mcp_server/CHANGELOG.md"
fi
echo "Prowler version: $PROWLER_VERSION"
echo "Branch name: $BRANCH_NAME"
echo "UI version: $UI_VERSION"
echo "API version: $API_VERSION"
echo "SDK version: $SDK_VERSION"
echo "MCP version: $MCP_VERSION"
echo "Is minor release: $([ $PATCH_VERSION -eq 0 ] && echo 'true' || echo 'false')"
else
echo "Invalid version syntax: '$PROWLER_VERSION' (must be N.N.N)" >&2
exit 1
fi
- name: Checkout release branch
run: |
echo "Checking out branch $BRANCH_NAME for release $PROWLER_VERSION..."
if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then
echo "Branch $BRANCH_NAME exists locally, checking out..."
git checkout "$BRANCH_NAME"
elif git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then
echo "Branch $BRANCH_NAME exists remotely, checking out..."
git checkout -b "$BRANCH_NAME" "origin/$BRANCH_NAME"
else
echo "ERROR: Branch $BRANCH_NAME does not exist. For minor releases (X.Y.0), create it manually first. For patch releases (X.Y.Z), the branch should already exist."
exit 1
fi
- name: Read changelog versions from release branch
run: |
# Function to extract the latest version from changelog
extract_latest_version() {
local changelog_file="$1"
if [ -f "$changelog_file" ]; then
# Extract the first version entry (most recent) from changelog
# Format: ## [version] (1.2.3) or ## [vversion] (v1.2.3)
local version=$(grep -m 1 '^## \[' "$changelog_file" | sed 's/^## \[\(.*\)\].*/\1/' | sed 's/^v//' | tr -d '[:space:]')
echo "$version"
else
echo ""
fi
}
# Read actual versions from changelogs (source of truth)
UI_VERSION=$(extract_latest_version "ui/CHANGELOG.md")
API_VERSION=$(extract_latest_version "api/CHANGELOG.md")
SDK_VERSION=$(extract_latest_version "prowler/CHANGELOG.md")
MCP_VERSION=$(extract_latest_version "mcp_server/CHANGELOG.md")
echo "UI_VERSION=${UI_VERSION}" >> "${GITHUB_ENV}"
echo "API_VERSION=${API_VERSION}" >> "${GITHUB_ENV}"
echo "SDK_VERSION=${SDK_VERSION}" >> "${GITHUB_ENV}"
echo "MCP_VERSION=${MCP_VERSION}" >> "${GITHUB_ENV}"
if [ -n "$UI_VERSION" ]; then
echo "Read UI version from changelog: $UI_VERSION"
else
echo "Warning: No UI version found in ui/CHANGELOG.md"
fi
if [ -n "$API_VERSION" ]; then
echo "Read API version from changelog: $API_VERSION"
else
echo "Warning: No API version found in api/CHANGELOG.md"
fi
if [ -n "$SDK_VERSION" ]; then
echo "Read SDK version from changelog: $SDK_VERSION"
else
echo "Warning: No SDK version found in prowler/CHANGELOG.md"
fi
if [ -n "$MCP_VERSION" ]; then
echo "Read MCP version from changelog: $MCP_VERSION"
else
echo "Warning: No MCP version found in mcp_server/CHANGELOG.md"
fi
echo "UI version: $UI_VERSION"
echo "API version: $API_VERSION"
echo "SDK version: $SDK_VERSION"
echo "MCP version: $MCP_VERSION"
- name: Extract and combine changelog entries
run: |
set -e
@@ -255,21 +272,6 @@ jobs:
echo "Combined changelog preview:"
cat combined_changelog.md
- name: Checkout release branch for patch release
if: ${{ env.PATCH_VERSION != '0' }}
run: |
echo "Patch release detected, checking out existing branch $BRANCH_NAME..."
if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then
echo "Branch $BRANCH_NAME exists locally, checking out..."
git checkout "$BRANCH_NAME"
elif git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then
echo "Branch $BRANCH_NAME exists remotely, checking out..."
git checkout -b "$BRANCH_NAME" "origin/$BRANCH_NAME"
else
echo "ERROR: Branch $BRANCH_NAME should exist for patch release $PROWLER_VERSION"
exit 1
fi
- name: Verify SDK version in pyproject.toml
run: |
CURRENT_VERSION=$(grep '^version = ' pyproject.toml | sed -E 's/version = "([^"]+)"/\1/' | tr -d '[:space:]')
@@ -323,18 +325,6 @@ jobs:
fi
echo "✓ api/src/backend/api/v1/views.py version: $CURRENT_API_VERSION"
- name: Checkout release branch for minor release
if: ${{ env.PATCH_VERSION == '0' }}
run: |
echo "Minor release detected (patch = 0), checking out existing branch $BRANCH_NAME..."
if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then
echo "Branch $BRANCH_NAME exists remotely, checking out..."
git checkout -b "$BRANCH_NAME" "origin/$BRANCH_NAME"
else
echo "ERROR: Branch $BRANCH_NAME should exist for minor release $PROWLER_VERSION. Please create it manually first."
exit 1
fi
- name: Update API prowler dependency for minor release
if: ${{ env.PATCH_VERSION == '0' }}
run: |
@@ -392,7 +382,7 @@ jobs:
no-changelog
- name: Create draft release
uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
with:
tag_name: ${{ env.PROWLER_VERSION }}
name: Prowler ${{ env.PROWLER_VERSION }}
+2 -12
View File
@@ -5,22 +5,10 @@ on:
branches:
- 'master'
- 'v5.*'
paths:
- 'prowler/**'
- 'tests/**'
- 'pyproject.toml'
- 'poetry.lock'
- '.github/workflows/sdk-code-quality.yml'
pull_request:
branches:
- 'master'
- 'v5.*'
paths:
- 'prowler/**'
- 'tests/**'
- 'pyproject.toml'
- 'poetry.lock'
- '.github/workflows/sdk-code-quality.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -49,7 +37,9 @@ jobs:
id: check-changes
uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0
with:
files: ./**
files_ignore: |
.github/**
prowler/CHANGELOG.md
docs/**
permissions/**
+2 -2
View File
@@ -52,12 +52,12 @@ jobs:
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Initialize CodeQL
uses: github/codeql-action/init@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
with:
languages: ${{ matrix.language }}
config-file: ./.github/codeql/sdk-codeql-config.yml
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
with:
category: '/language:${{ matrix.language }}'
+2 -10
View File
@@ -5,20 +5,10 @@ on:
branches:
- 'master'
- 'v5.*'
paths:
- 'prowler/**'
- 'tests/**'
- 'Dockerfile'
- '.github/workflows/sdk-container-checks.yml'
pull_request:
branches:
- 'master'
- 'v5.*'
paths:
- 'prowler/**'
- 'tests/**'
- 'Dockerfile'
- '.github/workflows/sdk-container-checks.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -69,7 +59,9 @@ jobs:
id: check-changes
uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0
with:
files: ./**
files_ignore: |
.github/**
prowler/CHANGELOG.md
docs/**
permissions/**
@@ -39,7 +39,7 @@ jobs:
run: pip install boto3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@a03048d87541d1d9fcf2ecf528a4a65ba9bd7838 # v5.0.0
uses: aws-actions/configure-aws-credentials@00943011d9042930efac3dcd3a170e4273319bc8 # v5.1.0
with:
aws-region: ${{ env.AWS_REGION }}
role-to-assume: ${{ secrets.DEV_IAM_ROLE_ARN }}
+2 -12
View File
@@ -5,22 +5,10 @@ on:
branches:
- 'master'
- 'v5.*'
paths:
- 'prowler/**'
- 'tests/**'
- 'pyproject.toml'
- 'poetry.lock'
- '.github/workflows/sdk-security.yml'
pull_request:
branches:
- 'master'
- 'v5.*'
paths:
- 'prowler/**'
- 'tests/**'
- 'pyproject.toml'
- 'poetry.lock'
- '.github/workflows/sdk-security.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -42,7 +30,9 @@ jobs:
id: check-changes
uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0
with:
files: ./**
files_ignore: |
.github/**
prowler/CHANGELOG.md
docs/**
permissions/**
+2 -12
View File
@@ -5,22 +5,10 @@ on:
branches:
- 'master'
- 'v5.*'
paths:
- 'prowler/**'
- 'tests/**'
- 'pyproject.toml'
- 'poetry.lock'
- '.github/workflows/sdk-tests.yml'
pull_request:
branches:
- 'master'
- 'v5.*'
paths:
- 'prowler/**'
- 'tests/**'
- 'pyproject.toml'
- 'poetry.lock'
- '.github/workflows/sdk-tests.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -49,7 +37,9 @@ jobs:
id: check-changes
uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0
with:
files: ./**
files_ignore: |
.github/**
prowler/CHANGELOG.md
docs/**
permissions/**
+2 -2
View File
@@ -48,12 +48,12 @@ jobs:
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Initialize CodeQL
uses: github/codeql-action/init@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
with:
languages: ${{ matrix.language }}
config-file: ./.github/codeql/ui-codeql-config.yml
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
with:
category: '/language:${{ matrix.language }}'
+1 -6
View File
@@ -5,16 +5,10 @@ on:
branches:
- 'master'
- 'v5.*'
paths:
- 'ui/**'
- '.github/workflows/ui-container-checks.yml'
pull_request:
branches:
- 'master'
- 'v5.*'
paths:
- 'ui/**'
- '.github/workflows/ui-container-checks.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -64,6 +58,7 @@ jobs:
id: check-changes
uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0
with:
files: ui/**
files_ignore: |
ui/CHANGELOG.md
ui/README.md
+1 -1
View File
@@ -95,7 +95,7 @@ jobs:
echo "All database fixtures loaded successfully!"
'
- name: Setup Node.js environment
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: '20.x'
cache: 'npm'
+3 -6
View File
@@ -5,16 +5,10 @@ on:
branches:
- 'master'
- 'v5.*'
paths:
- 'ui/**'
- '.github/workflows/ui-tests.yml'
pull_request:
branches:
- 'master'
- 'v5.*'
paths:
- 'ui/**'
- '.github/workflows/ui-tests.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -42,6 +36,9 @@ jobs:
id: check-changes
uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0
with:
files: |
ui/**
.github/workflows/ui-tests.yml
files_ignore: |
ui/CHANGELOG.md
ui/README.md
+14 -14
View File
@@ -56,7 +56,7 @@ Prowler includes hundreds of built-in controls to ensure compliance with standar
Prowler App is a web-based application that simplifies running Prowler across your cloud provider accounts. It provides a user-friendly interface to visualize the results and streamline your security assessments.
![Prowler App](docs/products/img/overview.png)
![Prowler App](docs/images/products/overview.png)
>For more details, refer to the [Prowler App Documentation](https://docs.prowler.com/projects/prowler-open-source/en/latest/#prowler-app-installation)
@@ -73,26 +73,26 @@ prowler <provider>
```console
prowler dashboard
```
![Prowler Dashboard](docs/products/img/dashboard.png)
![Prowler Dashboard](docs/images/products/dashboard.png)
# Prowler at a Glance
> [!Tip]
> For the most accurate and up-to-date information about checks, services, frameworks, and categories, visit [**Prowler Hub**](https://hub.prowler.com).
| Provider | Checks | Services | [Compliance Frameworks](https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/compliance/) | [Categories](https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/misc/#categories) | Support | Stage | Interface |
|---|---|---|---|---|---|---|---|
| AWS | 576 | 82 | 38 | 10 | Official | Stable | UI, API, CLI |
| GCP | 79 | 13 | 11 | 3 | Official | Stable | UI, API, CLI |
| Azure | 162 | 19 | 12 | 4 | Official | Stable | UI, API, CLI |
| Kubernetes | 83 | 7 | 5 | 7 | Official | Stable | UI, API, CLI |
| Provider | Checks | Services | [Compliance Frameworks](https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/compliance/) | [Categories](https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/misc/#categories) | Support | Interface |
|---|---|---|---|---|---|---|
| AWS | 576 | 82 | 38 | 10 | Official | UI, API, CLI |
| GCP | 79 | 13 | 12 | 3 | Official | UI, API, CLI |
| Azure | 162 | 19 | 12 | 4 | Official | UI, API, CLI |
| Kubernetes | 83 | 7 | 5 | 7 | Official | UI, API, CLI |
| GitHub | 17 | 2 | 1 | 0 | Official | Stable | UI, API, CLI |
| M365 | 70 | 7 | 3 | 2 | Official | Stable | UI, API, CLI |
| OCI | 51 | 13 | 1 | 10 | Official | Stable | UI, API, CLI |
| IaC | [See `trivy` docs.](https://trivy.dev/latest/docs/coverage/iac/) | N/A | N/A | N/A | Official | Beta | CLI |
| MongoDB Atlas | 10 | 3 | 0 | 0 | Official | Beta | CLI |
| LLM | [See `promptfoo` docs.](https://www.promptfoo.dev/docs/red-team/plugins/) | N/A | N/A | N/A | Official | Beta | CLI |
| NHN | 6 | 2 | 1 | 0 | Unofficial | Beta | CLI |
| M365 | 70 | 7 | 3 | 2 | Official | UI, API, CLI |
| OCI | 51 | 13 | 1 | 10 | 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 | 0 | Official | CLI, API |
| LLM | [See `promptfoo` docs.](https://www.promptfoo.dev/docs/red-team/plugins/) | N/A | N/A | N/A | Official | CLI |
| NHN | 6 | 2 | 1 | 0 | Unofficial | CLI |
> [!Note]
> The numbers in the table are updated periodically.
+2
View File
@@ -5,6 +5,7 @@ All notable changes to the **Prowler API** are documented in this file.
## [1.15.0] (Prowler UNRELEASED)
### Added
- IaC (Infrastructure as Code) provider support for remote repositories [(#8751)](https://github.com/prowler-cloud/prowler/pull/8751)
- Extend `GET /api/v1/providers` with provider-type filters and optional pagination disable to support the new Overview filters [(#8975)](https://github.com/prowler-cloud/prowler/pull/8975)
- New endpoint to retrieve the number of providers grouped by provider type [(#8975)](https://github.com/prowler-cloud/prowler/pull/8975)
- Support for configuring multiple LLM providers [(#8772)](https://github.com/prowler-cloud/prowler/pull/8772)
@@ -13,6 +14,7 @@ All notable changes to the **Prowler API** are documented in this file.
- Support muting findings based on simple rules with custom reason [(#9051)](https://github.com/prowler-cloud/prowler/pull/9051)
- Support C5 compliance framework for the GCP provider [(#9097)](https://github.com/prowler-cloud/prowler/pull/9097)
- Support for Amazon Bedrock and OpenAI compatible providers in Lighthouse AI [(#8957)](https://github.com/prowler-cloud/prowler/pull/8957)
- Support for MongoDB Atlas provider [(#9167)](https://github.com/prowler-cloud/prowler/pull/9167)
---
+21
View File
@@ -5,6 +5,9 @@ LABEL maintainer="https://github.com/prowler-cloud/api"
ARG POWERSHELL_VERSION=7.5.0
ENV POWERSHELL_VERSION=${POWERSHELL_VERSION}
ARG TRIVY_VERSION=0.66.0
ENV TRIVY_VERSION=${TRIVY_VERSION}
# hadolint ignore=DL3008
RUN apt-get update && apt-get install -y --no-install-recommends \
wget \
@@ -36,6 +39,24 @@ RUN ARCH=$(uname -m) && \
ln -s /opt/microsoft/powershell/7/pwsh /usr/bin/pwsh && \
rm /tmp/powershell.tar.gz
# Install Trivy for IaC scanning
RUN ARCH=$(uname -m) && \
if [ "$ARCH" = "x86_64" ]; then \
TRIVY_ARCH="Linux-64bit" ; \
elif [ "$ARCH" = "aarch64" ]; then \
TRIVY_ARCH="Linux-ARM64" ; \
else \
echo "Unsupported architecture for Trivy: $ARCH" && exit 1 ; \
fi && \
wget --progress=dot:giga "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_${TRIVY_ARCH}.tar.gz" -O /tmp/trivy.tar.gz && \
tar zxf /tmp/trivy.tar.gz -C /tmp && \
mv /tmp/trivy /usr/local/bin/trivy && \
chmod +x /usr/local/bin/trivy && \
rm /tmp/trivy.tar.gz && \
# Create trivy cache directory with proper permissions
mkdir -p /tmp/.cache/trivy && \
chmod 777 /tmp/.cache/trivy
# Add prowler user
RUN addgroup --gid 1000 prowler && \
adduser --uid 1000 --gid 1000 --disabled-password --gecos "" prowler
@@ -22,13 +22,13 @@ class Migration(migrations.Migration):
("kubernetes", "Kubernetes"),
("m365", "M365"),
("github", "GitHub"),
("oci", "Oracle Cloud Infrastructure"),
("oraclecloud", "Oracle Cloud Infrastructure"),
],
default="aws",
),
),
migrations.RunSQL(
"ALTER TYPE provider ADD VALUE IF NOT EXISTS 'oci';",
"ALTER TYPE provider ADD VALUE IF NOT EXISTS 'oraclecloud';",
reverse_sql=migrations.RunSQL.noop,
),
]
@@ -0,0 +1,35 @@
# Generated by Django 5.1.10 on 2025-09-09 09:25
from django.db import migrations
import api.db_utils
class Migration(migrations.Migration):
dependencies = [
("api", "0053_lighthouse_bedrock_openai_compatible"),
]
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"),
("oci", "Oracle Cloud Infrastructure"),
("iac", "IaC"),
],
default="aws",
),
),
migrations.RunSQL(
"ALTER TYPE provider ADD VALUE IF NOT EXISTS 'iac';",
reverse_sql=migrations.RunSQL.noop,
),
]
@@ -0,0 +1,32 @@
# Generated by Django 5.1.13 on 2025-11-05 08:37
from django.db import migrations
import api.db_utils
class Migration(migrations.Migration):
dependencies = [
("api", "0054_iac_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"),
],
default="aws",
),
),
]
+27 -3
View File
@@ -284,7 +284,9 @@ class Provider(RowLevelSecurityProtectedModel):
KUBERNETES = "kubernetes", _("Kubernetes")
M365 = "m365", _("M365")
GITHUB = "github", _("GitHub")
OCI = "oci", _("Oracle Cloud Infrastructure")
MONGODBATLAS = "mongodbatlas", _("MongoDB Atlas")
IAC = "iac", _("IaC")
ORACLECLOUD = "oraclecloud", _("Oracle Cloud Infrastructure")
@staticmethod
def validate_aws_uid(value):
@@ -356,14 +358,36 @@ class Provider(RowLevelSecurityProtectedModel):
)
@staticmethod
def validate_oci_uid(value):
def validate_iac_uid(value):
# Validate that it's a valid repository URL (git URL format)
if not re.match(
r"^(https?://|git@|ssh://)[^\s/]+[^\s]*\.git$|^(https?://)[^\s/]+[^\s]*$",
value,
):
raise ModelValidationError(
detail="IaC provider ID must be a valid repository URL (e.g., https://github.com/user/repo or https://github.com/user/repo.git).",
code="iac-uid",
pointer="/data/attributes/uid",
)
@staticmethod
def validate_oraclecloud_uid(value):
if not re.match(
r"^ocid1\.([a-z0-9_-]+)\.([a-z0-9_-]+)\.([a-z0-9_-]*)\.([a-z0-9]+)$", value
):
raise ModelValidationError(
detail="Oracle Cloud Infrastructure provider ID must be a valid tenancy OCID in the format: "
"ocid1.<resource_type>.<realm>.<region>.<unique_id>",
code="oci-uid",
code="oraclecloud-uid",
pointer="/data/attributes/uid",
)
@staticmethod
def validate_mongodbatlas_uid(value):
if not re.match(r"^[0-9a-fA-F]{24}$", value):
raise ModelValidationError(
detail="MongoDB Atlas organization ID must be a 24-character hexadecimal string.",
code="mongodbatlas-uid",
pointer="/data/attributes/uid",
)
File diff suppressed because it is too large. Load diff
+11 -3
View File
@@ -20,9 +20,11 @@ from prowler.providers.aws.aws_provider import AwsProvider
from prowler.providers.aws.lib.security_hub.security_hub import SecurityHubConnection
from prowler.providers.azure.azure_provider import AzureProvider
from prowler.providers.gcp.gcp_provider import GcpProvider
from prowler.providers.github.github_provider import GithubProvider
from prowler.providers.kubernetes.kubernetes_provider import KubernetesProvider
from prowler.providers.m365.m365_provider import M365Provider
from prowler.providers.oraclecloud.oci_provider import OciProvider
from prowler.providers.mongodbatlas.mongodbatlas_provider import MongodbatlasProvider
from prowler.providers.oraclecloud.oraclecloud_provider import OraclecloudProvider
class TestMergeDicts:
@@ -109,7 +111,9 @@ class TestReturnProwlerProvider:
(Provider.ProviderChoices.AZURE.value, AzureProvider),
(Provider.ProviderChoices.KUBERNETES.value, KubernetesProvider),
(Provider.ProviderChoices.M365.value, M365Provider),
(Provider.ProviderChoices.OCI.value, OciProvider),
(Provider.ProviderChoices.GITHUB.value, GithubProvider),
(Provider.ProviderChoices.MONGODBATLAS.value, MongodbatlasProvider),
(Provider.ProviderChoices.ORACLECLOUD.value, OraclecloudProvider),
],
)
def test_return_prowler_provider(self, provider_type, expected_provider):
@@ -206,9 +210,13 @@ class TestGetProwlerProviderKwargs:
{"organizations": ["provider_uid"]},
),
(
Provider.ProviderChoices.OCI.value,
Provider.ProviderChoices.ORACLECLOUD.value,
{},
),
(
Provider.ProviderChoices.MONGODBATLAS.value,
{"atlas_organization_id": "provider_uid"},
),
],
)
def test_get_prowler_provider_kwargs(self, provider_type, expected_extra_kwargs):
+79 -10
View File
@@ -1143,6 +1143,21 @@ class TestProviderViewSet:
"uid": "a12345678901234567890123456789012345678",
"alias": "Long Username",
},
{
"provider": "iac",
"uid": "https://github.com/user/repo.git",
"alias": "Git Repo",
},
{
"provider": "iac",
"uid": "https://gitlab.com/user/project",
"alias": "GitLab Repo",
},
{
"provider": "mongodbatlas",
"uid": "64b1d3c0e4b03b1234567890",
"alias": "Atlas Organization",
},
]
),
)
@@ -1292,6 +1307,51 @@ class TestProviderViewSet:
"github-uid",
"uid",
),
(
{
"provider": "iac",
"uid": "not-a-url",
"alias": "test",
},
"iac-uid",
"uid",
),
(
{
"provider": "iac",
"uid": "ftp://invalid-protocol.com/repo",
"alias": "test",
},
"iac-uid",
"uid",
),
(
{
"provider": "iac",
"uid": "http://",
"alias": "test",
},
"iac-uid",
"uid",
),
(
{
"provider": "mongodbatlas",
"uid": "64b1d3c0e4b03b123456789g",
"alias": "test",
},
"mongodbatlas-uid",
"uid",
),
(
{
"provider": "mongodbatlas",
"uid": "1234",
"alias": "test",
},
"mongodbatlas-uid",
"uid",
),
]
),
)
@@ -1465,22 +1525,22 @@ class TestProviderViewSet:
(
"uid.icontains",
"1",
6,
), # Updated: includes OCI provider with "1" in UID
7,
),
("alias", "aws_testing_1", 1),
("alias.icontains", "aws", 2),
("inserted_at", TODAY, 7), # Updated: 7 providers now (added OCI)
("inserted_at", TODAY, 8),
(
"inserted_at.gte",
"2024-01-01",
7,
), # Updated: 7 providers now (added OCI)
8,
),
("inserted_at.lte", "2024-01-01", 0),
(
"updated_at.gte",
"2024-01-01",
7,
), # Updated: 7 providers now (added OCI)
8,
),
("updated_at.lte", "2024-01-01", 0),
]
),
@@ -1985,7 +2045,7 @@ class TestProviderSecretViewSet:
),
# OCI with API key credentials (with key_content)
(
Provider.ProviderChoices.OCI.value,
Provider.ProviderChoices.ORACLECLOUD.value,
ProviderSecret.TypeChoices.STATIC,
{
"user": "ocid1.user.oc1..aaaaaaaakldibrbov4ubh25aqdeiroklxjngwka7u6w7no3glmdq3n5sxtkq",
@@ -1997,7 +2057,7 @@ class TestProviderSecretViewSet:
),
# OCI with API key credentials (with key_file)
(
Provider.ProviderChoices.OCI.value,
Provider.ProviderChoices.ORACLECLOUD.value,
ProviderSecret.TypeChoices.STATIC,
{
"user": "ocid1.user.oc1..aaaaaaaakldibrbov4ubh25aqdeiroklxjngwka7u6w7no3glmdq3n5sxtkq",
@@ -2009,7 +2069,7 @@ class TestProviderSecretViewSet:
),
# OCI with API key credentials (with passphrase)
(
Provider.ProviderChoices.OCI.value,
Provider.ProviderChoices.ORACLECLOUD.value,
ProviderSecret.TypeChoices.STATIC,
{
"user": "ocid1.user.oc1..aaaaaaaakldibrbov4ubh25aqdeiroklxjngwka7u6w7no3glmdq3n5sxtkq",
@@ -2020,6 +2080,15 @@ class TestProviderSecretViewSet:
"pass_phrase": "my-secure-passphrase",
},
),
# MongoDB Atlas credentials
(
Provider.ProviderChoices.MONGODBATLAS.value,
ProviderSecret.TypeChoices.STATIC,
{
"atlas_public_key": "public-key",
"atlas_private_key": "private-key",
},
),
],
)
def test_provider_secrets_create_valid(
+50 -11
View File
@@ -18,9 +18,11 @@ from prowler.providers.azure.azure_provider import AzureProvider
from prowler.providers.common.models import Connection
from prowler.providers.gcp.gcp_provider import GcpProvider
from prowler.providers.github.github_provider import GithubProvider
from prowler.providers.iac.iac_provider import IacProvider
from prowler.providers.kubernetes.kubernetes_provider import KubernetesProvider
from prowler.providers.m365.m365_provider import M365Provider
from prowler.providers.oraclecloud.oci_provider import OciProvider
from prowler.providers.mongodbatlas.mongodbatlas_provider import MongodbatlasProvider
from prowler.providers.oraclecloud.oraclecloud_provider import OraclecloudProvider
class CustomOAuth2Client(OAuth2Client):
@@ -66,9 +68,11 @@ def return_prowler_provider(
| AzureProvider
| GcpProvider
| GithubProvider
| IacProvider
| KubernetesProvider
| M365Provider
| OciProvider
| MongodbatlasProvider
| OraclecloudProvider
]:
"""Return the Prowler provider class based on the given provider type.
@@ -76,7 +80,7 @@ def return_prowler_provider(
provider (Provider): The provider object containing the provider type and associated secrets.
Returns:
AwsProvider | AzureProvider | GcpProvider | GithubProvider | KubernetesProvider | M365Provider | OciProvider: The corresponding provider class.
AwsProvider | AzureProvider | GcpProvider | GithubProvider | IacProvider | KubernetesProvider | M365Provider | OraclecloudProvider | MongodbatlasProvider: The corresponding provider class.
Raises:
ValueError: If the provider type specified in `provider.provider` is not supported.
@@ -94,8 +98,12 @@ def return_prowler_provider(
prowler_provider = M365Provider
case Provider.ProviderChoices.GITHUB.value:
prowler_provider = GithubProvider
case Provider.ProviderChoices.OCI.value:
prowler_provider = OciProvider
case Provider.ProviderChoices.MONGODBATLAS.value:
prowler_provider = MongodbatlasProvider
case Provider.ProviderChoices.IAC.value:
prowler_provider = IacProvider
case Provider.ProviderChoices.ORACLECLOUD.value:
prowler_provider = OraclecloudProvider
case _:
raise ValueError(f"Provider type {provider.provider} not supported")
return prowler_provider
@@ -132,6 +140,21 @@ def get_prowler_provider_kwargs(
**prowler_provider_kwargs,
"organizations": [provider.uid],
}
elif provider.provider == Provider.ProviderChoices.IAC.value:
# For IaC provider, uid contains the repository URL
# Extract the access token if present in the secret
prowler_provider_kwargs = {
"scan_repository_url": provider.uid,
}
if "access_token" in provider.secret.secret:
prowler_provider_kwargs["oauth_app_token"] = provider.secret.secret[
"access_token"
]
elif provider.provider == Provider.ProviderChoices.MONGODBATLAS.value:
prowler_provider_kwargs = {
**prowler_provider_kwargs,
"atlas_organization_id": provider.uid,
}
if mutelist_processor:
mutelist_content = mutelist_processor.configuration.get("Mutelist", {})
@@ -149,9 +172,11 @@ def initialize_prowler_provider(
| AzureProvider
| GcpProvider
| GithubProvider
| IacProvider
| KubernetesProvider
| M365Provider
| OciProvider
| MongodbatlasProvider
| OraclecloudProvider
):
"""Initialize a Prowler provider instance based on the given provider type.
@@ -160,8 +185,8 @@ def initialize_prowler_provider(
mutelist_processor (Processor): The mutelist processor object containing the mutelist configuration.
Returns:
AwsProvider | AzureProvider | GcpProvider | GithubProvider | KubernetesProvider | M365Provider | OciProvider: An instance of the corresponding provider class
(`AwsProvider`, `AzureProvider`, `GcpProvider`, `GithubProvider`, `KubernetesProvider`, `M365Provider` or `OciProvider`) initialized with the
AwsProvider | AzureProvider | GcpProvider | GithubProvider | IacProvider | KubernetesProvider | M365Provider | OraclecloudProvider | MongodbatlasProvider: An instance of the corresponding provider class
(`AwsProvider`, `AzureProvider`, `GcpProvider`, `GithubProvider`, `IacProvider`, `KubernetesProvider`, `M365Provider`, `OraclecloudProvider` or `MongodbatlasProvider`) initialized with the
provider's secrets.
"""
prowler_provider = return_prowler_provider(provider)
@@ -185,9 +210,23 @@ def prowler_provider_connection_test(provider: Provider) -> Connection:
except Provider.secret.RelatedObjectDoesNotExist as secret_error:
return Connection(is_connected=False, error=secret_error)
return prowler_provider.test_connection(
**prowler_provider_kwargs, provider_id=provider.uid, raise_on_exception=False
)
# For IaC provider, construct the kwargs properly for test_connection
if provider.provider == Provider.ProviderChoices.IAC.value:
# Don't pass repository_url from secret, use scan_repository_url with the UID
iac_test_kwargs = {
"scan_repository_url": provider.uid,
"raise_on_exception": False,
}
# Add access_token if present in the secret
if "access_token" in prowler_provider_kwargs:
iac_test_kwargs["access_token"] = prowler_provider_kwargs["access_token"]
return prowler_provider.test_connection(**iac_test_kwargs)
else:
return prowler_provider.test_connection(
**prowler_provider_kwargs,
provider_id=provider.uid,
raise_on_exception=False,
)
def prowler_integration_connection_test(integration: Integration) -> Connection:
@@ -239,6 +239,21 @@ from rest_framework_json_api import serializers
},
"required": ["github_app_id", "github_app_key"],
},
{
"type": "object",
"title": "IaC Repository Credentials",
"properties": {
"repository_url": {
"type": "string",
"description": "Repository URL to scan for IaC files.",
},
"access_token": {
"type": "string",
"description": "Optional access token for private repositories.",
},
},
"required": ["repository_url"],
},
{
"type": "object",
"title": "Oracle Cloud Infrastructure (OCI) API Key Credentials",
@@ -274,6 +289,21 @@ from rest_framework_json_api import serializers
},
"required": ["user", "fingerprint", "tenancy", "region"],
},
{
"type": "object",
"title": "MongoDB Atlas API Key",
"properties": {
"atlas_public_key": {
"type": "string",
"description": "MongoDB Atlas API public key.",
},
"atlas_private_key": {
"type": "string",
"description": "MongoDB Atlas API private key.",
},
},
"required": ["atlas_public_key", "atlas_private_key"],
},
]
}
)
+21 -1
View File
@@ -1362,12 +1362,16 @@ class BaseWriteProviderSecretSerializer(BaseWriteSerializer):
serializer = GCPProviderSecret(data=secret)
elif provider_type == Provider.ProviderChoices.GITHUB.value:
serializer = GithubProviderSecret(data=secret)
elif provider_type == Provider.ProviderChoices.IAC.value:
serializer = IacProviderSecret(data=secret)
elif provider_type == Provider.ProviderChoices.KUBERNETES.value:
serializer = KubernetesProviderSecret(data=secret)
elif provider_type == Provider.ProviderChoices.M365.value:
serializer = M365ProviderSecret(data=secret)
elif provider_type == Provider.ProviderChoices.OCI.value:
elif provider_type == Provider.ProviderChoices.ORACLECLOUD.value:
serializer = OracleCloudProviderSecret(data=secret)
elif provider_type == Provider.ProviderChoices.MONGODBATLAS.value:
serializer = MongoDBAtlasProviderSecret(data=secret)
else:
raise serializers.ValidationError(
{"provider": f"Provider type not supported {provider_type}"}
@@ -1464,6 +1468,14 @@ class GCPServiceAccountProviderSecret(serializers.Serializer):
resource_name = "provider-secrets"
class MongoDBAtlasProviderSecret(serializers.Serializer):
atlas_public_key = serializers.CharField()
atlas_private_key = serializers.CharField()
class Meta:
resource_name = "provider-secrets"
class KubernetesProviderSecret(serializers.Serializer):
kubeconfig_content = serializers.CharField()
@@ -1481,6 +1493,14 @@ class GithubProviderSecret(serializers.Serializer):
resource_name = "provider-secrets"
class IacProviderSecret(serializers.Serializer):
repository_url = serializers.CharField()
access_token = serializers.CharField(required=False)
class Meta:
resource_name = "provider-secrets"
class OracleCloudProviderSecret(serializers.Serializer):
user = serializers.CharField()
fingerprint = serializers.CharField()
+17 -2
View File
@@ -501,13 +501,28 @@ def providers_fixture(tenants_fixture):
tenant_id=tenant.id,
)
provider7 = Provider.objects.create(
provider="oci",
provider="oraclecloud",
uid="ocid1.tenancy.oc1..aaaaaaaa3dwoazoox4q7wrvriywpokp5grlhgnkwtyt6dmwyou7no6mdmzda",
alias="oci_testing",
tenant_id=tenant.id,
)
provider8 = Provider.objects.create(
provider="mongodbatlas",
uid="64b1d3c0e4b03b1234567890",
alias="mongodbatlas_testing",
tenant_id=tenant.id,
)
return provider1, provider2, provider3, provider4, provider5, provider6, provider7
return (
provider1,
provider2,
provider3,
provider4,
provider5,
provider6,
provider7,
provider8,
)
@pytest.fixture
+7 -3
View File
@@ -32,7 +32,7 @@ from prowler.lib.outputs.compliance.cis.cis_gcp import GCPCIS
from prowler.lib.outputs.compliance.cis.cis_github import GithubCIS
from prowler.lib.outputs.compliance.cis.cis_kubernetes import KubernetesCIS
from prowler.lib.outputs.compliance.cis.cis_m365 import M365CIS
from prowler.lib.outputs.compliance.cis.cis_oci import OCICIS
from prowler.lib.outputs.compliance.cis.cis_oraclecloud import OracleCloudCIS
from prowler.lib.outputs.compliance.ens.ens_aws import AWSENS
from prowler.lib.outputs.compliance.ens.ens_azure import AzureENS
from prowler.lib.outputs.compliance.ens.ens_gcp import GCPENS
@@ -113,8 +113,12 @@ COMPLIANCE_CLASS_MAP = {
"github": [
(lambda name: name.startswith("cis_"), GithubCIS),
],
"oci": [
(lambda name: name.startswith("cis_"), OCICIS),
"iac": [
# IaC provider doesn't have specific compliance frameworks yet
# Trivy handles its own compliance checks
],
"oraclecloud": [
(lambda name: name.startswith("cis_"), OracleCloudCIS),
],
}
+25
View File
@@ -0,0 +1,25 @@
import warnings
from dashboard.common_methods import get_section_containers_format3
warnings.filterwarnings("ignore")
def get_table(data):
aux = data[
[
"REQUIREMENTS_ID",
"REQUIREMENTS_ATTRIBUTES_SECTION",
"REQUIREMENTS_DESCRIPTION",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
].copy()
return get_section_containers_format3(
aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ID"
)
@@ -1,5 +1,5 @@
---
title: 'Installation'
title: "Installation"
---
### Installation
@@ -9,41 +9,40 @@ Prowler App supports multiple installation methods based on your environment.
Refer to the [Prowler App Tutorial](/user-guide/tutorials/prowler-app) for detailed usage instructions.
<Warning>
Prowler configuration is based in `.env` files. Every version of Prowler can have differences on that file, so, please, use the file that corresponds with that version or repository branch or tag.
Prowler configuration is based in `.env` files. Every version of Prowler can have differences on that file, so, please, use the file that corresponds with that version or repository branch or tag.
</Warning>
<Tabs>
<Tab title="Docker Compose">
_Requirements_:
* `Docker Compose` installed: https://docs.docker.com/compose/install/.
- `Docker Compose` installed: https://docs.docker.com/compose/install/.
_Commands_:
``` bash
curl -LO https://raw.githubusercontent.com/prowler-cloud/prowler/refs/heads/master/docker-compose.yml
curl -LO https://raw.githubusercontent.com/prowler-cloud/prowler/refs/heads/master/.env
```bash
VERSION=$(curl -s https://api.github.com/repos/prowler-cloud/prowler/releases/latest | jq -r .tag_name)
curl -sLO "https://raw.githubusercontent.com/prowler-cloud/prowler/refs/tags/${VERSION}/docker-compose.yml"
curl -sLO "https://raw.githubusercontent.com/prowler-cloud/prowler/refs/tags/${VERSION}/.env"
docker compose up -d
```
> Containers are built for `linux/amd64`. If your workstation's architecture is different, please set `DOCKER_DEFAULT_PLATFORM=linux/amd64` in your environment or use the `--platform linux/amd64` flag in the docker command.
</Tab>
<Tab title="GitHub">
_Requirements_:
* `git` installed.
* `poetry` installed: [poetry installation](https://python-poetry.org/docs/#installation).
* `npm` installed: [npm installation](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).
* `Docker Compose` installed: https://docs.docker.com/compose/install/.
- `git` installed.
- `poetry` installed: [poetry installation](https://python-poetry.org/docs/#installation).
- `npm` installed: [npm installation](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).
- `Docker Compose` installed: https://docs.docker.com/compose/install/.
<Warning>
Make sure to have `api/.env` and `ui/.env.local` files with the required environment variables. You can find the required environment variables in the [`api/.env.template`](https://github.com/prowler-cloud/prowler/blob/master/api/.env.example) and [`ui/.env.template`](https://github.com/prowler-cloud/prowler/blob/master/ui/.env.template) files.
Make sure to have `api/.env` and `ui/.env.local` files with the required environment variables. You can find the required environment variables in the [`api/.env.template`](https://github.com/prowler-cloud/prowler/blob/master/api/.env.example) and [`ui/.env.template`](https://github.com/prowler-cloud/prowler/blob/master/ui/.env.template) files.
</Warning>
_Commands to run the API_:
``` bash
```bash
git clone https://github.com/prowler-cloud/prowler \
cd prowler/api \
poetry install \
@@ -57,17 +56,15 @@ Prowler configuration is based in `.env` files. Every version of Prowler can hav
```
<Warning>
Starting from Poetry v2.0.0, `poetry shell` has been deprecated in favor of `poetry env activate`.
Starting from Poetry v2.0.0, `poetry shell` has been deprecated in favor of `poetry env activate`.
If your poetry version is below 2.0.0 you must keep using `poetry shell` to activate your environment.
In case you have any doubts, consult the Poetry environment activation guide: https://python-poetry.org/docs/managing-environments/#activating-the-environment
If your poetry version is below 2.0.0 you must keep using `poetry shell` to activate your environment. In case you have any doubts, consult the Poetry environment activation guide: https://python-poetry.org/docs/managing-environments/#activating-the-environment
</Warning>
> Now, you can access the API documentation at http://localhost:8080/api/v1/docs.
_Commands to run the API Worker_:
``` bash
```bash
git clone https://github.com/prowler-cloud/prowler \
cd prowler/api \
poetry install \
@@ -80,7 +77,7 @@ Prowler configuration is based in `.env` files. Every version of Prowler can hav
_Commands to run the API Scheduler_:
``` bash
```bash
git clone https://github.com/prowler-cloud/prowler \
cd prowler/api \
poetry install \
@@ -93,7 +90,7 @@ Prowler configuration is based in `.env` files. Every version of Prowler can hav
_Commands to run the UI_:
``` bash
```bash
git clone https://github.com/prowler-cloud/prowler \
cd prowler/ui \
npm install \
@@ -104,10 +101,11 @@ Prowler configuration is based in `.env` files. Every version of Prowler can hav
> Enjoy Prowler App at http://localhost:3000 by signing up with your email and password.
<Warning>
Google and GitHub authentication is only available in [Prowler Cloud](https://prowler.com).
Google and GitHub authentication is only available in [Prowler Cloud](https://prowler.com).
</Warning>
</Tab>
</Tabs>
### Update Prowler App
Upgrade Prowler App installation using one of two options:
@@ -129,13 +127,12 @@ docker compose pull --policy always
The `--policy always` flag ensures that Docker pulls the latest images even if they already exist locally.
<Note>
**What Gets Preserved During Upgrade**
Everything is preserved, nothing will be deleted after the update.
**What Gets Preserved During Upgrade**
Everything is preserved, nothing will be deleted after the update.
</Note>
### Troubleshooting
If containers don't start, check logs for errors:
@@ -155,7 +152,6 @@ docker compose pull
docker compose up -d
```
### Container versions
The available versions of Prowler CLI are the following:
@@ -171,6 +167,5 @@ The available versions of Prowler CLI are the following:
The container images are available here:
- Prowler App:
- [DockerHub - Prowler UI](https://hub.docker.com/r/prowlercloud/prowler-ui/tags)
- [DockerHub - Prowler API](https://hub.docker.com/r/prowlercloud/prowler-api/tags)
- [DockerHub - Prowler UI](https://hub.docker.com/r/prowlercloud/prowler-ui/tags)
- [DockerHub - Prowler API](https://hub.docker.com/r/prowlercloud/prowler-api/tags)
Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

+3 -3
View File
@@ -32,8 +32,8 @@ The supported providers right now are:
| [M365](/user-guide/providers/microsoft365/getting-started-m365) | Official | UI, API, CLI |
| [Github](/user-guide/providers/github/getting-started-github) | Official | UI, API, CLI |
| [Oracle Cloud](/user-guide/providers/oci/getting-started-oci) | Official | UI, API, CLI |
| [Infra as Code](/user-guide/providers/iac/getting-started-iac) | Official | CLI |
| [MongoDB Atlas](/user-guide/providers/mongodbatlas/getting-started-mongodbatlas) | Official | CLI |
| [Infra as Code](/user-guide/providers/iac/getting-started-iac) | Official | UI, API, CLI |
| [MongoDB Atlas](/user-guide/providers/mongodbatlas/getting-started-mongodbatlas) | Official | CLI, API |
| [LLM](/user-guide/providers/llm/getting-started-llm) | Official | CLI |
| **NHN** | Unofficial | CLI |
@@ -48,4 +48,4 @@ For more information about the checks and compliance of each provider visit [Pro
<Card title="Development Guide" icon="pen-to-square" href="/developer-guide/introduction">
Interested in contributing to Prowler?
</Card>
</Columns>
</Columns>
@@ -1,6 +1,7 @@
---
title: "Getting Started with the IaC Provider"
---
import { VersionBadge } from "/snippets/version-badge.mdx"
Prowler's Infrastructure as Code (IaC) provider enables scanning of local or remote infrastructure code for security and compliance issues using [Trivy](https://trivy.dev/). This provider supports a wide range of IaC frameworks, allowing assessment of code before deployment.
@@ -22,8 +23,46 @@ The IaC provider leverages [Trivy](https://trivy.dev/latest/docs/scanner/vulnera
- Mutelist logic ([filtering](https://trivy.dev/latest/docs/configuration/filtering/)) is handled by Trivy, not Prowler.
- Results are output in the same formats as other Prowler providers (CSV, JSON, HTML, etc.).
## Prowler App
<VersionBadge version="5.14.0" />
### Step 1: Access Prowler Cloud/App
1. Navigate to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler App](/user-guide/tutorials/prowler-app)
2. Go to "Configuration" > "Cloud Providers"
![Cloud Providers Page](/images/prowler-app/cloud-providers-page.png)
3. Click "Add Cloud Provider"
![Add a Cloud Provider](/images/prowler-app/add-cloud-provider.png)
4. Select "Infrastructure as Code"
![Select Infrastructure as Code](/images/providers/select-iac.png)
5. Add the Repository URL and an optional alias, then click "Next"
![Add IaC Repository URL](/images/providers/add-iac-repo.png)
### Step 2: Enter Authentication Details
6. Optionally provide the [authentication](/user-guide/providers/iac/authentication) details for private repositories, then click "Next"
![IaC Authentication](/images/providers/iac-authentication.png)
### Step 3: Verify Connection & Start Scan
7. Review the provider configuration and click "Launch scan" to initiate the scan
![Verify Connection & Start Scan](/images/providers/iac-verify-connection.png)
## Prowler CLI
<VersionBadge version="5.8.0" />
### Usage
Use the `iac` argument to run Prowler with the IaC provider. Specify the directory or repository to scan, frameworks to include, and paths to exclude.
Generated
+119 -98
View File
@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand.
# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand.
[[package]]
name = "about-time"
@@ -4420,106 +4420,127 @@ typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""}
[[package]]
name = "regex"
version = "2024.11.6"
version = "2025.9.18"
description = "Alternative regular expression module, to replace re."
optional = false
python-versions = ">=3.8"
groups = ["dev"]
python-versions = ">=3.9"
groups = ["dev", "docs"]
files = [
{file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"},
{file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"},
{file = "regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e"},
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde"},
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e"},
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2"},
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf"},
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c"},
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86"},
{file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67"},
{file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d"},
{file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2"},
{file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008"},
{file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62"},
{file = "regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e"},
{file = "regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519"},
{file = "regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638"},
{file = "regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7"},
{file = "regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20"},
{file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114"},
{file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3"},
{file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f"},
{file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"},
{file = "regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55"},
{file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89"},
{file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d"},
{file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34"},
{file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d"},
{file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45"},
{file = "regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9"},
{file = "regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60"},
{file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"},
{file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"},
{file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"},
{file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"},
{file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"},
{file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"},
{file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"},
{file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"},
{file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"},
{file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"},
{file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"},
{file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"},
{file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"},
{file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"},
{file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"},
{file = "regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84"},
{file = "regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4"},
{file = "regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0"},
{file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0"},
{file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7"},
{file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7"},
{file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c"},
{file = "regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3"},
{file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07"},
{file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e"},
{file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6"},
{file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4"},
{file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d"},
{file = "regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff"},
{file = "regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a"},
{file = "regex-2024.11.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b"},
{file = "regex-2024.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3"},
{file = "regex-2024.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467"},
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd"},
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf"},
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd"},
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6"},
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f"},
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5"},
{file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df"},
{file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773"},
{file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c"},
{file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc"},
{file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f"},
{file = "regex-2024.11.6-cp38-cp38-win32.whl", hash = "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4"},
{file = "regex-2024.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001"},
{file = "regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839"},
{file = "regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e"},
{file = "regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf"},
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b"},
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0"},
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b"},
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef"},
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48"},
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13"},
{file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2"},
{file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95"},
{file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9"},
{file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f"},
{file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b"},
{file = "regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57"},
{file = "regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983"},
{file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"},
{file = "regex-2025.9.18-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:12296202480c201c98a84aecc4d210592b2f55e200a1d193235c4db92b9f6788"},
{file = "regex-2025.9.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:220381f1464a581f2ea988f2220cf2a67927adcef107d47d6897ba5a2f6d51a4"},
{file = "regex-2025.9.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:87f681bfca84ebd265278b5daa1dcb57f4db315da3b5d044add7c30c10442e61"},
{file = "regex-2025.9.18-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34d674cbba70c9398074c8a1fcc1a79739d65d1105de2a3c695e2b05ea728251"},
{file = "regex-2025.9.18-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:385c9b769655cb65ea40b6eea6ff763cbb6d69b3ffef0b0db8208e1833d4e746"},
{file = "regex-2025.9.18-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8900b3208e022570ae34328712bef6696de0804c122933414014bae791437ab2"},
{file = "regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c204e93bf32cd7a77151d44b05eb36f469d0898e3fba141c026a26b79d9914a0"},
{file = "regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3acc471d1dd7e5ff82e6cacb3b286750decd949ecd4ae258696d04f019817ef8"},
{file = "regex-2025.9.18-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6479d5555122433728760e5f29edb4c2b79655a8deb681a141beb5c8a025baea"},
{file = "regex-2025.9.18-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:431bd2a8726b000eb6f12429c9b438a24062a535d06783a93d2bcbad3698f8a8"},
{file = "regex-2025.9.18-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0cc3521060162d02bd36927e20690129200e5ac9d2c6d32b70368870b122db25"},
{file = "regex-2025.9.18-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a021217b01be2d51632ce056d7a837d3fa37c543ede36e39d14063176a26ae29"},
{file = "regex-2025.9.18-cp310-cp310-win32.whl", hash = "sha256:4a12a06c268a629cb67cc1d009b7bb0be43e289d00d5111f86a2efd3b1949444"},
{file = "regex-2025.9.18-cp310-cp310-win_amd64.whl", hash = "sha256:47acd811589301298c49db2c56bde4f9308d6396da92daf99cba781fa74aa450"},
{file = "regex-2025.9.18-cp310-cp310-win_arm64.whl", hash = "sha256:16bd2944e77522275e5ee36f867e19995bcaa533dcb516753a26726ac7285442"},
{file = "regex-2025.9.18-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:51076980cd08cd13c88eb7365427ae27f0d94e7cebe9ceb2bb9ffdae8fc4d82a"},
{file = "regex-2025.9.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:828446870bd7dee4e0cbeed767f07961aa07f0ea3129f38b3ccecebc9742e0b8"},
{file = "regex-2025.9.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c28821d5637866479ec4cc23b8c990f5bc6dd24e5e4384ba4a11d38a526e1414"},
{file = "regex-2025.9.18-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:726177ade8e481db669e76bf99de0b278783be8acd11cef71165327abd1f170a"},
{file = "regex-2025.9.18-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f5cca697da89b9f8ea44115ce3130f6c54c22f541943ac8e9900461edc2b8bd4"},
{file = "regex-2025.9.18-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dfbde38f38004703c35666a1e1c088b778e35d55348da2b7b278914491698d6a"},
{file = "regex-2025.9.18-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f2f422214a03fab16bfa495cfec72bee4aaa5731843b771860a471282f1bf74f"},
{file = "regex-2025.9.18-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a295916890f4df0902e4286bc7223ee7f9e925daa6dcdec4192364255b70561a"},
{file = "regex-2025.9.18-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5db95ff632dbabc8c38c4e82bf545ab78d902e81160e6e455598014f0abe66b9"},
{file = "regex-2025.9.18-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fb967eb441b0f15ae610b7069bdb760b929f267efbf522e814bbbfffdf125ce2"},
{file = "regex-2025.9.18-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f04d2f20da4053d96c08f7fde6e1419b7ec9dbcee89c96e3d731fca77f411b95"},
{file = "regex-2025.9.18-cp311-cp311-win32.whl", hash = "sha256:895197241fccf18c0cea7550c80e75f185b8bd55b6924fcae269a1a92c614a07"},
{file = "regex-2025.9.18-cp311-cp311-win_amd64.whl", hash = "sha256:7e2b414deae99166e22c005e154a5513ac31493db178d8aec92b3269c9cce8c9"},
{file = "regex-2025.9.18-cp311-cp311-win_arm64.whl", hash = "sha256:fb137ec7c5c54f34a25ff9b31f6b7b0c2757be80176435bf367111e3f71d72df"},
{file = "regex-2025.9.18-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:436e1b31d7efd4dcd52091d076482031c611dde58bf9c46ca6d0a26e33053a7e"},
{file = "regex-2025.9.18-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c190af81e5576b9c5fdc708f781a52ff20f8b96386c6e2e0557a78402b029f4a"},
{file = "regex-2025.9.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e4121f1ce2b2b5eec4b397cc1b277686e577e658d8f5870b7eb2d726bd2300ab"},
{file = "regex-2025.9.18-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:300e25dbbf8299d87205e821a201057f2ef9aa3deb29caa01cd2cac669e508d5"},
{file = "regex-2025.9.18-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7b47fcf9f5316c0bdaf449e879407e1b9937a23c3b369135ca94ebc8d74b1742"},
{file = "regex-2025.9.18-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:57a161bd3acaa4b513220b49949b07e252165e6b6dc910ee7617a37ff4f5b425"},
{file = "regex-2025.9.18-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f130c3a7845ba42de42f380fff3c8aebe89a810747d91bcf56d40a069f15352"},
{file = "regex-2025.9.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f96fa342b6f54dcba928dd452e8d8cb9f0d63e711d1721cd765bb9f73bb048d"},
{file = "regex-2025.9.18-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0f0d676522d68c207828dcd01fb6f214f63f238c283d9f01d85fc664c7c85b56"},
{file = "regex-2025.9.18-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:40532bff8a1a0621e7903ae57fce88feb2e8a9a9116d341701302c9302aef06e"},
{file = "regex-2025.9.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:039f11b618ce8d71a1c364fdee37da1012f5a3e79b1b2819a9f389cd82fd6282"},
{file = "regex-2025.9.18-cp312-cp312-win32.whl", hash = "sha256:e1dd06f981eb226edf87c55d523131ade7285137fbde837c34dc9d1bf309f459"},
{file = "regex-2025.9.18-cp312-cp312-win_amd64.whl", hash = "sha256:3d86b5247bf25fa3715e385aa9ff272c307e0636ce0c9595f64568b41f0a9c77"},
{file = "regex-2025.9.18-cp312-cp312-win_arm64.whl", hash = "sha256:032720248cbeeae6444c269b78cb15664458b7bb9ed02401d3da59fe4d68c3a5"},
{file = "regex-2025.9.18-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2a40f929cd907c7e8ac7566ac76225a77701a6221bca937bdb70d56cb61f57b2"},
{file = "regex-2025.9.18-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c90471671c2cdf914e58b6af62420ea9ecd06d1554d7474d50133ff26ae88feb"},
{file = "regex-2025.9.18-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a351aff9e07a2dabb5022ead6380cff17a4f10e4feb15f9100ee56c4d6d06af"},
{file = "regex-2025.9.18-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc4b8e9d16e20ddfe16430c23468a8707ccad3365b06d4536142e71823f3ca29"},
{file = "regex-2025.9.18-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b8cdbddf2db1c5e80338ba2daa3cfa3dec73a46fff2a7dda087c8efbf12d62f"},
{file = "regex-2025.9.18-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a276937d9d75085b2c91fb48244349c6954f05ee97bba0963ce24a9d915b8b68"},
{file = "regex-2025.9.18-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92a8e375ccdc1256401c90e9dc02b8642894443d549ff5e25e36d7cf8a80c783"},
{file = "regex-2025.9.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0dc6893b1f502d73037cf807a321cdc9be29ef3d6219f7970f842475873712ac"},
{file = "regex-2025.9.18-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a61e85bfc63d232ac14b015af1261f826260c8deb19401c0597dbb87a864361e"},
{file = "regex-2025.9.18-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1ef86a9ebc53f379d921fb9a7e42b92059ad3ee800fcd9e0fe6181090e9f6c23"},
{file = "regex-2025.9.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d3bc882119764ba3a119fbf2bd4f1b47bc56c1da5d42df4ed54ae1e8e66fdf8f"},
{file = "regex-2025.9.18-cp313-cp313-win32.whl", hash = "sha256:3810a65675845c3bdfa58c3c7d88624356dd6ee2fc186628295e0969005f928d"},
{file = "regex-2025.9.18-cp313-cp313-win_amd64.whl", hash = "sha256:16eaf74b3c4180ede88f620f299e474913ab6924d5c4b89b3833bc2345d83b3d"},
{file = "regex-2025.9.18-cp313-cp313-win_arm64.whl", hash = "sha256:4dc98ba7dd66bd1261927a9f49bd5ee2bcb3660f7962f1ec02617280fc00f5eb"},
{file = "regex-2025.9.18-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:fe5d50572bc885a0a799410a717c42b1a6b50e2f45872e2b40f4f288f9bce8a2"},
{file = "regex-2025.9.18-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b9d9a2d6cda6621551ca8cf7a06f103adf72831153f3c0d982386110870c4d3"},
{file = "regex-2025.9.18-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:13202e4c4ac0ef9a317fff817674b293c8f7e8c68d3190377d8d8b749f566e12"},
{file = "regex-2025.9.18-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:874ff523b0fecffb090f80ae53dc93538f8db954c8bb5505f05b7787ab3402a0"},
{file = "regex-2025.9.18-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d13ab0490128f2bb45d596f754148cd750411afc97e813e4b3a61cf278a23bb6"},
{file = "regex-2025.9.18-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:05440bc172bc4b4b37fb9667e796597419404dbba62e171e1f826d7d2a9ebcef"},
{file = "regex-2025.9.18-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5514b8e4031fdfaa3d27e92c75719cbe7f379e28cacd939807289bce76d0e35a"},
{file = "regex-2025.9.18-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:65d3c38c39efce73e0d9dc019697b39903ba25b1ad45ebbd730d2cf32741f40d"},
{file = "regex-2025.9.18-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ae77e447ebc144d5a26d50055c6ddba1d6ad4a865a560ec7200b8b06bc529368"},
{file = "regex-2025.9.18-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e3ef8cf53dc8df49d7e28a356cf824e3623764e9833348b655cfed4524ab8a90"},
{file = "regex-2025.9.18-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9feb29817df349c976da9a0debf775c5c33fc1c8ad7b9f025825da99374770b7"},
{file = "regex-2025.9.18-cp313-cp313t-win32.whl", hash = "sha256:168be0d2f9b9d13076940b1ed774f98595b4e3c7fc54584bba81b3cc4181742e"},
{file = "regex-2025.9.18-cp313-cp313t-win_amd64.whl", hash = "sha256:d59ecf3bb549e491c8104fea7313f3563c7b048e01287db0a90485734a70a730"},
{file = "regex-2025.9.18-cp313-cp313t-win_arm64.whl", hash = "sha256:dbef80defe9fb21310948a2595420b36c6d641d9bea4c991175829b2cc4bc06a"},
{file = "regex-2025.9.18-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c6db75b51acf277997f3adcd0ad89045d856190d13359f15ab5dda21581d9129"},
{file = "regex-2025.9.18-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8f9698b6f6895d6db810e0bda5364f9ceb9e5b11328700a90cae573574f61eea"},
{file = "regex-2025.9.18-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29cd86aa7cb13a37d0f0d7c21d8d949fe402ffa0ea697e635afedd97ab4b69f1"},
{file = "regex-2025.9.18-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c9f285a071ee55cd9583ba24dde006e53e17780bb309baa8e4289cd472bcc47"},
{file = "regex-2025.9.18-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5adf266f730431e3be9021d3e5b8d5ee65e563fec2883ea8093944d21863b379"},
{file = "regex-2025.9.18-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1137cabc0f38807de79e28d3f6e3e3f2cc8cfb26bead754d02e6d1de5f679203"},
{file = "regex-2025.9.18-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7cc9e5525cada99699ca9223cce2d52e88c52a3d2a0e842bd53de5497c604164"},
{file = "regex-2025.9.18-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bbb9246568f72dce29bcd433517c2be22c7791784b223a810225af3b50d1aafb"},
{file = "regex-2025.9.18-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6a52219a93dd3d92c675383efff6ae18c982e2d7651c792b1e6d121055808743"},
{file = "regex-2025.9.18-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:ae9b3840c5bd456780e3ddf2f737ab55a79b790f6409182012718a35c6d43282"},
{file = "regex-2025.9.18-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d488c236ac497c46a5ac2005a952c1a0e22a07be9f10c3e735bc7d1209a34773"},
{file = "regex-2025.9.18-cp314-cp314-win32.whl", hash = "sha256:0c3506682ea19beefe627a38872d8da65cc01ffa25ed3f2e422dffa1474f0788"},
{file = "regex-2025.9.18-cp314-cp314-win_amd64.whl", hash = "sha256:57929d0f92bebb2d1a83af372cd0ffba2263f13f376e19b1e4fa32aec4efddc3"},
{file = "regex-2025.9.18-cp314-cp314-win_arm64.whl", hash = "sha256:6a4b44df31d34fa51aa5c995d3aa3c999cec4d69b9bd414a8be51984d859f06d"},
{file = "regex-2025.9.18-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b176326bcd544b5e9b17d6943f807697c0cb7351f6cfb45bf5637c95ff7e6306"},
{file = "regex-2025.9.18-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:0ffd9e230b826b15b369391bec167baed57c7ce39efc35835448618860995946"},
{file = "regex-2025.9.18-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ec46332c41add73f2b57e2f5b642f991f6b15e50e9f86285e08ffe3a512ac39f"},
{file = "regex-2025.9.18-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b80fa342ed1ea095168a3f116637bd1030d39c9ff38dc04e54ef7c521e01fc95"},
{file = "regex-2025.9.18-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4d97071c0ba40f0cf2a93ed76e660654c399a0a04ab7d85472239460f3da84b"},
{file = "regex-2025.9.18-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0ac936537ad87cef9e0e66c5144484206c1354224ee811ab1519a32373e411f3"},
{file = "regex-2025.9.18-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dec57f96d4def58c422d212d414efe28218d58537b5445cf0c33afb1b4768571"},
{file = "regex-2025.9.18-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:48317233294648bf7cd068857f248e3a57222259a5304d32c7552e2284a1b2ad"},
{file = "regex-2025.9.18-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:274687e62ea3cf54846a9b25fc48a04459de50af30a7bd0b61a9e38015983494"},
{file = "regex-2025.9.18-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a78722c86a3e7e6aadf9579e3b0ad78d955f2d1f1a8ca4f67d7ca258e8719d4b"},
{file = "regex-2025.9.18-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:06104cd203cdef3ade989a1c45b6215bf42f8b9dd705ecc220c173233f7cba41"},
{file = "regex-2025.9.18-cp314-cp314t-win32.whl", hash = "sha256:2e1eddc06eeaffd249c0adb6fafc19e2118e6308c60df9db27919e96b5656096"},
{file = "regex-2025.9.18-cp314-cp314t-win_amd64.whl", hash = "sha256:8620d247fb8c0683ade51217b459cb4a1081c0405a3072235ba43a40d355c09a"},
{file = "regex-2025.9.18-cp314-cp314t-win_arm64.whl", hash = "sha256:b7531a8ef61de2c647cdf68b3229b071e46ec326b3138b2180acb4275f470b01"},
{file = "regex-2025.9.18-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3dbcfcaa18e9480669030d07371713c10b4f1a41f791ffa5cb1a99f24e777f40"},
{file = "regex-2025.9.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1e85f73ef7095f0380208269055ae20524bfde3f27c5384126ddccf20382a638"},
{file = "regex-2025.9.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9098e29b3ea4ffffeade423f6779665e2a4f8db64e699c0ed737ef0db6ba7b12"},
{file = "regex-2025.9.18-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90b6b7a2d0f45b7ecaaee1aec6b362184d6596ba2092dd583ffba1b78dd0231c"},
{file = "regex-2025.9.18-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c81b892af4a38286101502eae7aec69f7cd749a893d9987a92776954f3943408"},
{file = "regex-2025.9.18-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3b524d010973f2e1929aeb635418d468d869a5f77b52084d9f74c272189c251d"},
{file = "regex-2025.9.18-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b498437c026a3d5d0be0020023ff76d70ae4d77118e92f6f26c9d0423452446"},
{file = "regex-2025.9.18-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0716e4d6e58853d83f6563f3cf25c281ff46cf7107e5f11879e32cb0b59797d9"},
{file = "regex-2025.9.18-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:065b6956749379d41db2625f880b637d4acc14c0a4de0d25d609a62850e96d36"},
{file = "regex-2025.9.18-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d4a691494439287c08ddb9b5793da605ee80299dd31e95fa3f323fac3c33d9d4"},
{file = "regex-2025.9.18-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ef8d10cc0989565bcbe45fb4439f044594d5c2b8919d3d229ea2c4238f1d55b0"},
{file = "regex-2025.9.18-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4baeb1b16735ac969a7eeecc216f1f8b7caf60431f38a2671ae601f716a32d25"},
{file = "regex-2025.9.18-cp39-cp39-win32.whl", hash = "sha256:8e5f41ad24a1e0b5dfcf4c4e5d9f5bd54c895feb5708dd0c1d0d35693b24d478"},
{file = "regex-2025.9.18-cp39-cp39-win_amd64.whl", hash = "sha256:50e8290707f2fb8e314ab3831e594da71e062f1d623b05266f8cfe4db4949afd"},
{file = "regex-2025.9.18-cp39-cp39-win_arm64.whl", hash = "sha256:039a9d7195fd88c943d7c777d4941e8ef736731947becce773c31a1009cb3c35"},
{file = "regex-2025.9.18.tar.gz", hash = "sha256:c5ba23274c61c6fef447ba6a39333297d0c247f53059dba0bca415cac511edc4"},
]
[[package]]
+23 -1
View File
@@ -13,6 +13,12 @@ All notable changes to the **Prowler SDK** are documented in this file.
- `cloudstorage_bucket_logging_enabled` check for GCP provider [(#9091)](https://github.com/prowler-cloud/prowler/pull/9091)
- C5 compliance framework for Azure provider [(#9081)](https://github.com/prowler-cloud/prowler/pull/9081)
- C5 compliance framework for the GCP provider [(#9097)](https://github.com/prowler-cloud/prowler/pull/9097)
- `organization_repository_creation_limited` check for GitHub provider [(#8844)](https://github.com/prowler-cloud/prowler/pull/8844)
- HIPAA compliance framework for the GCP provider [(#8955)](https://github.com/prowler-cloud/prowler/pull/8955)
- Add organization ID parameter for MongoDB Atlas provider [(#9167)](https://github.com/prowler-cloud/prowler/pull/9167)
- Add multiple compliance improvements [(#9145)](https://github.com/prowler-cloud/prowler/pull/9145)
- Added validation for invalid checks, services, and categories in `load_checks_to_execute` function [(#8971)](https://github.com/prowler-cloud/prowler/pull/8971)
- NIST CSF 2.0 compliance framework for the AWS provider [(#9185)](https://github.com/prowler-cloud/prowler/pull/9185)
### Changed
- Update AWS Direct Connect service metadata to new format [(#8855)](https://github.com/prowler-cloud/prowler/pull/8855)
@@ -31,17 +37,33 @@ All notable changes to the **Prowler SDK** are documented in this file.
- Update AWS FMS service metadata to new format [(#9005)](https://github.com/prowler-cloud/prowler/pull/9005)
- Update AWS FSx service metadata to new format [(#9006)](https://github.com/prowler-cloud/prowler/pull/9006)
- Update AWS Glacier service metadata to new format [(#9007)](https://github.com/prowler-cloud/prowler/pull/9007)
- Update oraclecloud analytics service metadata to new format [(#9114)](https://github.com/prowler-cloud/prowler/pull/9114)
- Update AWS CodeArtifact service metadata to new format [(#8850)](https://github.com/prowler-cloud/prowler/pull/8850)
- Rename OCI provider to oraclecloud with oci alias [(#9126)](https://github.com/prowler-cloud/prowler/pull/9126)
---
## [v5.13.1] (Prowler UNRELEASED)
## [v5.13.2] (Prowler UNRELEASED)
### Fixed
- Check `check_name` has no `resource_name` error for GCP provider [(#9169)](https://github.com/prowler-cloud/prowler/pull/9169)
---
## [v5.13.1] (Prowler v5.13.1)
### Fixed
- Add `resource_name` for checks under `logging` for the GCP provider [(#9023)](https://github.com/prowler-cloud/prowler/pull/9023)
- Fix `ec2_instance_with_outdated_ami` check to handle None AMIs [(#9046)](https://github.com/prowler-cloud/prowler/pull/9046)
- Handle timestamp when transforming compliance findings in CCC [(#9042)](https://github.com/prowler-cloud/prowler/pull/9042)
- Update `resource_id` for admincenter service and avoid unnecessary msgraph requests [(#9019)](https://github.com/prowler-cloud/prowler/pull/9019)
- Fix `firehose_stream_encrypted_at_rest` description and findings clarity [(#9142)](https://github.com/prowler-cloud/prowler/pull/9142)
---
### Changed
- Adapt IaC provider to be used in the Prowler App [(#8751)](https://github.com/prowler-cloud/prowler/pull/8751)
---
+10 -4
View File
@@ -61,7 +61,7 @@ from prowler.lib.outputs.compliance.cis.cis_gcp import GCPCIS
from prowler.lib.outputs.compliance.cis.cis_github import GithubCIS
from prowler.lib.outputs.compliance.cis.cis_kubernetes import KubernetesCIS
from prowler.lib.outputs.compliance.cis.cis_m365 import M365CIS
from prowler.lib.outputs.compliance.cis.cis_oci import OCICIS
from prowler.lib.outputs.compliance.cis.cis_oraclecloud import OracleCloudCIS
from prowler.lib.outputs.compliance.compliance import display_compliance_table
from prowler.lib.outputs.compliance.ens.ens_aws import AWSENS
from prowler.lib.outputs.compliance.ens.ens_azure import AzureENS
@@ -334,7 +334,7 @@ def prowler():
output_options = IACOutputOptions(args, bulk_checks_metadata)
elif provider == "llm":
output_options = LLMOutputOptions(args, bulk_checks_metadata)
elif provider == "oci":
elif provider == "oraclecloud":
output_options = OCIOutputOptions(
args, bulk_checks_metadata, global_provider.identity
)
@@ -359,6 +359,12 @@ def prowler():
else:
# Original behavior for IAC or non-verbose LLM
findings = global_provider.run()
# Note: IaC doesn't support granular progress tracking since Trivy runs as a black box
# and returns all findings at once. Progress tracking would just be 0% → 100%.
# Filter findings by status if specified
if hasattr(args, "status") and args.status:
findings = [f for f in findings if f.status in args.status]
# Report findings for verbose output
report(findings, global_provider, output_options)
elif len(checks_to_execute):
@@ -963,7 +969,7 @@ def prowler():
generated_outputs["compliance"].append(generic_compliance)
generic_compliance.batch_write_data_to_file()
elif provider == "oci":
elif provider == "oraclecloud":
for compliance_name in input_compliance_frameworks:
if compliance_name.startswith("cis_"):
# Generate CIS Finding Object
@@ -971,7 +977,7 @@ def prowler():
f"{output_options.output_directory}/compliance/"
f"{output_options.output_filename}_{compliance_name}.csv"
)
cis = OCICIS(
cis = OracleCloudCIS(
findings=finding_outputs,
compliance=bulk_compliance_frameworks[compliance_name],
file_path=filename,
+1 -1
View File
@@ -1,6 +1,6 @@
{
"Framework": "ENS",
"Name": "ENS RD 311/2022",
"Name": "ENS RD 311/2022 - Categoría Alta",
"Version": "RD2022",
"Provider": "AWS",
"Description": "The accreditation scheme of the ENS (National Security Scheme) has been developed by the Ministry of Finance and Public Administrations and the CCN (National Cryptological Center). This includes the basic principles and minimum requirements necessary for the adequate protection of information.",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"Framework": "NIS2",
"Name": "Network and Information Security Directive (Directive (EU) 2022/2555)",
"Name": "NIS2 - Network and Information Security Directive (Directive (EU) 2022/2555)",
"Version": "",
"Provider": "AWS",
"Description": "ANNEX to the Commission Implementing Regulation laying down rules for the application of Directive (EU) 2022/2555 as regards technical and methodological requirements of cybersecurity risk-management measures and further specification of the cases in which an incident is considered to be significant with regard to DNS service providers, TLD name registries, cloud computing service providers, data centre service providers, content delivery network providers, managed service providers, managed security service providers, providers of online market places, of online search engines and of social networking services platforms, and trust service providers",
File diff suppressed because it is too large. Load diff
@@ -1,6 +1,6 @@
{
"Framework": "ENS",
"Name": "ENS RD 311/2022",
"Name": "ENS RD 311/2022 - Categoría Alta",
"Version": "RD2022",
"Provider": "AZURE",
"Description": "The accreditation scheme of the ENS (National Security Scheme) has been developed by the Ministry of Finance and Public Administrations and the CCN (National Cryptological Center). This includes the basic principles and minimum requirements necessary for the adequate protection of information.",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"Framework": "NIS2",
"Name": "Network and Information Security Directive (Directive (EU) 2022/2555)",
"Name": "NIS2 - Network and Information Security Directive (Directive (EU) 2022/2555)",
"Version": "",
"Provider": "Azure",
"Description": "ANNEX to the Commission Implementing Regulation laying down rules for the application of Directive (EU) 2022/2555 as regards technical and methodological requirements of cybersecurity risk-management measures and further specification of the cases in which an incident is considered to be significant with regard to DNS service providers, TLD name registries, cloud computing service providers, data centre service providers, content delivery network providers, managed service providers, managed security service providers, providers of online market places, of online search engines and of social networking services platforms, and trust service providers",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"Framework": "ENS",
"Name": "ENS RD 311/2022",
"Name": "ENS RD 311/2022 - Categoría Alta",
"Version": "RD2022",
"Provider": "GCP",
"Description": "The accreditation scheme of the ENS (National Security Scheme) has been developed by the Ministry of Finance and Public Administrations and the CCN (National Cryptological Center). This includes the basic principles and minimum requirements necessary for the adequate protection of information.",
+415
View File
@@ -0,0 +1,415 @@
{
"Framework": "HIPAA",
"Name": "HIPAA compliance framework for GCP",
"Version": "",
"Provider": "GCP",
"Description": "The Health Insurance Portability and Accountability Act of 1996 (HIPAA) is legislation that helps US workers to retain health insurance coverage when they change or lose jobs. The legislation also seeks to encourage electronic health records to improve the efficiency and quality of the US healthcare system through improved information sharing. This framework maps HIPAA requirements to Google Cloud Platform (GCP) security best practices.",
"Requirements": [
{
"Id": "164_308_a_1_ii_a",
"Name": "164.308(a)(1)(ii)(A) Risk analysis",
"Description": "Conduct an accurate and thorough assessment of the potential risks and vulnerabilities to the confidentiality, integrity, and availability of electronic protected health information held by the covered entity or business associate.",
"Attributes": [
{
"ItemId": "164_308_a_1_ii_a",
"Section": "164.308 Administrative Safeguards",
"Service": "gcp"
}
],
"Checks": [
"iam_cloud_asset_inventory_enabled",
"securitycenter_security_health_analytics_enabled",
"essentialcontacts_security_contacts_configured"
]
},
{
"Id": "164_308_a_1_ii_b",
"Name": "164.308(a)(1)(ii)(B) Risk Management",
"Description": "Implement security measures sufficient to reduce risks and vulnerabilities to a reasonable and appropriate level to comply with 164.306(a): Ensure the confidentiality, integrity, and availability of all electronic protected health information the covered entity or business associate creates, receives, maintains, or transmits.",
"Attributes": [
{
"ItemId": "164_308_a_1_ii_b",
"Section": "164.308 Administrative Safeguards",
"Service": "gcp"
}
],
"Checks": [
"cloudstorage_bucket_encryption",
"cloudstorage_bucket_public_access",
"cloudstorage_bucket_uniform_access",
"cloudsql_instance_automatic_backups_enabled",
"cloudsql_instance_encryption_enabled",
"cloudsql_instance_public_access",
"compute_instance_public_ip",
"compute_disk_encryption_enabled",
"compute_firewall_rdp_access_from_internet_restricted",
"compute_firewall_ssh_access_from_internet_restricted",
"compute_network_legacy_network_not_used",
"gke_cluster_master_authorized_networks_enabled",
"gke_cluster_private_cluster_enabled",
"iam_sa_no_administrative_privileges",
"iam_no_service_roles_at_project_level",
"bigquery_dataset_public_access",
"bigquery_dataset_cmek_encryption",
"kms_key_rotation_enabled"
]
},
{
"Id": "164_308_a_1_ii_d",
"Name": "164.308(a)(1)(ii)(D) Information system activity review",
"Description": "Implement procedures to regularly review records of information system activity, such as audit logs, access reports, and security incident tracking reports.",
"Attributes": [
{
"ItemId": "164_308_a_1_ii_d",
"Section": "164.308 Administrative Safeguards",
"Service": "gcp"
}
],
"Checks": [
"logging_sink_created",
"logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled",
"logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled",
"logging_log_metric_filter_and_alert_for_custom_role_changes_enabled",
"logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled",
"logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled",
"logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled",
"logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled",
"logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled"
]
},
{
"Id": "164_308_a_3_i",
"Name": "164.308(a)(3)(i) Workforce security",
"Description": "Implement policies and procedures to ensure that all members of its workforce have appropriate access to electronic protected health information.",
"Attributes": [
{
"ItemId": "164_308_a_3_i",
"Section": "164.308 Administrative Safeguards",
"Service": "gcp"
}
],
"Checks": [
"iam_sa_no_administrative_privileges",
"iam_no_service_roles_at_project_level",
"iam_role_kms_enforce_separation_of_duties",
"iam_role_sa_enforce_separation_of_duties",
"iam_sa_no_user_managed_keys",
"iam_sa_user_managed_key_unused"
]
},
{
"Id": "164_308_a_4_i",
"Name": "164.308(a)(4)(i) Information access management",
"Description": "Implement policies and procedures for authorizing access to electronic protected health information that are consistent with the applicable requirements of subpart E of this part.",
"Attributes": [
{
"ItemId": "164_308_a_4_i",
"Section": "164.308 Administrative Safeguards",
"Service": "gcp"
}
],
"Checks": [
"iam_account_access_approval_enabled",
"iam_sa_no_administrative_privileges",
"iam_no_service_roles_at_project_level",
"iam_organization_essential_contacts_configured",
"cloudstorage_bucket_public_access",
"cloudsql_instance_public_access",
"bigquery_dataset_public_access"
]
},
{
"Id": "164_308_a_5_ii_c",
"Name": "164.308(a)(5)(ii)(C) Log-in monitoring",
"Description": "Procedures for monitoring log-in attempts and reporting discrepancies.",
"Attributes": [
{
"ItemId": "164_308_a_5_ii_c",
"Section": "164.308 Administrative Safeguards",
"Service": "gcp"
}
],
"Checks": [
"logging_sink_created",
"logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled",
"logging_log_metric_filter_and_alert_for_custom_role_changes_enabled"
]
},
{
"Id": "164_308_a_6_ii",
"Name": "164.308(a)(6)(ii) Response and reporting",
"Description": "Identify and respond to suspected or known security incidents; mitigate, to the extent practicable, harmful effects of security incidents that are known to the covered entity or business associate; and document security incidents and their outcomes.",
"Attributes": [
{
"ItemId": "164_308_a_6_ii",
"Section": "164.308 Administrative Safeguards",
"Service": "gcp"
}
],
"Checks": [
"securitycenter_security_health_analytics_enabled",
"essentialcontacts_security_contacts_configured",
"logging_sink_created"
]
},
{
"Id": "164_308_a_7_i",
"Name": "164.308(a)(7)(i) Contingency plan",
"Description": "Establish (and implement as needed) policies and procedures for responding to an emergency or other occurrence (for example, fire, vandalism, system failure, and natural disaster) that damages systems that contain electronic protected health information.",
"Attributes": [
{
"ItemId": "164_308_a_7_i",
"Section": "164.308 Administrative Safeguards",
"Service": "gcp"
}
],
"Checks": [
"cloudsql_instance_automatic_backups_enabled",
"compute_disk_snapshot_encryption_enabled",
"gke_cluster_stackdriver_logging_enabled"
]
},
{
"Id": "164_308_a_7_ii_a",
"Name": "164.308(a)(7)(ii)(A) Data backup plan",
"Description": "Establish and implement procedures to create and maintain retrievable exact copies of electronic protected health information.",
"Attributes": [
{
"ItemId": "164_308_a_7_ii_a",
"Section": "164.308 Administrative Safeguards",
"Service": "gcp"
}
],
"Checks": [
"cloudsql_instance_automatic_backups_enabled",
"cloudstorage_bucket_object_versioning",
"compute_disk_snapshot_encryption_enabled"
]
},
{
"Id": "164_308_a_7_ii_b",
"Name": "164.308(a)(7)(ii)(B) Disaster recovery plan",
"Description": "Establish (and implement as needed) procedures to restore any loss of data.",
"Attributes": [
{
"ItemId": "164_308_a_7_ii_b",
"Section": "164.308 Administrative Safeguards",
"Service": "gcp"
}
],
"Checks": [
"cloudsql_instance_automatic_backups_enabled",
"cloudsql_instance_point_in_time_recovery_enabled",
"cloudstorage_bucket_object_versioning"
]
},
{
"Id": "164_310_a_1",
"Name": "164.310(a)(1) Facility access controls",
"Description": "Implement policies and procedures to limit physical access to its electronic information systems and the facility or facilities in which they are housed, while ensuring that properly authorized access is allowed.",
"Attributes": [
{
"ItemId": "164_310_a_1",
"Section": "164.310 Physical Safeguards",
"Service": "gcp"
}
],
"Checks": [
"compute_instance_public_ip",
"compute_firewall_rdp_access_from_internet_restricted",
"compute_firewall_ssh_access_from_internet_restricted",
"gke_cluster_private_cluster_enabled"
]
},
{
"Id": "164_310_d_1",
"Name": "164.310(d)(1) Device and media controls",
"Description": "Implement policies and procedures that govern the receipt and removal of hardware and electronic media that contain electronic protected health information into and out of a facility, and the movement of these items within the facility.",
"Attributes": [
{
"ItemId": "164_310_d_1",
"Section": "164.310 Physical Safeguards",
"Service": "gcp"
}
],
"Checks": [
"compute_disk_encryption_enabled",
"compute_disk_snapshot_encryption_enabled",
"cloudstorage_bucket_encryption"
]
},
{
"Id": "164_312_a_1",
"Name": "164.312(a)(1) Access control",
"Description": "Implement technical policies and procedures for electronic information systems that maintain electronic protected health information to allow access only to those persons or software programs that have been granted access rights as specified in 164.308(a)(4).",
"Attributes": [
{
"ItemId": "164_312_a_1",
"Section": "164.312 Technical Safeguards",
"Service": "gcp"
}
],
"Checks": [
"iam_sa_no_administrative_privileges",
"iam_no_service_roles_at_project_level",
"iam_account_access_approval_enabled",
"cloudstorage_bucket_public_access",
"cloudstorage_bucket_uniform_access",
"cloudsql_instance_public_access",
"bigquery_dataset_public_access",
"compute_instance_public_ip",
"gke_cluster_private_cluster_enabled"
]
},
{
"Id": "164_312_a_2_i",
"Name": "164.312(a)(2)(i) Unique user identification",
"Description": "Assign a unique name and/or number for identifying and tracking user identity.",
"Attributes": [
{
"ItemId": "164_312_a_2_i",
"Section": "164.312 Technical Safeguards",
"Service": "gcp"
}
],
"Checks": [
"iam_sa_no_user_managed_keys",
"iam_sa_user_managed_key_unused"
]
},
{
"Id": "164_312_a_2_iv",
"Name": "164.312(a)(2)(iv) Encryption and decryption",
"Description": "Implement a mechanism to encrypt and decrypt electronic protected health information.",
"Attributes": [
{
"ItemId": "164_312_a_2_iv",
"Section": "164.312 Technical Safeguards",
"Service": "gcp"
}
],
"Checks": [
"cloudstorage_bucket_encryption",
"cloudsql_instance_encryption_enabled",
"compute_disk_encryption_enabled",
"compute_disk_snapshot_encryption_enabled",
"bigquery_dataset_cmek_encryption",
"kms_key_rotation_enabled"
]
},
{
"Id": "164_312_b",
"Name": "164.312(b) Audit controls",
"Description": "Implement hardware, software, and/or procedural mechanisms that record and examine activity in information systems that contain or use electronic protected health information.",
"Attributes": [
{
"ItemId": "164_312_b",
"Section": "164.312 Technical Safeguards",
"Service": "gcp"
}
],
"Checks": [
"logging_sink_created",
"logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled",
"logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled",
"logging_log_metric_filter_and_alert_for_custom_role_changes_enabled",
"logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled",
"logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled",
"logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled",
"logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled",
"logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled",
"gke_cluster_stackdriver_logging_enabled"
]
},
{
"Id": "164_312_c_1",
"Name": "164.312(c)(1) Integrity",
"Description": "Implement policies and procedures to protect electronic protected health information from improper alteration or destruction.",
"Attributes": [
{
"ItemId": "164_312_c_1",
"Section": "164.312 Technical Safeguards",
"Service": "gcp"
}
],
"Checks": [
"cloudstorage_bucket_object_versioning",
"cloudsql_instance_automatic_backups_enabled",
"cloudsql_instance_point_in_time_recovery_enabled",
"kms_key_rotation_enabled"
]
},
{
"Id": "164_312_d",
"Name": "164.312(d) Person or entity authentication",
"Description": "Implement procedures to verify that a person or entity seeking access to electronic protected health information is the one claimed.",
"Attributes": [
{
"ItemId": "164_312_d",
"Section": "164.312 Technical Safeguards",
"Service": "gcp"
}
],
"Checks": [
"iam_account_access_approval_enabled",
"iam_sa_no_user_managed_keys"
]
},
{
"Id": "164_312_e_1",
"Name": "164.312(e)(1) Transmission security",
"Description": "Implement technical security measures to guard against unauthorized access to electronic protected health information that is being transmitted over an electronic communications network.",
"Attributes": [
{
"ItemId": "164_312_e_1",
"Section": "164.312 Technical Safeguards",
"Service": "gcp"
}
],
"Checks": [
"cloudstorage_bucket_encryption",
"compute_firewall_rdp_access_from_internet_restricted",
"compute_firewall_ssh_access_from_internet_restricted",
"cloudsql_instance_ssl_required",
"gke_cluster_master_authorized_networks_enabled"
]
},
{
"Id": "164_312_e_2_i",
"Name": "164.312(e)(2)(i) Integrity controls",
"Description": "Implement security measures to ensure that electronically transmitted electronic protected health information is not improperly modified without detection until disposed of.",
"Attributes": [
{
"ItemId": "164_312_e_2_i",
"Section": "164.312 Technical Safeguards",
"Service": "gcp"
}
],
"Checks": [
"cloudstorage_bucket_object_versioning",
"cloudsql_instance_automatic_backups_enabled",
"logging_sink_created"
]
},
{
"Id": "164_312_e_2_ii",
"Name": "164.312(e)(2)(ii) Encryption",
"Description": "Implement a mechanism to encrypt electronic protected health information whenever deemed appropriate.",
"Attributes": [
{
"ItemId": "164_312_e_2_ii",
"Section": "164.312 Technical Safeguards",
"Service": "gcp"
}
],
"Checks": [
"cloudstorage_bucket_encryption",
"cloudsql_instance_encryption_enabled",
"compute_disk_encryption_enabled",
"bigquery_dataset_cmek_encryption",
"kms_key_rotation_enabled",
"cloudsql_instance_ssl_required"
]
}
]
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"Framework": "NIS2",
"Name": "Network and Information Security Directive (Directive (EU) 2022/2555)",
"Name": "NIS2 - Network and Information Security Directive (Directive (EU) 2022/2555)",
"Version": "",
"Provider": "GCP",
"Description": "ANNEX to the Commission Implementing Regulation laying down rules for the application of Directive (EU) 2022/2555 as regards technical and methodological requirements of cybersecurity risk-management measures and further specification of the cases in which an incident is considered to be significant with regard to DNS service providers, TLD name registries, cloud computing service providers, data centre service providers, content delivery network providers, managed service providers, managed security service providers, providers of online market places, of online search engines and of social networking services platforms, and trust service providers",
@@ -476,7 +476,9 @@
{
"Id": "1.2.2",
"Description": "Limit the ability to create repositories to trusted users and teams.",
"Checks": [],
"Checks": [
"organization_repository_creation_limited"
],
"Attributes": [
{
"Section": "1 Source Code",
@@ -2,7 +2,7 @@
"Framework": "CIS",
"Name": "CIS Oracle Cloud Infrastructure Foundations Benchmark v3.0.0",
"Version": "3.0",
"Provider": "OCI",
"Provider": "OracleCloud",
"Description": "The CIS Oracle Cloud Infrastructure Foundations Benchmark provides prescriptive guidance for configuring security options for Oracle Cloud Infrastructure with an emphasis on foundational, testable, and architecture agnostic settings.",
"Requirements": [
{
+1 -1
View File
@@ -33,7 +33,7 @@ class Provider(str, Enum):
IAC = "iac"
NHN = "nhn"
MONGODBATLAS = "mongodbatlas"
OCI = "oci"
ORACLECLOUD = "oraclecloud"
# Compliance
+2 -18
View File
@@ -438,16 +438,8 @@ def execute_checks(
service = check_name.split("_")[0]
try:
try:
# Map CLI provider names to directory names (for cases where they differ)
provider_directory_map = {
"oci": "oraclecloud", # OCI SDK conflict avoidance
}
provider_directory = provider_directory_map.get(
global_provider.type, global_provider.type
)
# Import check module
check_module_path = f"prowler.providers.{provider_directory}.services.{service}.{check_name}.{check_name}"
check_module_path = f"prowler.providers.{global_provider.type}.services.{service}.{check_name}.{check_name}"
lib = import_check(check_module_path)
# Recover functions from check
check_to_execute = getattr(lib, check_name)
@@ -526,16 +518,8 @@ def execute_checks(
)
try:
try:
# Map CLI provider names to directory names (for cases where they differ)
provider_directory_map = {
"oci": "oraclecloud", # oci SDK conflict avoidance
}
provider_directory = provider_directory_map.get(
global_provider.type, global_provider.type
)
# Import check module
check_module_path = f"prowler.providers.{provider_directory}.services.{service}.{check_name}.{check_name}"
check_module_path = f"prowler.providers.{global_provider.type}.services.{service}.{check_name}.{check_name}"
lib = import_check(check_module_path)
# Recover functions from check
check_to_execute = getattr(lib, check_name)
+65 -1
View File
@@ -1,3 +1,5 @@
import sys
from colorama import Fore, Style
from prowler.lib.check.check import parse_checks_from_file
@@ -57,8 +59,24 @@ def load_checks_to_execute(
# Handle if there are checks passed using -c/--checks
if check_list:
# Validate that all checks exist
available_checks = set(bulk_checks_metadata.keys())
available_checks.update(check_aliases.keys())
invalid_checks = []
for check_name in check_list:
checks_to_execute.add(check_name)
if check_name not in available_checks:
invalid_checks.append(check_name)
else:
checks_to_execute.add(check_name)
if invalid_checks:
logger.critical(
f"Invalid check(s) specified: {', '.join(invalid_checks)}"
)
logger.critical(
f"Please provide valid check names. Use 'prowler {provider} --list-checks' to see available checks."
)
sys.exit(1)
# Handle if there are some severities passed using --severity
elif severities:
@@ -66,6 +84,23 @@ def load_checks_to_execute(
checks_to_execute.update(check_severities[severity])
if service_list:
# Validate that all services exist
available_services = set()
for metadata in bulk_checks_metadata.values():
available_services.add(metadata.ServiceName)
invalid_services = [
s for s in service_list if s not in available_services
]
if invalid_services:
logger.critical(
f"Invalid service(s) specified: {', '.join(invalid_services)}"
)
logger.critical(
f"Please provide valid service names. Use 'prowler {provider} --list-services' to see available services."
)
sys.exit(1)
checks_from_services = set()
for service in service_list:
service_checks = CheckMetadata.list(
@@ -81,6 +116,21 @@ def load_checks_to_execute(
# Handle if there are services passed using -s/--services
elif service_list:
# Validate that all services exist
available_services = set()
for metadata in bulk_checks_metadata.values():
available_services.add(metadata.ServiceName)
invalid_services = [s for s in service_list if s not in available_services]
if invalid_services:
logger.critical(
f"Invalid service(s) specified: {', '.join(invalid_services)}"
)
logger.critical(
f"Please provide valid service names. Use 'prowler {provider} --list-services' to see available services."
)
sys.exit(1)
for service in service_list:
checks_to_execute.update(
CheckMetadata.list(
@@ -103,6 +153,20 @@ def load_checks_to_execute(
# Handle if there are categories passed using --categories
elif categories:
# Validate that all categories exist
available_categories = set(check_categories.keys())
invalid_categories = [
c for c in categories if c not in available_categories
]
if invalid_categories:
logger.critical(
f"Invalid category(ies) specified: {', '.join(invalid_categories)}"
)
logger.critical(
f"Please provide valid category names. Use 'prowler {provider} --list-categories' to see available categories."
)
sys.exit(1)
for category in categories:
checks_to_execute.update(check_categories[category])
+10 -1
View File
@@ -588,8 +588,17 @@ class Check_Report_GCP(Check_Report):
or getattr(resource, "name", None)
or ""
)
# Prefer the explicit resource_name argument, otherwise look for a name attribute on the resource
resource_name_candidate = resource_name or getattr(resource, "name", None)
if not resource_name_candidate and isinstance(resource, dict):
# Some callers pass a dict, so fall back to the dict entry if available
resource_name_candidate = resource.get("name")
if isinstance(resource_name_candidate, str):
# Trim whitespace so empty strings collapse to the default
resource_name_candidate = resource_name_candidate.strip()
self.resource_name = (
resource_name or getattr(resource, "name", "") or "GCP Project"
str(resource_name_candidate) if resource_name_candidate else "GCP Project"
)
self.project_id = project_id or getattr(resource, "project_id", "")
self.location = (
+1 -7
View File
@@ -46,14 +46,8 @@ def recover_checks_from_provider(
# List all available modules in the selected provider and service
def list_modules(provider: str, service: str):
# Map CLI provider names to directory names (for cases where they differ)
provider_directory_map = {
"oci": "oraclecloud", # OCI SDK conflict avoidance
}
provider_directory = provider_directory_map.get(provider, provider)
# This module path requires the full path including "prowler."
module_path = f"prowler.providers.{provider_directory}.services"
module_path = f"prowler.providers.{provider}.services"
if service:
module_path += f".{service}"
return walk_packages(
+6 -3
View File
@@ -26,17 +26,17 @@ class ProwlerArgumentParser:
self.parser = argparse.ArgumentParser(
prog="prowler",
formatter_class=RawTextHelpFormatter,
usage="prowler [-h] [--version] {aws,azure,gcp,kubernetes,m365,github,nhn,mongodbatlas,oci,dashboard,iac} ...",
usage="prowler [-h] [--version] {aws,azure,gcp,kubernetes,m365,github,nhn,mongodbatlas,oraclecloud,dashboard,iac} ...",
epilog="""
Available Cloud Providers:
{aws,azure,gcp,kubernetes,m365,github,iac,llm,nhn,mongodbatlas,oci}
{aws,azure,gcp,kubernetes,m365,github,iac,llm,nhn,mongodbatlas,oraclecloud}
aws AWS Provider
azure Azure Provider
gcp GCP Provider
kubernetes Kubernetes Provider
m365 Microsoft 365 Provider
github GitHub Provider
oci Oracle Cloud Infrastructure Provider
oraclecloud Oracle Cloud Infrastructure Provider
iac IaC Provider (Beta)
llm LLM Provider (Beta)
nhn NHN Provider (Unofficial)
@@ -113,6 +113,9 @@ Detailed documentation at https://docs.prowler.com
# Microsoft 365
elif sys.argv[1] == "microsoft365":
sys.argv[1] = "m365"
# Oracle Cloud Infrastructure
elif sys.argv[1] == "oci":
sys.argv[1] = "oraclecloud"
# Parse arguments
args = self.parser.parse_args()
@@ -1,20 +1,20 @@
from prowler.config.config import timestamp
from prowler.lib.check.compliance_models import Compliance
from prowler.lib.outputs.compliance.cis.models import OCICISModel
from prowler.lib.outputs.compliance.cis.models import OracleCloudCISModel
from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput
from prowler.lib.outputs.finding import Finding
class OCICIS(ComplianceOutput):
class OracleCloudCIS(ComplianceOutput):
"""
This class represents the OCI CIS compliance output.
This class represents the Oracle Cloud CIS compliance output.
Attributes:
- _data (list): A list to store transformed data from findings.
- _file_descriptor (TextIOWrapper): A file descriptor to write data to a file.
Methods:
- transform: Transforms findings into OCI CIS compliance format.
- transform: Transforms findings into Oracle Cloud CIS compliance format.
"""
def transform(
@@ -24,7 +24,7 @@ class OCICIS(ComplianceOutput):
compliance_name: str,
) -> None:
"""
Transforms a list of findings into OCI CIS compliance format.
Transforms a list of findings into Oracle Cloud CIS compliance format.
Parameters:
- findings (list): A list of findings.
@@ -40,7 +40,7 @@ class OCICIS(ComplianceOutput):
for requirement in compliance.Requirements:
if requirement.Id in finding_requirements:
for attribute in requirement.Attributes:
compliance_row = OCICISModel(
compliance_row = OracleCloudCISModel(
Provider=finding.provider,
Description=compliance.Description,
TenancyId=finding.account_uid,
@@ -74,7 +74,7 @@ class OCICIS(ComplianceOutput):
for requirement in compliance.Requirements:
if not requirement.Checks:
for attribute in requirement.Attributes:
compliance_row = OCICISModel(
compliance_row = OracleCloudCISModel(
Provider=compliance.Provider.lower(),
Description=compliance.Description,
TenancyId="",
+3 -3
View File
@@ -207,9 +207,9 @@ class GithubCISModel(BaseModel):
Name: str
class OCICISModel(BaseModel):
class OracleCloudCISModel(BaseModel):
"""
OCICISModel generates a finding's output in OCI CIS Compliance format.
OracleCloudCISModel generates a finding's output in Oracle Cloud CIS Compliance format.
"""
Provider: str
@@ -248,7 +248,7 @@ CIS_GCP = GCPCISModel
CIS_Kubernetes = KubernetesCISModel
CIS_M365 = M365CISModel
CIS_Github = GithubCISModel
CIS_OCI = OCICISModel
CIS_OracleCloud = OracleCloudCISModel
# TODO: Create a parent class for the common fields of CIS and have the specific classes from each provider to inherit from it.
+17 -6
View File
@@ -309,10 +309,17 @@ class Finding(BaseModel):
output_data["auth_method"] = provider.auth_method
output_data["account_uid"] = "iac"
output_data["account_name"] = "iac"
output_data["resource_name"] = check_output.resource_name
output_data["resource_uid"] = check_output.resource_name
output_data["region"] = check_output.resource_line_range
output_data["resource_line_range"] = check_output.resource_line_range
output_data["resource_name"] = getattr(
check_output, "resource_name", ""
)
output_data["resource_uid"] = getattr(check_output, "resource_name", "")
# For IaC, resource_line_range only exists on CheckReportIAC, not on Finding objects
output_data["region"] = getattr(
check_output, "resource_line_range", "file"
)
output_data["resource_line_range"] = getattr(
check_output, "resource_line_range", ""
)
output_data["framework"] = check_output.check_metadata.ServiceName
elif provider.type == "llm":
@@ -323,7 +330,7 @@ class Finding(BaseModel):
output_data["resource_uid"] = check_output.model
output_data["region"] = check_output.model
elif provider.type == "oci":
elif provider.type == "oraclecloud":
output_data["auth_method"] = (
f"Profile: {get_nested_attribute(provider, 'session.profile')}"
)
@@ -407,7 +414,11 @@ class Finding(BaseModel):
finding.subscription = list(provider.identity.subscriptions.keys())[0]
elif provider.type == "gcp":
finding.project_id = list(provider.projects.keys())[0]
elif provider.type == "oci":
elif provider.type == "iac":
# For IaC, we don't have resource_line_range in the Finding model
# It would need to be extracted from the resource metadata if needed
finding.resource_line_range = "" # Set empty for compatibility
elif provider.type == "oraclecloud":
finding.compartment_id = getattr(finding, "compartment_id", "")
finding.check_metadata = CheckMetadata(
+1 -1
View File
@@ -28,7 +28,7 @@ def stdout_report(finding, color, verbose, status, fix):
details = finding.check_metadata.CheckID
if finding.check_metadata.Provider == "iac":
details = finding.check_metadata.CheckID
if finding.check_metadata.Provider == "oci":
if finding.check_metadata.Provider == "oraclecloud":
details = finding.region
if (verbose or fix) and (not status or finding.status in status):
+1 -1
View File
@@ -67,7 +67,7 @@ def display_summary_table(
elif provider.type == "llm":
entity_type = "LLM"
audited_entities = provider.model
elif provider.type == "oci":
elif provider.type == "oraclecloud":
entity_type = "Tenancy"
audited_entities = (
provider.identity.tenancy_name
+106 -33
View File
@@ -1,4 +1,5 @@
import datetime
from datetime import timezone
from types import SimpleNamespace
from typing import Generator
@@ -25,6 +26,7 @@ from prowler.lib.scan.exceptions.exceptions import (
)
from prowler.providers.common.models import Audit_Metadata, ProviderOutputOptions
from prowler.providers.common.provider import Provider
from prowler.providers.iac.iac_provider import IacProvider
class Scan:
@@ -90,15 +92,25 @@ class Scan:
except ValueError:
raise ScanInvalidStatusError(f"Invalid status provided: {s}.")
# Load bulk compliance frameworks
self._bulk_compliance_frameworks = Compliance.get_bulk(provider.type)
# Get bulk checks metadata for the provider
self._bulk_checks_metadata = CheckMetadata.get_bulk(provider.type)
# Complete checks metadata with the compliance framework specification
self._bulk_checks_metadata = update_checks_metadata_with_compliance(
self._bulk_compliance_frameworks, self._bulk_checks_metadata
)
# Special setup for IaC provider - override inputs to work with traditional flow
if provider.type == "iac":
# IaC doesn't use traditional Prowler checks, so clear all input parameters
# to avoid validation errors and let it flow through the normal logic
checks = None
services = None
excluded_checks = None
excluded_services = None
self._bulk_checks_metadata = {}
self._bulk_compliance_frameworks = {}
else:
# Load bulk compliance frameworks
self._bulk_compliance_frameworks = Compliance.get_bulk(provider.type)
# Get bulk checks metadata for the provider
self._bulk_checks_metadata = CheckMetadata.get_bulk(provider.type)
# Complete checks metadata with the compliance framework specification
self._bulk_checks_metadata = update_checks_metadata_with_compliance(
self._bulk_compliance_frameworks, self._bulk_checks_metadata
)
# Create a list of valid categories
valid_categories = set()
@@ -148,19 +160,22 @@ class Scan:
)
# Load checks to execute
self._checks_to_execute = sorted(
load_checks_to_execute(
bulk_checks_metadata=self._bulk_checks_metadata,
bulk_compliance_frameworks=self._bulk_compliance_frameworks,
check_list=checks,
service_list=services,
compliance_frameworks=compliances,
categories=categories,
severities=severities,
provider=provider.type,
checks_file=None,
if provider.type == "iac":
self._checks_to_execute = ["iac_scan"] # Dummy check name for IaC
else:
self._checks_to_execute = sorted(
load_checks_to_execute(
bulk_checks_metadata=self._bulk_checks_metadata,
bulk_compliance_frameworks=self._bulk_compliance_frameworks,
check_list=checks,
service_list=services,
compliance_frameworks=compliances,
categories=categories,
severities=severities,
provider=provider.type,
checks_file=None,
)
)
)
# Exclude checks
if excluded_checks:
@@ -184,9 +199,13 @@ class Scan:
self._number_of_checks_to_execute = len(self._checks_to_execute)
service_checks_to_execute = get_service_checks_to_execute(
self._checks_to_execute
)
# Set up service-based checks tracking
if provider.type == "iac":
service_checks_to_execute = {"iac": set(["iac_scan"])}
else:
service_checks_to_execute = get_service_checks_to_execute(
self._checks_to_execute
)
service_checks_completed = dict()
self._service_checks_to_execute = service_checks_to_execute
@@ -245,6 +264,9 @@ class Scan:
Exception: If any other error occurs during the execution of a check.
"""
try:
# Initialize check_name for error handling
check_name = None
# Using SimpleNamespace to create a mocked object
arguments = SimpleNamespace()
@@ -266,21 +288,71 @@ class Scan:
start_time = datetime.datetime.now()
# Special handling for IaC provider
if self._provider.type == "iac":
# IaC provider doesn't use regular checks, it runs Trivy directly
if isinstance(self._provider, IacProvider):
logger.info("Running IaC scan with Trivy...")
# Run the IaC scan
iac_reports = self._provider.run()
# Convert IaC reports to Finding objects
findings = []
for report in iac_reports:
# Generate unique UID for the finding
finding_uid = f"{report.check_metadata.CheckID}-{report.resource_name}-{report.resource_line_range}"
# Convert status string to Status enum
status_enum = (
Status.FAIL if report.status == "FAIL" else Status.PASS
)
if report.muted:
status_enum = Status.MUTED
finding = Finding(
auth_method="Repository", # IaC uses repository as auth method
timestamp=datetime.datetime.now(timezone.utc),
account_uid=self._provider.scan_repository_url or "local",
account_name="IaC Repository",
metadata=report.check_metadata, # Pass the CheckMetadata object directly
uid=finding_uid,
status=status_enum,
status_extended=report.status_extended,
muted=report.muted,
resource_uid=report.resource_name, # For IaC, the file path is the UID
resource_metadata=report.resource, # The raw finding dict
resource_name=report.resource_name,
resource_details=report.resource_details,
resource_tags={}, # IaC doesn't have resource tags
region="global", # IaC doesn't have regions
compliance={}, # IaC doesn't have compliance mappings yet
raw=report.resource, # The raw finding dict
)
findings.append(finding)
# Filter the findings by the status
if self._status:
findings = [f for f in findings if f.status in self._status]
# Update progress and yield findings
self._number_of_checks_completed = 1
self._number_of_checks_to_execute = 1
yield (100.0, findings)
# Calculate duration
end_time = datetime.datetime.now()
self._duration = int((end_time - start_time).total_seconds())
return
for check_name in checks_to_execute:
try:
# Recover service from check name
service = get_service_name_from_check_name(check_name)
try:
# Map CLI provider names to directory names (for cases where they differ)
provider_directory_map = {
"oci": "oraclecloud", # OCI SDK conflict avoidance
}
provider_directory = provider_directory_map.get(
self._provider.type, self._provider.type
)
# Import check module
check_module_path = f"prowler.providers.{provider_directory}.services.{service}.{check_name}.{check_name}"
check_module_path = f"prowler.providers.{self._provider.type}.services.{service}.{check_name}.{check_name}"
lib = import_check(check_module_path)
# Recover functions from check
check_to_execute = getattr(lib, check_name)
@@ -349,6 +421,7 @@ class Scan:
# Update the scan duration when all checks are completed
self._duration = int((datetime.datetime.now() - start_time).total_seconds())
except Exception as error:
check_name = check_name or "Scan error"
logger.error(
f"{check_name} - {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
@@ -2988,6 +2988,7 @@
"ap-southeast-3",
"ap-southeast-4",
"ap-southeast-5",
"ap-southeast-6",
"ap-southeast-7",
"ca-central-1",
"ca-west-1",
@@ -4574,6 +4575,25 @@
"aws-us-gov": []
}
},
"evs": {
"regions": {
"aws": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
"ca-central-1",
"eu-central-1",
"eu-west-1",
"eu-west-2",
"us-east-1",
"us-east-2",
"us-west-2"
],
"aws-cn": [],
"aws-us-gov": []
}
},
"fargate": {
"regions": {
"aws": [
@@ -1,9 +1,9 @@
from typing import Optional
from venv import logger
from botocore.client import ClientError
from pydantic.v1 import BaseModel
from prowler.lib.logger import logger
from prowler.providers.aws.lib.service.service import AWSService
@@ -13,7 +13,7 @@
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "AwsKinesisStream",
"Description": "**Amazon Data Firehose** delivery streams use **server-side encryption at rest** with AWS KMS. For `DirectPut` or database sources, the stream must have KMS encryption enabled. When the source is **Kinesis Data Streams**, the source's encryption is considered. For **MSK** sources, encryption at rest is always applied by AWS.",
"Description": "**Amazon Data Firehose** delivery streams must enable **server-side encryption at rest** with AWS KMS regardless of the source type. Encryption of upstream sources such as **Kinesis Data Streams** or **MSK** does not replace the need to protect the delivery stream itself.",
"Risk": "Unencrypted Firehose data at rest can be read if storage or backups are accessed, harming **confidentiality** and **integrity**. Disk-level access, snapshots, or misconfigured destinations enable data exfiltration or tampering. Lacking KMS-backed controls also reduces key rotation, segregation of duties, and auditability.",
"RelatedUrl": "",
"AdditionalURLs": [
@@ -26,23 +26,27 @@ class firehose_stream_encrypted_at_rest(Check):
for stream in firehose_client.delivery_streams.values():
report = Check_Report_AWS(metadata=self.metadata(), resource=stream)
report.status = "FAIL"
report.status_extended = f"Firehose Stream {stream.name} does not have at rest encryption enabled or the source stream is not encrypted."
report.status_extended = f"Firehose Stream {stream.name} does not have at rest encryption enabled."
# Encrypted Kinesis Stream source
if stream.delivery_stream_type == "KinesisStreamAsSource":
source_stream = kinesis_client.streams.get(
stream.source.kinesis_stream.kinesis_stream_arn
)
if stream.kms_encryption == EncryptionStatus.ENABLED:
report.status = "PASS"
report.status_extended = f"Firehose Stream {stream.name} does have at rest encryption enabled."
elif stream.delivery_stream_type == "KinesisStreamAsSource":
source_stream_arn = stream.source.kinesis_stream.kinesis_stream_arn
source_stream = kinesis_client.streams.get(source_stream_arn, None)
if source_stream:
if source_stream.encrypted_at_rest != EncryptionType.NONE:
report.status = "PASS"
report.status_extended = f"Firehose Stream {stream.name} does not have at rest encryption enabled but the source stream {source_stream.name} has at rest encryption enabled."
if source_stream.encrypted_at_rest == EncryptionType.KMS:
report.status_extended = f"Firehose Stream {stream.name} does not have at rest encryption enabled even though source stream {source_stream.name} has at rest encryption enabled."
else:
report.status_extended = f"Firehose Stream {stream.name} does not have at rest encryption enabled and the source stream {source_stream.name} is not encrypted at rest."
else:
report.status_extended = f"Firehose Stream {stream.name} does not have at rest encryption enabled and the referenced source stream could not be found."
# MSK source - check if the MSK cluster has encryption at rest with CMK
elif stream.delivery_stream_type == "MSKAsSource":
msk_cluster_arn = stream.source.msk.msk_cluster_arn
msk_cluster = None
if msk_cluster_arn:
msk_cluster = None
for cluster in kafka_client.clusters.values():
if cluster.arn == msk_cluster_arn:
msk_cluster = cluster
@@ -59,11 +63,6 @@ class firehose_stream_encrypted_at_rest(Check):
else:
report.status_extended = f"Firehose Stream {stream.name} uses MSK source which always has encryption at rest enabled by AWS."
# Check if the stream has encryption enabled directly (DirectPut or DatabaseAsSource cases)
elif stream.kms_encryption == EncryptionStatus.ENABLED:
report.status = "PASS"
report.status_extended = f"Firehose Stream {stream.name} does have at rest encryption enabled."
findings.append(report)
return findings
+2 -16
View File
@@ -16,15 +16,9 @@ def init_providers_parser(self):
providers = Provider.get_available_providers()
for provider in providers:
try:
# Map CLI provider names to directory names (for cases where they differ)
provider_directory_map = {
"oci": "oraclecloud", # OCI SDK conflict avoidance
}
provider_directory = provider_directory_map.get(provider, provider)
getattr(
import_module(
f"{providers_path}.{provider_directory}.{provider_arguments_lib_path}"
f"{providers_path}.{provider}.{provider_arguments_lib_path}"
),
init_provider_arguments_function,
)(self)
@@ -38,18 +32,10 @@ def init_providers_parser(self):
def validate_provider_arguments(arguments: Namespace) -> tuple[bool, str]:
"""validate_provider_arguments returns {True, "} if the provider arguments passed are valid and can be used together"""
try:
# Map CLI provider names to directory names (for cases where they differ)
provider_directory_map = {
"oci": "oraclecloud", # OCI SDK conflict avoidance
}
provider_directory = provider_directory_map.get(
arguments.provider, arguments.provider
)
# Provider function must be located at prowler.providers.<provider>.lib.arguments.arguments.validate_arguments
return getattr(
import_module(
f"{providers_path}.{provider_directory}.{provider_arguments_lib_path}"
f"{providers_path}.{arguments.provider}.{provider_arguments_lib_path}"
),
validate_provider_arguments_function,
)(arguments)
+2 -18
View File
@@ -146,24 +146,8 @@ class Provider(ABC):
@staticmethod
def init_global_provider(arguments: Namespace) -> None:
try:
# Map CLI provider names to directory names (for cases where they differ)
provider_directory_map = {
"oci": "oraclecloud", # oci SDK conflict avoidance
}
# Map CLI provider names to provider file names (for cases where they differ)
provider_file_map = {
"oci": "oci", # oraclecloud directory but oci_provider.py file
}
provider_directory = provider_directory_map.get(
arguments.provider, arguments.provider
)
provider_file = provider_file_map.get(
arguments.provider, arguments.provider
)
provider_class_path = (
f"{providers_path}.{provider_directory}.{provider_file}_provider"
f"{providers_path}.{arguments.provider}.{arguments.provider}_provider"
)
provider_class_name = f"{arguments.provider.capitalize()}Provider"
provider_class = getattr(
@@ -291,7 +275,7 @@ class Provider(ABC):
mutelist_path=arguments.mutelist_file,
fixer_config=fixer_config,
)
elif "oci" in provider_class_name.lower():
elif "oraclecloud" in provider_class_name.lower():
provider_class(
oci_config_file=arguments.oci_config_file,
profile=arguments.profile,
@@ -0,0 +1,30 @@
{
"Provider": "github",
"CheckID": "organization_repository_creation_limited",
"CheckTitle": "Ensure repository creation is limited to trusted organization members.",
"CheckType": [],
"ServiceName": "organization",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "GitHubOrganization",
"Description": "Ensure that repository creation is restricted so that only trusted owners or specific teams can create new repositories within the organization.",
"Risk": "Allowing all members to create repositories increases the likelihood of shadow repositories, data leakage, or malicious projects being introduced without oversight.",
"RelatedUrl": "https://docs.github.com/en/organizations/managing-organization-settings/restricting-repository-creation-in-your-organization",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Disable repository creation for members or limit it to specific trusted teams by adjusting Member privileges in the organization's settings.",
"Url": "https://docs.github.com/en/organizations/managing-organization-settings/restricting-repository-creation-in-your-organization"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,106 @@
from typing import List
from prowler.lib.check.models import Check, CheckReportGithub
from prowler.providers.github.services.organization.organization_client import (
organization_client,
)
def _join_human_readable(items: List[str]) -> str:
"""Return a simple human readable comma-separated list."""
if not items:
return ""
if len(items) == 1:
return items[0]
return ", ".join(items[:-1]) + f" and {items[-1]}"
class organization_repository_creation_limited(Check):
"""Check if repository creation is limited to trusted organization members."""
def execute(self) -> List[CheckReportGithub]:
findings = []
for org in organization_client.organizations.values():
repo_data = [
getattr(org, "members_can_create_repositories", None),
getattr(org, "members_can_create_public_repositories", None),
getattr(org, "members_can_create_private_repositories", None),
getattr(org, "members_can_create_internal_repositories", None),
getattr(org, "members_allowed_repository_creation_type", None),
]
if all(value is None for value in repo_data):
continue
report = CheckReportGithub(metadata=self.metadata(), resource=org)
global_creation = getattr(org, "members_can_create_repositories", None)
public_creation = getattr(
org, "members_can_create_public_repositories", None
)
private_creation = getattr(
org, "members_can_create_private_repositories", None
)
internal_creation = getattr(
org, "members_can_create_internal_repositories", None
)
creation_type = getattr(
org, "members_allowed_repository_creation_type", None
)
type_flags = []
enabled_types = []
if global_creation is not None:
if global_creation:
enabled_types.append("repositories of any type")
else:
type_flags.append(False)
visibility_flags = [
(public_creation, "public repositories"),
(private_creation, "private repositories"),
(internal_creation, "internal repositories"),
]
for flag, label in visibility_flags:
if flag is not None:
type_flags.append(flag)
if flag:
enabled_types.append(label)
if creation_type:
normalized_type = creation_type.lower()
if normalized_type == "none":
type_flags.append(False)
else:
creation_messages = {
"all": "repositories of any type",
"public": "public repositories",
"private": "private repositories",
"internal": "internal repositories",
"selected": "repositories for selected members or teams",
}
enabled_types.append(
creation_messages.get(
normalized_type, f"{creation_type} repositories"
)
)
restricted = bool(type_flags) and all(flag is False for flag in type_flags)
if restricted:
report.status = "PASS"
report.status_extended = f"Organization {org.name} has disabled repository creation for members."
else:
report.status = "FAIL"
unique_enabled = list(dict.fromkeys(enabled_types))
allowed_desc = _join_human_readable(unique_enabled)
if allowed_desc:
report.status_extended = f"Organization {org.name} allows members to create {allowed_desc}."
else:
report.status_extended = f"Organization {org.name} does not have enough data to confirm repository creation restrictions."
findings.append(report)
return findings
@@ -38,8 +38,9 @@ class Organization(GithubService):
org_names_to_check = set()
try:
for client in getattr(self, "clients", []) or []:
if getattr(self.provider, "organizations", None):
clients = getattr(self, "clients", [])
for client in clients:
if self.provider.organizations:
org_names_to_check.update(self.provider.organizations)
# If repositories are specified without organizations, don't perform organization checks
@@ -147,22 +148,74 @@ class Organization(GithubService):
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
repo_creation_settings = {
"members_can_create_repositories": None,
"members_can_create_public_repositories": None,
"members_can_create_private_repositories": None,
"members_can_create_internal_repositories": None,
"members_allowed_repository_creation_type": None,
}
# Base permission (default repository permission for members)
base_perm: Optional[str] = None
try:
base_perm = getattr(org, "default_repository_permission", None)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
base_perm = None
def _extract_flag(attribute: str, expected_type: type):
"""Return attribute value if it matches expected type and is not a mock placeholder."""
try:
value = getattr(org, attribute, None)
if hasattr(value, "_mock_parent"):
return None
if expected_type is bool and isinstance(value, bool):
return value
if expected_type is str and isinstance(value, str):
return value
return None
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return None
repo_creation_settings["members_can_create_repositories"] = _extract_flag(
"members_can_create_repositories", bool
)
repo_creation_settings["members_can_create_public_repositories"] = (
_extract_flag("members_can_create_public_repositories", bool)
)
repo_creation_settings["members_can_create_private_repositories"] = (
_extract_flag("members_can_create_private_repositories", bool)
)
repo_creation_settings["members_can_create_internal_repositories"] = (
_extract_flag("members_can_create_internal_repositories", bool)
)
repo_creation_settings["members_allowed_repository_creation_type"] = (
_extract_flag("members_allowed_repository_creation_type", str)
)
base_permission_raw = _extract_flag("default_repository_permission", str)
base_permission = (
base_permission_raw.lower()
if isinstance(base_permission_raw, str)
else None
)
organizations[org.id] = Org(
id=org.id,
name=org.login,
mfa_required=require_mfa,
base_permission=base_perm,
members_can_create_repositories=repo_creation_settings[
"members_can_create_repositories"
],
members_can_create_public_repositories=repo_creation_settings[
"members_can_create_public_repositories"
],
members_can_create_private_repositories=repo_creation_settings[
"members_can_create_private_repositories"
],
members_can_create_internal_repositories=repo_creation_settings[
"members_can_create_internal_repositories"
],
members_allowed_repository_creation_type=repo_creation_settings[
"members_allowed_repository_creation_type"
],
base_permission=base_permission,
)
@@ -172,4 +225,9 @@ class Org(BaseModel):
id: int
name: str
mfa_required: Optional[bool] = False
members_can_create_repositories: Optional[bool] = None
members_can_create_public_repositories: Optional[bool] = None
members_can_create_private_repositories: Optional[bool] = None
members_can_create_internal_repositories: Optional[bool] = None
members_allowed_repository_creation_type: Optional[str] = None
base_permission: Optional[str] = None
+194 -39
View File
@@ -1,10 +1,11 @@
import json
import re
import shutil
import subprocess
import sys
import tempfile
from os import environ
from typing import List
from typing import Generator, List
from alive_progress import alive_bar
from colorama import Fore, Style
@@ -17,7 +18,7 @@ from prowler.config.config import (
from prowler.lib.check.models import CheckReportIAC
from prowler.lib.logger import logger
from prowler.lib.utils.utils import print_boxes
from prowler.providers.common.models import Audit_Metadata
from prowler.providers.common.models import Audit_Metadata, Connection
from prowler.providers.common.provider import Provider
@@ -173,7 +174,7 @@ class IacProvider(Provider):
"Severity": finding["Severity"],
"ResourceType": "iac",
"Description": finding_description,
"Risk": "",
"Risk": "This provider has not defined a risk for this check.",
"RelatedUrl": finding.get("PrimaryURL", ""),
"Remediation": {
"Code": {
@@ -242,20 +243,39 @@ class IacProvider(Provider):
logger.info(
f"Cloning repository {original_url} into {temporary_directory}..."
)
with alive_bar(
ctrl_c=False,
bar="blocks",
spinner="classic",
stats=False,
enrich_print=False,
) as bar:
try:
bar.title = f"-> Cloning {original_url}..."
# Check if we're in an environment with a TTY
# Celery workers and other non-interactive environments don't have TTY
# and cannot use the alive_bar
try:
if sys.stdout.isatty():
with alive_bar(
ctrl_c=False,
bar="blocks",
spinner="classic",
stats=False,
enrich_print=False,
) as bar:
try:
bar.title = f"-> Cloning {original_url}..."
porcelain.clone(
repository_url, temporary_directory, depth=1
)
bar.title = "-> Repository cloned successfully!"
except Exception as clone_error:
bar.title = "-> Cloning failed!"
raise clone_error
else:
# No TTY, just clone without progress bar
logger.info(f"Cloning {original_url}...")
porcelain.clone(repository_url, temporary_directory, depth=1)
bar.title = "-> Repository cloned successfully!"
except Exception as clone_error:
bar.title = "-> Cloning failed!"
raise clone_error
logger.info("Repository cloned successfully!")
except (AttributeError, OSError):
# Fallback if isatty() check fails
logger.info(f"Cloning {original_url}...")
porcelain.clone(repository_url, temporary_directory, depth=1)
logger.info("Repository cloned successfully!")
return temporary_directory
except Exception as error:
logger.critical(
@@ -275,7 +295,10 @@ class IacProvider(Provider):
scan_dir = self.scan_path
try:
reports = self.run_scan(scan_dir, self.scanners, self.exclude_path)
# Collect all batches from the generator
reports = []
for batch in self.run_scan(scan_dir, self.scanners, self.exclude_path):
reports.extend(batch)
finally:
if temp_dir:
logger.info(f"Removing temporary directory {temp_dir}...")
@@ -285,7 +308,7 @@ class IacProvider(Provider):
def run_scan(
self, directory: str, scanners: list[str], exclude_path: list[str]
) -> List[CheckReportIAC]:
) -> Generator[List[CheckReportIAC], None, None]:
try:
logger.info(f"Running IaC scan on {directory} ...")
trivy_command = [
@@ -302,25 +325,47 @@ class IacProvider(Provider):
]
if exclude_path:
trivy_command.extend(["--skip-dirs", ",".join(exclude_path)])
with alive_bar(
ctrl_c=False,
bar="blocks",
spinner="classic",
stats=False,
enrich_print=False,
) as bar:
try:
bar.title = f"-> Running IaC scan on {directory} ..."
# Run Trivy with JSON output
# Check if we're in an environment with a TTY
try:
if sys.stdout.isatty():
with alive_bar(
ctrl_c=False,
bar="blocks",
spinner="classic",
stats=False,
enrich_print=False,
) as bar:
try:
bar.title = f"-> Running IaC scan on {directory} ..."
# Run Trivy with JSON output
process = subprocess.run(
trivy_command,
capture_output=True,
text=True,
)
bar.title = "-> Scan completed!"
except Exception as error:
bar.title = "-> Scan failed!"
raise error
else:
# No TTY, just run without progress bar
logger.info(f"Running Trivy scan on {directory}...")
process = subprocess.run(
trivy_command,
capture_output=True,
text=True,
)
bar.title = "-> Scan completed!"
except Exception as error:
bar.title = "-> Scan failed!"
raise error
logger.info("Trivy scan completed!")
except (AttributeError, OSError):
# Fallback if isatty() check fails
logger.info(f"Running Trivy scan on {directory}...")
process = subprocess.run(
trivy_command,
capture_output=True,
text=True,
)
logger.info("Trivy scan completed!")
# Log Trivy's stderr output with preserved log levels
if process.stderr:
for line in process.stderr.strip().split("\n"):
@@ -354,14 +399,15 @@ class IacProvider(Provider):
if not output:
logger.warning("No findings returned from Trivy scan")
return []
return
except Exception as error:
logger.critical(
f"{error.__class__.__name__}:{error.__traceback__.tb_lineno} -- {error}"
)
sys.exit(1)
reports = []
batch = []
batch_size = 100
# Process all trivy findings
for finding in output:
@@ -371,27 +417,44 @@ class IacProvider(Provider):
report = self._process_finding(
misconfiguration, finding["Target"], finding["Type"]
)
reports.append(report)
batch.append(report)
if len(batch) >= batch_size:
yield batch
batch = []
# Process Vulnerabilities
for vulnerability in finding.get("Vulnerabilities", []):
report = self._process_finding(
vulnerability, finding["Target"], finding["Type"]
)
reports.append(report)
batch.append(report)
if len(batch) >= batch_size:
yield batch
batch = []
# Process Secrets
for secret in finding.get("Secrets", []):
report = self._process_finding(
secret, finding["Target"], finding["Class"]
)
reports.append(report)
batch.append(report)
if len(batch) >= batch_size:
yield batch
batch = []
# Process Licenses
for license in finding.get("Licenses", []):
report = self._process_finding(
license, finding["Target"], finding["Type"]
)
reports.append(report)
batch.append(report)
if len(batch) >= batch_size:
yield batch
batch = []
return reports
# Yield any remaining findings in the last batch
if batch:
yield batch
except Exception as error:
if "No such file or directory: 'trivy'" in str(error):
@@ -434,3 +497,95 @@ class IacProvider(Provider):
)
print_boxes(report_lines, report_title)
@staticmethod
def test_connection(
scan_repository_url: str = None,
oauth_app_token: str = None,
access_token: str = None,
raise_on_exception: bool = True,
provider_id: str = None,
) -> "Connection":
"""Test connection to IaC repository.
Test the connection to the IaC repository using the provided credentials.
Args:
scan_repository_url (str): Repository URL to scan.
oauth_app_token (str): OAuth App token for authentication.
access_token (str): Access token for authentication (alias for oauth_app_token).
raise_on_exception (bool): Flag indicating whether to raise an exception if the connection fails.
provider_id (str): The provider ID, in this case it's the repository URL.
Returns:
Connection: Connection object with success status or error information.
Raises:
Exception: If failed to test the connection to the repository.
Examples:
>>> IacProvider.test_connection(scan_repository_url="https://github.com/user/repo")
Connection(is_connected=True)
"""
try:
# If provider_id is provided and scan_repository_url is not, use provider_id as the repository URL
if provider_id and not scan_repository_url:
scan_repository_url = provider_id
# Handle both oauth_app_token and access_token parameters
if access_token and not oauth_app_token:
oauth_app_token = access_token
if not scan_repository_url:
return Connection(
is_connected=False, error="Repository URL is required"
)
# Try to clone the repository to test the connection
with tempfile.TemporaryDirectory():
try:
if oauth_app_token:
# If token is provided, use it for authentication
# Extract the domain and path from the URL
url_pattern = r"(https?://)([^/]+)/(.+)"
match = re.match(url_pattern, scan_repository_url)
if match:
protocol, domain, path = match.groups()
# Construct URL with token
auth_url = f"{protocol}x-access-token:{oauth_app_token}@{domain}/{path}"
else:
auth_url = scan_repository_url
else:
# Public repository
auth_url = scan_repository_url
# Use dulwich to test the connection
porcelain.ls_remote(auth_url)
return Connection(is_connected=True)
except Exception as e:
error_msg = str(e)
if "authentication" in error_msg.lower() or "401" in error_msg:
return Connection(
is_connected=False,
error="Authentication failed. Please check your access token.",
)
elif "404" in error_msg or "not found" in error_msg.lower():
return Connection(
is_connected=False,
error="Repository not found or not accessible.",
)
else:
return Connection(
is_connected=False,
error=f"Failed to connect to repository: {error_msg}",
)
except Exception as error:
if raise_on_exception:
raise
return Connection(
is_connected=False,
error=f"Unexpected error testing connection: {str(error)}",
)
@@ -54,6 +54,7 @@ class MongodbatlasProvider(Provider):
mutelist_content: dict = None,
# Optional filters
atlas_project_id: str = None,
atlas_organization_id: str = None,
):
"""
MongoDB Atlas Provider constructor
@@ -67,6 +68,7 @@ class MongodbatlasProvider(Provider):
mutelist_path: Path to the mutelist file
mutelist_content: Mutelist content
atlas_project_id: Project ID to filter
atlas_organization_id: Organization ID
"""
logger.info("Instantiating MongoDB Atlas Provider...")
@@ -79,6 +81,7 @@ class MongodbatlasProvider(Provider):
# Store filter options
self._project_id = atlas_project_id
self._organization_id = atlas_organization_id
# Audit Config
if config_content:
@@ -292,6 +295,7 @@ class MongodbatlasProvider(Provider):
atlas_public_key: str = "",
atlas_private_key: str = "",
raise_on_exception: bool = True,
provider_id: str = None,
) -> Connection:
"""
Test connection to MongoDB Atlas
@@ -300,7 +304,7 @@ class MongodbatlasProvider(Provider):
atlas_public_key: MongoDB Atlas API public key
atlas_private_key: MongoDB Atlas API private key
raise_on_exception: Whether to raise exceptions
provider_id: MongoDB Atlas project ID to validate access (added for API compatibility)
Returns:
Connection: Connection status
"""
@@ -5,9 +5,11 @@ from prowler.providers.oraclecloud.config import OCI_DEFAULT_CONFIG_FILE, OCI_RE
def init_parser(self):
"""Init the OCI Provider CLI parser"""
"""Init the Oracle Cloud Infrastructure Provider CLI parser"""
oci_parser = self.subparsers.add_parser(
"oci", parents=[self.common_providers_parser], help="OCI Provider"
"oraclecloud",
parents=[self.common_providers_parser],
help="Oracle Cloud Infrastructure Provider",
)
# Config File Authentication Options
@@ -109,15 +111,4 @@ def validate_arguments(arguments: Namespace) -> tuple[bool, str]:
"Cannot use --use-instance-principal with --oci-config-file or --profile options",
)
# # Validate compartment OCIDs if provided
# if arguments.compartment_id:
# for compartment_id in arguments.compartment_id:
# if not OciProvider.validate_ocid(compartment_id, "compartment"):
# # Check if it's a tenancy OCID (root compartment)
# if not OciProvider.validate_ocid(compartment_id, "tenancy"):
# return (
# False,
# f"Invalid compartment OCID: {compartment_id}",
# )
return (True, "")
@@ -1,7 +1,7 @@
from concurrent.futures import ThreadPoolExecutor, as_completed
from prowler.lib.logger import logger
from prowler.providers.oraclecloud.oci_provider import OciProvider
from prowler.providers.oraclecloud.oraclecloud_provider import OraclecloudProvider
MAX_WORKERS = 10
@@ -16,13 +16,13 @@ class OCIService:
- Handles compartment traversal
"""
def __init__(self, service: str, provider: OciProvider):
def __init__(self, service: str, provider: OraclecloudProvider):
"""
Initialize the OCIService base class.
Args:
service (str): The OCI service name (e.g., 'compute', 'object_storage').
provider (OciProvider): The OCI provider instance.
provider (OraclecloudProvider): The Oracle Cloud Infrastructure provider instance.
"""
# Audit Information
self.provider = provider
@@ -40,9 +40,9 @@ from prowler.providers.oraclecloud.models import (
)
class OciProvider(Provider):
class OraclecloudProvider(Provider):
"""
OciProvider class is the main class for the OCI provider.
OraclecloudProvider class is the main class for the Oracle Cloud Infrastructure provider.
This class is responsible for initializing the OCI provider, setting up the OCI session,
validating the OCI credentials, getting the OCI identity, and managing compartments and regions.
@@ -58,7 +58,7 @@ class OciProvider(Provider):
audit_metadata (Audit_Metadata): The audit metadata.
"""
_type: str = "oci"
_type: str = "oraclecloud"
_identity: OCIIdentityInfo
_session: OCISession
_audit_config: dict
@@ -118,11 +118,11 @@ class OciProvider(Provider):
or
- export OCI_CLI_AUTH=instance_principal (for instance principal)
- To create a new OCI provider object:
- oci = OciProvider()
- oci = OciProvider(profile="profile_name")
- oci = OciProvider(oci_config_file="/path/to/config")
- oci = OciProvider(use_instance_principal=True)
- oci = OciProvider(user="ocid1...", fingerprint="...", key_content="...", tenancy="ocid1...", region="us-ashburn-1")
- oci = OraclecloudProvider()
- oci = OraclecloudProvider(profile="profile_name")
- oci = OraclecloudProvider(oci_config_file="/path/to/config")
- oci = OraclecloudProvider(use_instance_principal=True)
- oci = OraclecloudProvider(user="ocid1...", fingerprint="...", key_content="...", tenancy="ocid1...", region="us-ashburn-1")
"""
logger.info("Initializing OCI provider ...")
@@ -439,7 +439,7 @@ class OciProvider(Provider):
)
# Validate tenancy OCID format
if not OciProvider.validate_ocid(tenancy_id, "tenancy"):
if not OraclecloudProvider.validate_ocid(tenancy_id, "tenancy"):
raise OCIInvalidTenancyError(
file=pathlib.Path(__file__).name,
message=f"Invalid tenancy OCID format: {tenancy_id}",
@@ -817,11 +817,11 @@ class OciProvider(Provider):
Exception: If there is an unexpected error.
Examples:
>>> OciProvider.test_connection(profile="DEFAULT", raise_on_exception=False)
>>> OraclecloudProvider.test_connection(profile="DEFAULT", raise_on_exception=False)
Connection(is_connected=True, Error=None)
>>> OciProvider.test_connection(use_instance_principal=True, raise_on_exception=False)
>>> OraclecloudProvider.test_connection(use_instance_principal=True, raise_on_exception=False)
Connection(is_connected=True, Error=None)
>>> OciProvider.test_connection(
>>> OraclecloudProvider.test_connection(
user="ocid1.user.oc1..aaaaaa...",
fingerprint="12:34:56:78:...",
key_content="base64_encoded_key",
@@ -890,13 +890,13 @@ class OciProvider(Provider):
session = OCISession(config=config, signer=None, profile=None)
else:
# Use traditional config file or instance principal authentication
session = OciProvider.setup_session(
session = OraclecloudProvider.setup_session(
oci_config_file=oci_config_file,
profile=profile,
use_instance_principal=use_instance_principal,
)
identity = OciProvider.set_identity(
identity = OraclecloudProvider.set_identity(
session=session,
region=region,
)
@@ -1032,7 +1032,7 @@ class OciProvider(Provider):
set: A set of region names.
Example:
>>> OciProvider.get_regions()
>>> OraclecloudProvider.get_regions()
{"us-ashburn-1", "us-phoenix-1", ...}
"""
return set(OCI_REGIONS.keys())
@@ -1,34 +1,38 @@
{
"Provider": "oci",
"Provider": "oraclecloud",
"CheckID": "analytics_instance_access_restricted",
"CheckTitle": "Ensure Oracle Analytics Cloud (OAC) access is restricted to allowed sources or deployed within a Virtual Cloud Network",
"CheckType": [
"Software and Configuration Checks",
"Industry and Regulatory Standards",
"CIS OCI Foundations Benchmark"
],
"CheckTitle": "Oracle Analytics Cloud instance is deployed within a Virtual Cloud Network or restricts public access to allowed sources",
"CheckType": [],
"ServiceName": "analytics",
"SubServiceName": "",
"ResourceIdTemplate": "oci:analytics:instance",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "AnalyticsInstance",
"Description": "Oracle Analytics Cloud access should be restricted or deployed in VCN.",
"Risk": "Not meeting this network security requirement increases risk of unauthorized access.",
"RelatedUrl": "https://docs.oracle.com/en-us/iaas/Content/Network/home.htm",
"Description": "Oracle Analytics Cloud endpoints are evaluated for **network exposure**. Public endpoints must use **restricted allowlists** of specific IPs/CIDRs; presence of `0.0.0.0/0` or no allowed sources indicates unrestricted access. Instances using a **VCN/private endpoint** or public endpoints limited to specific sources align with the intended exposure model.",
"Risk": "Unrestricted OAC endpoints allow Internet-wide access to the login surface, enabling **credential stuffing** and **brute force**. Account takeover can expose **reports and data sources** (**confidentiality**), permit **dashboard/model changes** (**integrity**), and support **lateral movement** into connected systems.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://docs.public.content.oci.oraclecloud.com/en-us/iaas/analytics-cloud/doc/public-endpoints-and-access-control-rules.html",
"https://docs.oracle.com/en/cloud/paas/analytics-cloud/acsds/connect-databases-deployed-public-ip-address.html",
"https://docs.oracle.com/en/cloud/paas/analytics-cloud/acoci/top-faqs-public-or-private-endpoint-security.html",
"https://docs.oracle.com/en/cloud/paas/analytics-cloud/acoci/manage-ingress-access-rules-public-endpoint-using-console.html",
"https://docs.oracle.com/en-us/iaas/analytics-cloud/doc/public-endpoints-and-access-control-rules.html"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
"Other": "1. In OCI Console, go to Analytics & AI > Analytics Cloud and select your instance\n2. On Instance Details, under Network Access, click Edit next to Access Control\n3. Remove any 0.0.0.0/0 entry (if present)\n4. Add an access rule with the specific allowed public IP or CIDR\n5. Click Save",
"Terraform": "```hcl\nresource \"oci_analytics_analytics_instance\" \"example\" {\n compartment_id = \"<example_resource_id>\"\n name = \"<example_resource_name>\"\n feature_set = \"ENTERPRISE_ANALYTICS\"\n license_type = \"LICENSE_INCLUDED\"\n idcs_access_token = \"<example_resource_id>\"\n\n capacity {\n capacity_type = \"OLPU_COUNT\"\n capacity_value = 1\n }\n\n network_endpoint_details {\n network_endpoint_type = \"PUBLIC\"\n whitelisted_ips = [\"<example_resource_id>\"] # Critical: restrict to specific allowed CIDR; not 0.0.0.0/0\n }\n}\n```"
},
"Recommendation": {
"Text": "Ensure Oracle Analytics Cloud (OAC) access is restricted to allowed sources or deployed within a Virtual Cloud Network",
"Url": "https://hub.prowler.com/check/oci/analytics_instance_access_restricted"
"Text": "Prefer **private deployment in a VCN** and apply **least privilege** network access. *If public is required*, enforce **allowlists** to specific IPs/CIDRs and never include `0.0.0.0/0`. Use **private access channels/service gateways**, require **MFA/SSO**, and apply **defense in depth** (WAF, audit monitoring) to reduce exposure.",
"Url": "https://hub.prowler.com/check/analytics_instance_access_restricted"
}
},
"Categories": [
"network-security"
"internet-exposed",
"trust-boundaries"
],
"DependsOn": [],
"RelatedTo": [],
@@ -1,5 +1,5 @@
{
"Provider": "oci",
"Provider": "oraclecloud",
"CheckID": "audit_log_retention_period_365_days",
"CheckTitle": "Ensure audit log retention period is set to 365 days or greater",
"CheckType": [
@@ -1,5 +1,5 @@
{
"Provider": "oci",
"Provider": "oraclecloud",
"CheckID": "blockstorage_block_volume_encrypted_with_cmk",
"CheckTitle": "Ensure Block Volumes are encrypted with Customer Managed Keys",
"CheckType": [
@@ -1,5 +1,5 @@
{
"Provider": "oci",
"Provider": "oraclecloud",
"CheckID": "blockstorage_boot_volume_encrypted_with_cmk",
"CheckTitle": "Ensure Boot Volumes are encrypted with Customer Managed Key",
"CheckType": [
@@ -1,5 +1,5 @@
{
"Provider": "oci",
"Provider": "oraclecloud",
"CheckID": "cloudguard_enabled",
"CheckTitle": "Ensure Cloud Guard is enabled in the root compartment of the tenancy",
"CheckType": [
@@ -1,5 +1,5 @@
{
"Provider": "oci",
"Provider": "oraclecloud",
"CheckID": "compute_instance_in_transit_encryption_enabled",
"CheckTitle": "Ensure In-transit Encryption is enabled on Compute Instance",
"CheckType": [
@@ -1,5 +1,5 @@
{
"Provider": "oci",
"Provider": "oraclecloud",
"CheckID": "compute_instance_legacy_metadata_endpoint_disabled",
"CheckTitle": "Ensure Compute Instance Legacy Metadata service endpoint is disabled",
"CheckType": [
@@ -1,5 +1,5 @@
{
"Provider": "oci",
"Provider": "oraclecloud",
"CheckID": "compute_instance_secure_boot_enabled",
"CheckTitle": "Ensure Secure Boot is enabled on Compute Instance",
"CheckType": [
Loaded 100 of 271 files, more files were not shown because too many files have changed in this diff. Show more