mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 12:31:54 +00:00
Merge branch 'master' into DEVREL-98-include-open-api-specification-in-mintlify
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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>"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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 }}'
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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)(\([^)]+\))?!?: .+'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -382,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 }}
|
||||
|
||||
@@ -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 }}'
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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 }}'
|
||||
|
||||
@@ -75,7 +75,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'
|
||||
|
||||
@@ -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.
|
||||
|
||||

|
||||

|
||||
|
||||
>For more details, refer to the [Prowler App Documentation](https://docs.prowler.com/projects/prowler-open-source/en/latest/#prowler-app-installation)
|
||||
|
||||
@@ -73,7 +73,7 @@ prowler <provider>
|
||||
```console
|
||||
prowler dashboard
|
||||
```
|
||||

|
||||

|
||||
|
||||
# Prowler at a Glance
|
||||
> [!Tip]
|
||||
@@ -90,7 +90,7 @@ prowler dashboard
|
||||
| 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 |
|
||||
| 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 |
|
||||
|
||||
|
||||
@@ -15,9 +15,15 @@ All notable changes to the **Prowler API** are documented in this file.
|
||||
- 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)
|
||||
- OpenAPI schema integration with Mintlify documentation including compatibility fixes and dynamic server URL configuration
|
||||
- Support for MongoDB Atlas provider [(#9167)](https://github.com/prowler-cloud/prowler/pull/9167)
|
||||
|
||||
---
|
||||
|
||||
## [1.14.2] (Prowler 5.13.2)
|
||||
|
||||
### Fixed
|
||||
- Update unique constraint for `Provider` model to exclude soft-deleted entries, resolving duplicate errors when re-deleting providers.
|
||||
|
||||
## [1.14.1] (Prowler 5.13.1)
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -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",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,24 @@
|
||||
# Generated by Django 5.1.13 on 2025-11-06 09:20
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("api", "0055_mongodbatlas_provider"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveConstraint(
|
||||
model_name="provider",
|
||||
name="unique_provider_uids",
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name="provider",
|
||||
constraint=models.UniqueConstraint(
|
||||
condition=models.Q(("is_deleted", False)),
|
||||
fields=("tenant_id", "provider", "uid"),
|
||||
name="unique_provider_uids",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -284,6 +284,7 @@ class Provider(RowLevelSecurityProtectedModel):
|
||||
KUBERNETES = "kubernetes", _("Kubernetes")
|
||||
M365 = "m365", _("M365")
|
||||
GITHUB = "github", _("GitHub")
|
||||
MONGODBATLAS = "mongodbatlas", _("MongoDB Atlas")
|
||||
IAC = "iac", _("IaC")
|
||||
ORACLECLOUD = "oraclecloud", _("Oracle Cloud Infrastructure")
|
||||
|
||||
@@ -381,6 +382,15 @@ class Provider(RowLevelSecurityProtectedModel):
|
||||
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",
|
||||
)
|
||||
|
||||
id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
|
||||
inserted_at = models.DateTimeField(auto_now_add=True, editable=False)
|
||||
updated_at = models.DateTimeField(auto_now=True, editable=False)
|
||||
@@ -415,7 +425,8 @@ class Provider(RowLevelSecurityProtectedModel):
|
||||
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
fields=("tenant_id", "provider", "uid", "is_deleted"),
|
||||
fields=("tenant_id", "provider", "uid"),
|
||||
condition=Q(is_deleted=False),
|
||||
name="unique_provider_uids",
|
||||
),
|
||||
RowLevelSecurityConstraint(
|
||||
|
||||
@@ -875,6 +875,7 @@ paths:
|
||||
- azure
|
||||
- gcp
|
||||
- github
|
||||
- mongodbatlas
|
||||
- iac
|
||||
- kubernetes
|
||||
- m365
|
||||
@@ -886,6 +887,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
- in: query
|
||||
@@ -901,6 +903,7 @@ paths:
|
||||
- gcp
|
||||
- github
|
||||
- iac
|
||||
- mongodbatlas
|
||||
- kubernetes
|
||||
- m365
|
||||
- oraclecloud
|
||||
@@ -913,6 +916,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
explode: false
|
||||
@@ -1410,6 +1414,7 @@ paths:
|
||||
- azure
|
||||
- gcp
|
||||
- github
|
||||
- mongodbatlas
|
||||
- iac
|
||||
- kubernetes
|
||||
- m365
|
||||
@@ -1421,6 +1426,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
- in: query
|
||||
@@ -1436,6 +1442,7 @@ paths:
|
||||
- gcp
|
||||
- github
|
||||
- iac
|
||||
- mongodbatlas
|
||||
- kubernetes
|
||||
- m365
|
||||
- oraclecloud
|
||||
@@ -1448,6 +1455,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
explode: false
|
||||
@@ -1853,6 +1861,7 @@ paths:
|
||||
- azure
|
||||
- gcp
|
||||
- github
|
||||
- mongodbatlas
|
||||
- iac
|
||||
- kubernetes
|
||||
- m365
|
||||
@@ -1864,6 +1873,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
- in: query
|
||||
@@ -1879,6 +1889,7 @@ paths:
|
||||
- gcp
|
||||
- github
|
||||
- iac
|
||||
- mongodbatlas
|
||||
- kubernetes
|
||||
- m365
|
||||
- oraclecloud
|
||||
@@ -1891,6 +1902,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
explode: false
|
||||
@@ -2294,6 +2306,7 @@ paths:
|
||||
- azure
|
||||
- gcp
|
||||
- github
|
||||
- mongodbatlas
|
||||
- iac
|
||||
- kubernetes
|
||||
- m365
|
||||
@@ -2305,6 +2318,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
- in: query
|
||||
@@ -2320,6 +2334,7 @@ paths:
|
||||
- gcp
|
||||
- github
|
||||
- iac
|
||||
- mongodbatlas
|
||||
- kubernetes
|
||||
- m365
|
||||
- oraclecloud
|
||||
@@ -2332,6 +2347,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
explode: false
|
||||
@@ -2723,6 +2739,7 @@ paths:
|
||||
- azure
|
||||
- gcp
|
||||
- github
|
||||
- mongodbatlas
|
||||
- iac
|
||||
- kubernetes
|
||||
- m365
|
||||
@@ -2734,6 +2751,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
- in: query
|
||||
@@ -2749,6 +2767,7 @@ paths:
|
||||
- gcp
|
||||
- github
|
||||
- iac
|
||||
- mongodbatlas
|
||||
- kubernetes
|
||||
- m365
|
||||
- oraclecloud
|
||||
@@ -2761,6 +2780,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
explode: false
|
||||
@@ -4556,6 +4576,7 @@ paths:
|
||||
- azure
|
||||
- gcp
|
||||
- github
|
||||
- mongodbatlas
|
||||
- iac
|
||||
- kubernetes
|
||||
- m365
|
||||
@@ -4567,6 +4588,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
- in: query
|
||||
@@ -4582,6 +4604,7 @@ paths:
|
||||
- gcp
|
||||
- github
|
||||
- iac
|
||||
- mongodbatlas
|
||||
- kubernetes
|
||||
- m365
|
||||
- oraclecloud
|
||||
@@ -4594,6 +4617,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
explode: false
|
||||
@@ -4741,6 +4765,7 @@ paths:
|
||||
- azure
|
||||
- gcp
|
||||
- github
|
||||
- mongodbatlas
|
||||
- iac
|
||||
- kubernetes
|
||||
- m365
|
||||
@@ -4752,6 +4777,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
- in: query
|
||||
@@ -4767,6 +4793,7 @@ paths:
|
||||
- gcp
|
||||
- github
|
||||
- iac
|
||||
- mongodbatlas
|
||||
- kubernetes
|
||||
- m365
|
||||
- oraclecloud
|
||||
@@ -4779,6 +4806,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
explode: false
|
||||
@@ -4992,6 +5020,7 @@ paths:
|
||||
- azure
|
||||
- gcp
|
||||
- github
|
||||
- mongodbatlas
|
||||
- iac
|
||||
- kubernetes
|
||||
- m365
|
||||
@@ -5003,6 +5032,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
- in: query
|
||||
@@ -5018,6 +5048,7 @@ paths:
|
||||
- gcp
|
||||
- github
|
||||
- iac
|
||||
- mongodbatlas
|
||||
- kubernetes
|
||||
- m365
|
||||
- oraclecloud
|
||||
@@ -5030,6 +5061,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
explode: false
|
||||
@@ -5723,6 +5755,7 @@ paths:
|
||||
- azure
|
||||
- gcp
|
||||
- github
|
||||
- mongodbatlas
|
||||
- iac
|
||||
- kubernetes
|
||||
- m365
|
||||
@@ -5734,6 +5767,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
- in: query
|
||||
@@ -5749,6 +5783,7 @@ paths:
|
||||
- gcp
|
||||
- github
|
||||
- iac
|
||||
- mongodbatlas
|
||||
- kubernetes
|
||||
- m365
|
||||
- oraclecloud
|
||||
@@ -5762,6 +5797,7 @@ paths:
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
explode: false
|
||||
style: form
|
||||
@@ -6418,6 +6454,7 @@ paths:
|
||||
- azure
|
||||
- gcp
|
||||
- github
|
||||
- mongodbatlas
|
||||
- iac
|
||||
- kubernetes
|
||||
- m365
|
||||
@@ -6429,6 +6466,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
- in: query
|
||||
@@ -6444,6 +6482,7 @@ paths:
|
||||
- gcp
|
||||
- github
|
||||
- iac
|
||||
- mongodbatlas
|
||||
- kubernetes
|
||||
- m365
|
||||
- oraclecloud
|
||||
@@ -6456,6 +6495,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
explode: false
|
||||
@@ -6791,6 +6831,7 @@ paths:
|
||||
- azure
|
||||
- gcp
|
||||
- github
|
||||
- mongodbatlas
|
||||
- iac
|
||||
- kubernetes
|
||||
- m365
|
||||
@@ -6802,6 +6843,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
- in: query
|
||||
@@ -6817,6 +6859,7 @@ paths:
|
||||
- gcp
|
||||
- github
|
||||
- iac
|
||||
- mongodbatlas
|
||||
- kubernetes
|
||||
- m365
|
||||
- oraclecloud
|
||||
@@ -6829,6 +6872,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
explode: false
|
||||
@@ -7065,6 +7109,7 @@ paths:
|
||||
- azure
|
||||
- gcp
|
||||
- github
|
||||
- mongodbatlas
|
||||
- iac
|
||||
- kubernetes
|
||||
- m365
|
||||
@@ -7076,6 +7121,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
- in: query
|
||||
@@ -7091,6 +7137,7 @@ paths:
|
||||
- gcp
|
||||
- github
|
||||
- iac
|
||||
- mongodbatlas
|
||||
- kubernetes
|
||||
- m365
|
||||
- oraclecloud
|
||||
@@ -7103,6 +7150,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
explode: false
|
||||
@@ -7345,6 +7393,7 @@ paths:
|
||||
- azure
|
||||
- gcp
|
||||
- github
|
||||
- mongodbatlas
|
||||
- iac
|
||||
- kubernetes
|
||||
- m365
|
||||
@@ -7356,6 +7405,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
- in: query
|
||||
@@ -7371,6 +7421,7 @@ paths:
|
||||
- gcp
|
||||
- github
|
||||
- iac
|
||||
- mongodbatlas
|
||||
- kubernetes
|
||||
- m365
|
||||
- oraclecloud
|
||||
@@ -7383,6 +7434,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
explode: false
|
||||
@@ -8188,6 +8240,7 @@ paths:
|
||||
- azure
|
||||
- gcp
|
||||
- github
|
||||
- mongodbatlas
|
||||
- iac
|
||||
- kubernetes
|
||||
- m365
|
||||
@@ -8199,6 +8252,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
- in: query
|
||||
@@ -8214,6 +8268,7 @@ paths:
|
||||
- gcp
|
||||
- github
|
||||
- iac
|
||||
- mongodbatlas
|
||||
- kubernetes
|
||||
- m365
|
||||
- oraclecloud
|
||||
@@ -8226,6 +8281,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `iac` - IaC
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
explode: false
|
||||
@@ -14251,7 +14307,7 @@ components:
|
||||
type: string
|
||||
description: GitHub OAuth App token for authentication.
|
||||
required:
|
||||
- oauth_app_token
|
||||
- oauth_app_token
|
||||
- type: object
|
||||
title: GitHub App Credentials
|
||||
properties:
|
||||
@@ -14264,6 +14320,18 @@ components:
|
||||
required:
|
||||
- github_app_id
|
||||
- github_app_key
|
||||
- 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
|
||||
- type: object
|
||||
title: IaC Repository Credentials
|
||||
properties:
|
||||
@@ -15290,6 +15358,7 @@ components:
|
||||
- m365
|
||||
- github
|
||||
- iac
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
type: string
|
||||
description: |-
|
||||
@@ -15300,6 +15369,7 @@ components:
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
x-spec-enum-id: cfe6f885ff538a1a
|
||||
uid:
|
||||
@@ -15414,6 +15484,7 @@ components:
|
||||
- m365
|
||||
- github
|
||||
- iac
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
type: string
|
||||
x-spec-enum-id: cfe6f885ff538a1a
|
||||
@@ -15427,12 +15498,13 @@ components:
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
uid:
|
||||
type: string
|
||||
title: Unique identifier for the provider, set by the provider
|
||||
description: Unique identifier for the provider, set by the provider,
|
||||
e.g. AWS account ID, Azure subscription ID, GCP project ID, etc.
|
||||
e.g. AWS account ID, Azure subscription ID, GCP project ID, MongoDB Atlas organization ID, etc.
|
||||
maxLength: 250
|
||||
minLength: 3
|
||||
required:
|
||||
@@ -15472,6 +15544,7 @@ components:
|
||||
- m365
|
||||
- github
|
||||
- iac
|
||||
- mongodbatlas
|
||||
- oraclecloud
|
||||
type: string
|
||||
x-spec-enum-id: cfe6f885ff538a1a
|
||||
@@ -15485,13 +15558,14 @@ components:
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
* `mongodbatlas` - MongoDB Atlas
|
||||
* `oraclecloud` - Oracle Cloud Infrastructure
|
||||
uid:
|
||||
type: string
|
||||
minLength: 3
|
||||
title: Unique identifier for the provider, set by the provider
|
||||
description: Unique identifier for the provider, set by the provider,
|
||||
e.g. AWS account ID, Azure subscription ID, GCP project ID, etc.
|
||||
e.g. AWS account ID, Azure subscription ID, GCP project ID, MongoDB Atlas organization ID, etc.
|
||||
maxLength: 250
|
||||
required:
|
||||
- uid
|
||||
|
||||
@@ -20,8 +20,10 @@ 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.mongodbatlas.mongodbatlas_provider import MongodbatlasProvider
|
||||
from prowler.providers.oraclecloud.oraclecloud_provider import OraclecloudProvider
|
||||
|
||||
|
||||
@@ -109,6 +111,8 @@ class TestReturnProwlerProvider:
|
||||
(Provider.ProviderChoices.AZURE.value, AzureProvider),
|
||||
(Provider.ProviderChoices.KUBERNETES.value, KubernetesProvider),
|
||||
(Provider.ProviderChoices.M365.value, M365Provider),
|
||||
(Provider.ProviderChoices.GITHUB.value, GithubProvider),
|
||||
(Provider.ProviderChoices.MONGODBATLAS.value, MongodbatlasProvider),
|
||||
(Provider.ProviderChoices.ORACLECLOUD.value, OraclecloudProvider),
|
||||
],
|
||||
)
|
||||
@@ -209,6 +213,10 @@ class TestGetProwlerProviderKwargs:
|
||||
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):
|
||||
|
||||
@@ -1153,6 +1153,11 @@ class TestProviderViewSet:
|
||||
"uid": "https://gitlab.com/user/project",
|
||||
"alias": "GitLab Repo",
|
||||
},
|
||||
{
|
||||
"provider": "mongodbatlas",
|
||||
"uid": "64b1d3c0e4b03b1234567890",
|
||||
"alias": "Atlas Organization",
|
||||
},
|
||||
]
|
||||
),
|
||||
)
|
||||
@@ -1166,6 +1171,161 @@ class TestProviderViewSet:
|
||||
assert Provider.objects.get().uid == provider_json_payload["uid"]
|
||||
assert Provider.objects.get().alias == provider_json_payload["alias"]
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"provider_json_payload",
|
||||
(
|
||||
[
|
||||
{"provider": "aws", "uid": "111111111111", "alias": "test"},
|
||||
{"provider": "gcp", "uid": "a12322-test54321", "alias": "test"},
|
||||
{
|
||||
"provider": "kubernetes",
|
||||
"uid": "kubernetes-test-123456789",
|
||||
"alias": "test",
|
||||
},
|
||||
{
|
||||
"provider": "kubernetes",
|
||||
"uid": "arn:aws:eks:us-east-1:111122223333:cluster/test-cluster-long-name-123456789",
|
||||
"alias": "EKS",
|
||||
},
|
||||
{
|
||||
"provider": "kubernetes",
|
||||
"uid": "gke_aaaa-dev_europe-test1_dev-aaaa-test-cluster-long-name-123456789",
|
||||
"alias": "GKE",
|
||||
},
|
||||
{
|
||||
"provider": "kubernetes",
|
||||
"uid": "gke_project/cluster-name",
|
||||
"alias": "GKE",
|
||||
},
|
||||
{
|
||||
"provider": "kubernetes",
|
||||
"uid": "admin@k8s-demo",
|
||||
"alias": "test",
|
||||
},
|
||||
{
|
||||
"provider": "azure",
|
||||
"uid": "8851db6b-42e5-4533-aa9e-30a32d67e875",
|
||||
"alias": "test",
|
||||
},
|
||||
{
|
||||
"provider": "m365",
|
||||
"uid": "TestingPro.onmicrosoft.com",
|
||||
"alias": "test",
|
||||
},
|
||||
{
|
||||
"provider": "m365",
|
||||
"uid": "subdomain.domain.es",
|
||||
"alias": "test",
|
||||
},
|
||||
{
|
||||
"provider": "m365",
|
||||
"uid": "microsoft.net",
|
||||
"alias": "test",
|
||||
},
|
||||
{
|
||||
"provider": "m365",
|
||||
"uid": "subdomain1.subdomain2.subdomain3.subdomain4.domain.net",
|
||||
"alias": "test",
|
||||
},
|
||||
{
|
||||
"provider": "github",
|
||||
"uid": "test-user",
|
||||
"alias": "test",
|
||||
},
|
||||
{
|
||||
"provider": "github",
|
||||
"uid": "test-organization",
|
||||
"alias": "GitHub Org",
|
||||
},
|
||||
{
|
||||
"provider": "github",
|
||||
"uid": "prowler-cloud",
|
||||
"alias": "Prowler",
|
||||
},
|
||||
{
|
||||
"provider": "github",
|
||||
"uid": "microsoft",
|
||||
"alias": "Microsoft",
|
||||
},
|
||||
{
|
||||
"provider": "github",
|
||||
"uid": "a12345678901234567890123456789012345678",
|
||||
"alias": "Long Username",
|
||||
},
|
||||
]
|
||||
),
|
||||
)
|
||||
@patch("api.v1.views.Task.objects.get")
|
||||
@patch("api.v1.views.delete_provider_task.delay")
|
||||
def test_providers_soft_delete(
|
||||
self,
|
||||
mock_delete_task,
|
||||
mock_task_get,
|
||||
authenticated_client,
|
||||
provider_json_payload,
|
||||
tasks_fixture,
|
||||
):
|
||||
# Mock the Celery task response
|
||||
prowler_task = tasks_fixture[0]
|
||||
task_mock = Mock()
|
||||
task_mock.id = prowler_task.id
|
||||
mock_delete_task.return_value = task_mock
|
||||
mock_task_get.return_value = prowler_task
|
||||
|
||||
# 1.Create a provider
|
||||
response = authenticated_client.post(
|
||||
reverse("provider-list"), data=provider_json_payload, format="json"
|
||||
)
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
assert Provider.objects.count() == 1
|
||||
provider_id = response.json()["data"]["id"]
|
||||
|
||||
# 2. Soft delete the provider using the actual API endpoint
|
||||
response = authenticated_client.delete(
|
||||
reverse("provider-detail", kwargs={"pk": provider_id})
|
||||
)
|
||||
assert response.status_code == status.HTTP_202_ACCEPTED
|
||||
assert Provider.objects.count() == 0
|
||||
assert Provider.all_objects.count() == 1
|
||||
|
||||
mock_delete_task.assert_called_once_with(
|
||||
provider_id=str(provider_id), tenant_id=ANY
|
||||
)
|
||||
|
||||
# 3. Create a provider with the same UID should succeed (since the old one is soft deleted)
|
||||
response = authenticated_client.post(
|
||||
reverse("provider-list"), data=provider_json_payload, format="json"
|
||||
)
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
assert Provider.objects.count() == 1
|
||||
assert Provider.all_objects.count() == 2
|
||||
provider_id = response.json()["data"]["id"]
|
||||
|
||||
# 4. Creating another provider with the same UID should fail (duplicate)
|
||||
response = authenticated_client.post(
|
||||
reverse("provider-list"), data=provider_json_payload, format="json"
|
||||
)
|
||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
|
||||
mock_delete_task.reset_mock()
|
||||
mock_delete_task.return_value = task_mock
|
||||
|
||||
# 5. Delete the second provider
|
||||
response = authenticated_client.delete(
|
||||
reverse("provider-detail", kwargs={"pk": provider_id})
|
||||
)
|
||||
assert response.status_code == status.HTTP_202_ACCEPTED
|
||||
assert Provider.objects.count() == 0
|
||||
assert Provider.all_objects.count() == 2
|
||||
|
||||
# 6. Creating a provider with the same UID should succeed again
|
||||
response = authenticated_client.post(
|
||||
reverse("provider-list"), data=provider_json_payload, format="json"
|
||||
)
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
assert Provider.objects.count() == 1
|
||||
assert Provider.all_objects.count() == 3
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"provider_json_payload, error_code, error_pointer",
|
||||
(
|
||||
@@ -1329,6 +1489,24 @@ class TestProviderViewSet:
|
||||
"iac-uid",
|
||||
"uid",
|
||||
),
|
||||
(
|
||||
{
|
||||
"provider": "mongodbatlas",
|
||||
"uid": "64b1d3c0e4b03b123456789g",
|
||||
"alias": "test",
|
||||
},
|
||||
"mongodbatlas-uid",
|
||||
"uid",
|
||||
),
|
||||
(
|
||||
{
|
||||
"provider": "mongodbatlas",
|
||||
"uid": "1234",
|
||||
"alias": "test",
|
||||
},
|
||||
"mongodbatlas-uid",
|
||||
"uid",
|
||||
),
|
||||
]
|
||||
),
|
||||
)
|
||||
@@ -1502,22 +1680,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),
|
||||
]
|
||||
),
|
||||
@@ -2057,6 +2235,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(
|
||||
|
||||
@@ -21,6 +21,7 @@ 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.mongodbatlas.mongodbatlas_provider import MongodbatlasProvider
|
||||
from prowler.providers.oraclecloud.oraclecloud_provider import OraclecloudProvider
|
||||
|
||||
|
||||
@@ -70,6 +71,7 @@ def return_prowler_provider(
|
||||
| IacProvider
|
||||
| KubernetesProvider
|
||||
| M365Provider
|
||||
| MongodbatlasProvider
|
||||
| OraclecloudProvider
|
||||
]:
|
||||
"""Return the Prowler provider class based on the given provider type.
|
||||
@@ -78,7 +80,7 @@ def return_prowler_provider(
|
||||
provider (Provider): The provider object containing the provider type and associated secrets.
|
||||
|
||||
Returns:
|
||||
AwsProvider | AzureProvider | GcpProvider | GithubProvider | IacProvider | KubernetesProvider | M365Provider | OraclecloudProvider: 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.
|
||||
@@ -96,6 +98,8 @@ def return_prowler_provider(
|
||||
prowler_provider = M365Provider
|
||||
case Provider.ProviderChoices.GITHUB.value:
|
||||
prowler_provider = GithubProvider
|
||||
case Provider.ProviderChoices.MONGODBATLAS.value:
|
||||
prowler_provider = MongodbatlasProvider
|
||||
case Provider.ProviderChoices.IAC.value:
|
||||
prowler_provider = IacProvider
|
||||
case Provider.ProviderChoices.ORACLECLOUD.value:
|
||||
@@ -146,6 +150,11 @@ def get_prowler_provider_kwargs(
|
||||
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", {})
|
||||
@@ -166,6 +175,7 @@ def initialize_prowler_provider(
|
||||
| IacProvider
|
||||
| KubernetesProvider
|
||||
| M365Provider
|
||||
| MongodbatlasProvider
|
||||
| OraclecloudProvider
|
||||
):
|
||||
"""Initialize a Prowler provider instance based on the given provider type.
|
||||
@@ -175,8 +185,8 @@ def initialize_prowler_provider(
|
||||
mutelist_processor (Processor): The mutelist processor object containing the mutelist configuration.
|
||||
|
||||
Returns:
|
||||
AwsProvider | AzureProvider | GcpProvider | GithubProvider | IacProvider | KubernetesProvider | M365Provider | OciProvider: An instance of the corresponding provider class
|
||||
(`AwsProvider`, `AzureProvider`, `GcpProvider`, `GithubProvider`, `IacProvider`, `KubernetesProvider`, `M365Provider` or `OraclecloudProvider`) 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)
|
||||
|
||||
@@ -289,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"],
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1370,6 +1370,8 @@ class BaseWriteProviderSecretSerializer(BaseWriteSerializer):
|
||||
serializer = M365ProviderSecret(data=secret)
|
||||
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}"}
|
||||
@@ -1466,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()
|
||||
|
||||
|
||||
@@ -506,8 +506,23 @@ def providers_fixture(tenants_fixture):
|
||||
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
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
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"
|
||||
)
|
||||
@@ -33,7 +33,7 @@ The supported providers right now are:
|
||||
| [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 | UI, API, CLI |
|
||||
| [MongoDB Atlas](/user-guide/providers/mongodbatlas/getting-started-mongodbatlas) | Official | 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>
|
||||
|
||||
Generated
+119
-98
@@ -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]]
|
||||
|
||||
+16
-1
@@ -13,7 +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)
|
||||
@@ -32,12 +37,22 @@ 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)
|
||||
- Depth Truncation and parsing error in PowerShell queries [(#9181)](https://github.com/prowler-cloud/prowler/pull/9181)
|
||||
|
||||
---
|
||||
|
||||
## [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)
|
||||
|
||||
@@ -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,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,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,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.",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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])
|
||||
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
@@ -220,18 +220,19 @@ class PowerShellSession:
|
||||
if output == "":
|
||||
return {}
|
||||
|
||||
json_match = re.search(r"(\[.*\]|\{.*\})", output, re.DOTALL)
|
||||
if not json_match:
|
||||
logger.error(
|
||||
f"Unexpected PowerShell output: {output}\n",
|
||||
)
|
||||
else:
|
||||
decoder = json.JSONDecoder()
|
||||
for index, character in enumerate(output):
|
||||
if character not in ("{", "["):
|
||||
continue
|
||||
try:
|
||||
return json.loads(json_match.group(1))
|
||||
except json.JSONDecodeError as error:
|
||||
logger.error(
|
||||
f"Error parsing PowerShell output as JSON: {str(error)}\n",
|
||||
)
|
||||
parsed_json, _ = decoder.raw_decode(output[index:])
|
||||
return parsed_json
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
logger.error(
|
||||
f"Unexpected PowerShell output: {output}\n",
|
||||
)
|
||||
|
||||
return {}
|
||||
|
||||
|
||||
+30
@@ -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": ""
|
||||
}
|
||||
+106
@@ -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
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.lib.powershell.powershell import PowerShellSession
|
||||
@@ -11,6 +12,7 @@ from prowler.providers.m365.models import M365Credentials, M365IdentityInfo
|
||||
|
||||
|
||||
class M365PowerShell(PowerShellSession):
|
||||
CONNECT_TIMEOUT = 15
|
||||
"""
|
||||
Microsoft 365 specific PowerShell session management implementation.
|
||||
|
||||
@@ -123,6 +125,23 @@ class M365PowerShell(PowerShellSession):
|
||||
'$graphToken = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantID/oauth2/v2.0/token" -Method POST -Body $graphtokenBody | Select-Object -ExpandProperty Access_Token'
|
||||
)
|
||||
|
||||
def _execute_connect_command(
|
||||
self, command: str, timeout: Optional[int] = None
|
||||
) -> str:
|
||||
"""
|
||||
Execute a PowerShell connect command ensuring empty responses surface as timeouts.
|
||||
|
||||
Args:
|
||||
command (str): PowerShell connect command to run.
|
||||
timeout (Optional[int]): Timeout in seconds for the command execution.
|
||||
|
||||
Returns:
|
||||
str: Command output or 'Timeout' if the command produced no output.
|
||||
"""
|
||||
effective_timeout = timeout or self.CONNECT_TIMEOUT
|
||||
result = self.execute(command, timeout=effective_timeout)
|
||||
return result or "Timeout"
|
||||
|
||||
def test_credentials(self, credentials: M365Credentials) -> bool:
|
||||
"""
|
||||
Test Microsoft 365 credentials by attempting to authenticate against Entra ID.
|
||||
@@ -141,24 +160,32 @@ class M365PowerShell(PowerShellSession):
|
||||
# Test Certificate Auth
|
||||
if credentials.certificate_content and credentials.client_id:
|
||||
try:
|
||||
self.test_teams_certificate_connection() or self.test_exchange_certificate_connection()
|
||||
logger.info("Testing Microsoft Graph Certificate connection...")
|
||||
self.test_graph_certificate_connection()
|
||||
logger.info("Microsoft Graph Certificate connection successful")
|
||||
teams_connection_successful = self.test_teams_certificate_connection()
|
||||
if not teams_connection_successful:
|
||||
self.test_exchange_certificate_connection()
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Exchange Online Certificate connection failed: {e}")
|
||||
|
||||
else:
|
||||
# Test Microsoft Graph connection
|
||||
try:
|
||||
logger.info("Testing Microsoft Graph connection...")
|
||||
self.test_graph_connection()
|
||||
logger.info("Microsoft Graph connection successful")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Microsoft Graph connection failed: {e}")
|
||||
logger.error(f"Microsoft Graph Cer connection failed: {e}")
|
||||
raise M365GraphConnectionError(
|
||||
file=os.path.basename(__file__),
|
||||
original_exception=e,
|
||||
message="Check your Microsoft Application credentials and ensure the app has proper permissions",
|
||||
message="Check your Microsoft Application Certificate and ensure the app has proper permissions",
|
||||
)
|
||||
else:
|
||||
try:
|
||||
logger.info("Testing Microsoft Graph Client Secret connection...")
|
||||
self.test_graph_connection()
|
||||
logger.info("Microsoft Graph Client Secret connection successful")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Microsoft Graph Client Secret connection failed: {e}")
|
||||
raise M365GraphConnectionError(
|
||||
file=os.path.basename(__file__),
|
||||
original_exception=e,
|
||||
message="Check your Microsoft Application Client Secret and ensure the app has proper permissions",
|
||||
)
|
||||
|
||||
def test_graph_connection(self) -> bool:
|
||||
@@ -178,6 +205,16 @@ class M365PowerShell(PowerShellSession):
|
||||
message=f"Failed to connect to Microsoft Graph API: {str(e)}",
|
||||
)
|
||||
|
||||
def test_graph_certificate_connection(self) -> bool:
|
||||
"""Test Microsoft Graph API connection using certificate and raise exception if it fails."""
|
||||
result = self._execute_connect_command(
|
||||
"Connect-Graph -Certificate $certificate -AppId $clientID -TenantId $tenantID"
|
||||
)
|
||||
if "Welcome to Microsoft Graph!" not in result:
|
||||
logger.error(f"Microsoft Graph Certificate connection failed: {result}")
|
||||
return False
|
||||
return True
|
||||
|
||||
def test_teams_connection(self) -> bool:
|
||||
"""Test Microsoft Teams API connection and raise exception if it fails."""
|
||||
try:
|
||||
@@ -195,7 +232,7 @@ class M365PowerShell(PowerShellSession):
|
||||
"Microsoft Teams connection failed: Please check your permissions and try again."
|
||||
)
|
||||
return False
|
||||
self.execute(
|
||||
self._execute_connect_command(
|
||||
'Connect-MicrosoftTeams -AccessTokens @("$graphToken","$teamsToken")'
|
||||
)
|
||||
return True
|
||||
@@ -207,7 +244,7 @@ class M365PowerShell(PowerShellSession):
|
||||
|
||||
def test_teams_certificate_connection(self) -> bool:
|
||||
"""Test Microsoft Teams API connection using certificate and raise exception if it fails."""
|
||||
result = self.execute(
|
||||
result = self._execute_connect_command(
|
||||
"Connect-MicrosoftTeams -Certificate $certificate -ApplicationId $clientID -TenantId $tenantID"
|
||||
)
|
||||
if self.tenant_identity.identity_id not in result:
|
||||
@@ -231,8 +268,9 @@ class M365PowerShell(PowerShellSession):
|
||||
"Exchange Online connection failed: Please check your permissions and try again."
|
||||
)
|
||||
return False
|
||||
self.execute(
|
||||
'Connect-ExchangeOnline -AccessToken $exchangeToken.AccessToken -Organization "$tenantID"'
|
||||
self._execute_connect_command(
|
||||
'Connect-ExchangeOnline -AccessToken $exchangeToken.AccessToken -Organization "$tenantID"',
|
||||
timeout=self.CONNECT_TIMEOUT,
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
@@ -243,8 +281,9 @@ class M365PowerShell(PowerShellSession):
|
||||
|
||||
def test_exchange_certificate_connection(self) -> bool:
|
||||
"""Test Exchange Online API connection using certificate and raise exception if it fails."""
|
||||
result = self.execute(
|
||||
"Connect-ExchangeOnline -Certificate $certificate -AppId $clientID -Organization $tenantDomain"
|
||||
result = self._execute_connect_command(
|
||||
"Connect-ExchangeOnline -Certificate $certificate -AppId $clientID -Organization $tenantDomain",
|
||||
timeout=self.CONNECT_TIMEOUT,
|
||||
)
|
||||
if "https://aka.ms/exov3-module" not in result:
|
||||
logger.error(f"Exchange Online Certificate connection failed: {result}")
|
||||
@@ -290,7 +329,8 @@ class M365PowerShell(PowerShellSession):
|
||||
}
|
||||
"""
|
||||
return self.execute(
|
||||
"Get-CsTeamsClientConfiguration | ConvertTo-Json", json_parse=True
|
||||
"Get-CsTeamsClientConfiguration | ConvertTo-Json -Depth 10",
|
||||
json_parse=True,
|
||||
)
|
||||
|
||||
def get_global_meeting_policy(self) -> dict:
|
||||
@@ -309,7 +349,7 @@ class M365PowerShell(PowerShellSession):
|
||||
}
|
||||
"""
|
||||
return self.execute(
|
||||
"Get-CsTeamsMeetingPolicy -Identity Global | ConvertTo-Json",
|
||||
"Get-CsTeamsMeetingPolicy -Identity Global | ConvertTo-Json -Depth 10",
|
||||
json_parse=True,
|
||||
)
|
||||
|
||||
@@ -329,7 +369,7 @@ class M365PowerShell(PowerShellSession):
|
||||
}
|
||||
"""
|
||||
return self.execute(
|
||||
"Get-CsTeamsMessagingPolicy -Identity Global | ConvertTo-Json",
|
||||
"Get-CsTeamsMessagingPolicy -Identity Global | ConvertTo-Json -Depth 10",
|
||||
json_parse=True,
|
||||
)
|
||||
|
||||
@@ -349,7 +389,8 @@ class M365PowerShell(PowerShellSession):
|
||||
}
|
||||
"""
|
||||
return self.execute(
|
||||
"Get-CsTenantFederationConfiguration | ConvertTo-Json", json_parse=True
|
||||
"Get-CsTenantFederationConfiguration | ConvertTo-Json -Depth 10",
|
||||
json_parse=True,
|
||||
)
|
||||
|
||||
def connect_exchange_online(self) -> dict:
|
||||
@@ -389,7 +430,7 @@ class M365PowerShell(PowerShellSession):
|
||||
}
|
||||
"""
|
||||
return self.execute(
|
||||
"Get-AdminAuditLogConfig | Select-Object UnifiedAuditLogIngestionEnabled | ConvertTo-Json",
|
||||
"Get-AdminAuditLogConfig | Select-Object UnifiedAuditLogIngestionEnabled | ConvertTo-Json -Depth 10",
|
||||
json_parse=True,
|
||||
)
|
||||
|
||||
@@ -409,7 +450,9 @@ class M365PowerShell(PowerShellSession):
|
||||
"Identity": "Default"
|
||||
}
|
||||
"""
|
||||
return self.execute("Get-MalwareFilterPolicy | ConvertTo-Json", json_parse=True)
|
||||
return self.execute(
|
||||
"Get-MalwareFilterPolicy | ConvertTo-Json -Depth 10", json_parse=True
|
||||
)
|
||||
|
||||
def get_malware_filter_rule(self) -> dict:
|
||||
"""
|
||||
@@ -427,7 +470,9 @@ class M365PowerShell(PowerShellSession):
|
||||
"State": "Enabled"
|
||||
}
|
||||
"""
|
||||
return self.execute("Get-MalwareFilterRule | ConvertTo-Json", json_parse=True)
|
||||
return self.execute(
|
||||
"Get-MalwareFilterRule | ConvertTo-Json -Depth 10", json_parse=True
|
||||
)
|
||||
|
||||
def get_outbound_spam_filter_policy(self) -> dict:
|
||||
"""
|
||||
@@ -448,7 +493,8 @@ class M365PowerShell(PowerShellSession):
|
||||
}
|
||||
"""
|
||||
return self.execute(
|
||||
"Get-HostedOutboundSpamFilterPolicy | ConvertTo-Json", json_parse=True
|
||||
"Get-HostedOutboundSpamFilterPolicy | ConvertTo-Json -Depth 10",
|
||||
json_parse=True,
|
||||
)
|
||||
|
||||
def get_outbound_spam_filter_rule(self) -> dict:
|
||||
@@ -467,7 +513,8 @@ class M365PowerShell(PowerShellSession):
|
||||
}
|
||||
"""
|
||||
return self.execute(
|
||||
"Get-HostedOutboundSpamFilterRule | ConvertTo-Json", json_parse=True
|
||||
"Get-HostedOutboundSpamFilterRule | ConvertTo-Json -Depth 10",
|
||||
json_parse=True,
|
||||
)
|
||||
|
||||
def get_antiphishing_policy(self) -> dict:
|
||||
@@ -493,7 +540,9 @@ class M365PowerShell(PowerShellSession):
|
||||
"IsDefault": false
|
||||
}
|
||||
"""
|
||||
return self.execute("Get-AntiPhishPolicy | ConvertTo-Json", json_parse=True)
|
||||
return self.execute(
|
||||
"Get-AntiPhishPolicy | ConvertTo-Json -Depth 10", json_parse=True
|
||||
)
|
||||
|
||||
def get_antiphishing_rules(self) -> dict:
|
||||
"""
|
||||
@@ -511,7 +560,9 @@ class M365PowerShell(PowerShellSession):
|
||||
"State": Enabled,
|
||||
}
|
||||
"""
|
||||
return self.execute("Get-AntiPhishRule | ConvertTo-Json", json_parse=True)
|
||||
return self.execute(
|
||||
"Get-AntiPhishRule | ConvertTo-Json -Depth 10", json_parse=True
|
||||
)
|
||||
|
||||
def get_organization_config(self) -> dict:
|
||||
"""
|
||||
@@ -530,7 +581,9 @@ class M365PowerShell(PowerShellSession):
|
||||
"AuditDisabled": false
|
||||
}
|
||||
"""
|
||||
return self.execute("Get-OrganizationConfig | ConvertTo-Json", json_parse=True)
|
||||
return self.execute(
|
||||
"Get-OrganizationConfig | ConvertTo-Json -Depth 10", json_parse=True
|
||||
)
|
||||
|
||||
def get_mailbox_audit_config(self) -> dict:
|
||||
"""
|
||||
@@ -550,7 +603,8 @@ class M365PowerShell(PowerShellSession):
|
||||
}
|
||||
"""
|
||||
return self.execute(
|
||||
"Get-MailboxAuditBypassAssociation | ConvertTo-Json", json_parse=True
|
||||
"Get-MailboxAuditBypassAssociation | ConvertTo-Json -Depth 10",
|
||||
json_parse=True,
|
||||
)
|
||||
|
||||
def get_mailbox_policy(self) -> dict:
|
||||
@@ -569,7 +623,9 @@ class M365PowerShell(PowerShellSession):
|
||||
"AdditionalStorageProvidersAvailable": True
|
||||
}
|
||||
"""
|
||||
return self.execute("Get-OwaMailboxPolicy | ConvertTo-Json", json_parse=True)
|
||||
return self.execute(
|
||||
"Get-OwaMailboxPolicy | ConvertTo-Json -Depth 10", json_parse=True
|
||||
)
|
||||
|
||||
def get_external_mail_config(self) -> dict:
|
||||
"""
|
||||
@@ -587,7 +643,9 @@ class M365PowerShell(PowerShellSession):
|
||||
"ExternalMailTagEnabled": true
|
||||
}
|
||||
"""
|
||||
return self.execute("Get-ExternalInOutlook | ConvertTo-Json", json_parse=True)
|
||||
return self.execute(
|
||||
"Get-ExternalInOutlook | ConvertTo-Json -Depth 10", json_parse=True
|
||||
)
|
||||
|
||||
def get_transport_rules(self) -> dict:
|
||||
"""
|
||||
@@ -606,7 +664,9 @@ class M365PowerShell(PowerShellSession):
|
||||
"SenderDomainIs": ["example.com"]
|
||||
}
|
||||
"""
|
||||
return self.execute("Get-TransportRule | ConvertTo-Json", json_parse=True)
|
||||
return self.execute(
|
||||
"Get-TransportRule | ConvertTo-Json -Depth 10", json_parse=True
|
||||
)
|
||||
|
||||
def get_connection_filter_policy(self) -> dict:
|
||||
"""
|
||||
@@ -625,7 +685,7 @@ class M365PowerShell(PowerShellSession):
|
||||
}
|
||||
"""
|
||||
return self.execute(
|
||||
"Get-HostedConnectionFilterPolicy -Identity Default | ConvertTo-Json",
|
||||
"Get-HostedConnectionFilterPolicy -Identity Default | ConvertTo-Json -Depth 10",
|
||||
json_parse=True,
|
||||
)
|
||||
|
||||
@@ -645,7 +705,9 @@ class M365PowerShell(PowerShellSession):
|
||||
"Enabled": true
|
||||
}
|
||||
"""
|
||||
return self.execute("Get-DkimSigningConfig | ConvertTo-Json", json_parse=True)
|
||||
return self.execute(
|
||||
"Get-DkimSigningConfig | ConvertTo-Json -Depth 10", json_parse=True
|
||||
)
|
||||
|
||||
def get_inbound_spam_filter_policy(self) -> dict:
|
||||
"""
|
||||
@@ -664,7 +726,8 @@ class M365PowerShell(PowerShellSession):
|
||||
}
|
||||
"""
|
||||
return self.execute(
|
||||
"Get-HostedContentFilterPolicy | ConvertTo-Json", json_parse=True
|
||||
"Get-HostedContentFilterPolicy | ConvertTo-Json -Depth 10",
|
||||
json_parse=True,
|
||||
)
|
||||
|
||||
def get_inbound_spam_filter_rule(self) -> dict:
|
||||
@@ -684,7 +747,8 @@ class M365PowerShell(PowerShellSession):
|
||||
}
|
||||
"""
|
||||
return self.execute(
|
||||
"Get-HostedContentFilterRule | ConvertTo-Json", json_parse=True
|
||||
"Get-HostedContentFilterRule | ConvertTo-Json -Depth 10",
|
||||
json_parse=True,
|
||||
)
|
||||
|
||||
def get_report_submission_policy(self) -> dict:
|
||||
@@ -715,7 +779,8 @@ class M365PowerShell(PowerShellSession):
|
||||
}
|
||||
"""
|
||||
return self.execute(
|
||||
"Get-ReportSubmissionPolicy | ConvertTo-Json", json_parse=True
|
||||
"Get-ReportSubmissionPolicy | ConvertTo-Json -Depth 10",
|
||||
json_parse=True,
|
||||
)
|
||||
|
||||
def get_role_assignment_policies(self) -> dict:
|
||||
@@ -736,7 +801,8 @@ class M365PowerShell(PowerShellSession):
|
||||
}
|
||||
"""
|
||||
return self.execute(
|
||||
"Get-RoleAssignmentPolicy | ConvertTo-Json", json_parse=True
|
||||
"Get-RoleAssignmentPolicy | ConvertTo-Json -Depth 10",
|
||||
json_parse=True,
|
||||
)
|
||||
|
||||
def get_mailbox_audit_properties(self) -> dict:
|
||||
@@ -801,7 +867,7 @@ class M365PowerShell(PowerShellSession):
|
||||
}
|
||||
"""
|
||||
return self.execute(
|
||||
"Get-EXOMailbox -PropertySets Audit -ResultSize Unlimited | ConvertTo-Json",
|
||||
"Get-EXOMailbox -PropertySets Audit -ResultSize Unlimited | ConvertTo-Json -Depth 10",
|
||||
json_parse=True,
|
||||
)
|
||||
|
||||
@@ -820,7 +886,9 @@ class M365PowerShell(PowerShellSession):
|
||||
"SmtpClientAuthenticationDisabled": True,
|
||||
}
|
||||
"""
|
||||
return self.execute("Get-TransportConfig | ConvertTo-Json", json_parse=True)
|
||||
return self.execute(
|
||||
"Get-TransportConfig | ConvertTo-Json -Depth 10", json_parse=True
|
||||
)
|
||||
|
||||
def get_sharing_policy(self) -> dict:
|
||||
"""
|
||||
@@ -838,7 +906,9 @@ class M365PowerShell(PowerShellSession):
|
||||
"Enabled": true
|
||||
}
|
||||
"""
|
||||
return self.execute("Get-SharingPolicy | ConvertTo-Json", json_parse=True)
|
||||
return self.execute(
|
||||
"Get-SharingPolicy | ConvertTo-Json -Depth 10", json_parse=True
|
||||
)
|
||||
|
||||
def get_user_account_status(self) -> dict:
|
||||
"""
|
||||
@@ -850,7 +920,7 @@ class M365PowerShell(PowerShellSession):
|
||||
dict: User account status settings in JSON format.
|
||||
"""
|
||||
return self.execute(
|
||||
"$dict=@{}; Get-User -ResultSize Unlimited | ForEach-Object { $dict[$_.Id] = @{ AccountDisabled = $_.AccountDisabled } }; $dict | ConvertTo-Json",
|
||||
"$dict=@{}; Get-User -ResultSize Unlimited | ForEach-Object { $dict[$_.Id] = @{ AccountDisabled = $_.AccountDisabled } }; $dict | ConvertTo-Json -Depth 10",
|
||||
json_parse=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
|
||||
+19
-15
@@ -1,34 +1,38 @@
|
||||
{
|
||||
"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,3 +1,4 @@
|
||||
import pytest
|
||||
from mock import patch
|
||||
|
||||
from prowler.lib.check.checks_loader import (
|
||||
@@ -190,18 +191,22 @@ class TestCheckLoader:
|
||||
def test_load_checks_to_execute_with_severities_and_services_not_within_severity(
|
||||
self,
|
||||
):
|
||||
"""Test that service not in metadata causes sys.exit(1) when used with severities"""
|
||||
bulk_checks_metatada = {
|
||||
S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata()
|
||||
}
|
||||
service_list = ["ec2"]
|
||||
severities = [S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_SEVERITY]
|
||||
|
||||
assert set() == load_checks_to_execute(
|
||||
bulk_checks_metadata=bulk_checks_metatada,
|
||||
service_list=service_list,
|
||||
severities=severities,
|
||||
provider=self.provider,
|
||||
)
|
||||
# ec2 service doesn't exist in the metadata, so it should exit with error
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
load_checks_to_execute(
|
||||
bulk_checks_metadata=bulk_checks_metatada,
|
||||
service_list=service_list,
|
||||
severities=severities,
|
||||
provider=self.provider,
|
||||
)
|
||||
assert exc_info.value.code == 1
|
||||
|
||||
def test_load_checks_to_execute_with_checks_file(
|
||||
self,
|
||||
@@ -382,3 +387,140 @@ class TestCheckLoader:
|
||||
categories=categories,
|
||||
provider=self.provider,
|
||||
)
|
||||
|
||||
def test_load_checks_to_execute_with_invalid_check(self):
|
||||
"""Test that invalid check names cause sys.exit(1)"""
|
||||
bulk_checks_metatada = {
|
||||
S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata()
|
||||
}
|
||||
check_list = ["invalid_check_name"]
|
||||
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
load_checks_to_execute(
|
||||
bulk_checks_metadata=bulk_checks_metatada,
|
||||
check_list=check_list,
|
||||
provider=self.provider,
|
||||
)
|
||||
assert exc_info.value.code == 1
|
||||
|
||||
def test_load_checks_to_execute_with_multiple_invalid_checks(self):
|
||||
"""Test that multiple invalid check names cause sys.exit(1)"""
|
||||
bulk_checks_metatada = {
|
||||
S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata()
|
||||
}
|
||||
check_list = ["invalid_check_1", "invalid_check_2", "invalid_check_3"]
|
||||
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
load_checks_to_execute(
|
||||
bulk_checks_metadata=bulk_checks_metatada,
|
||||
check_list=check_list,
|
||||
provider=self.provider,
|
||||
)
|
||||
assert exc_info.value.code == 1
|
||||
|
||||
def test_load_checks_to_execute_with_mixed_valid_invalid_checks(self):
|
||||
"""Test that mix of valid and invalid checks cause sys.exit(1)"""
|
||||
bulk_checks_metatada = {
|
||||
S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata()
|
||||
}
|
||||
check_list = [S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME, "invalid_check"]
|
||||
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
load_checks_to_execute(
|
||||
bulk_checks_metadata=bulk_checks_metatada,
|
||||
check_list=check_list,
|
||||
provider=self.provider,
|
||||
)
|
||||
assert exc_info.value.code == 1
|
||||
|
||||
def test_load_checks_to_execute_with_invalid_service(self):
|
||||
"""Test that invalid service names cause sys.exit(1)"""
|
||||
bulk_checks_metatada = {
|
||||
S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata()
|
||||
}
|
||||
service_list = ["invalid_service"]
|
||||
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
load_checks_to_execute(
|
||||
bulk_checks_metadata=bulk_checks_metatada,
|
||||
service_list=service_list,
|
||||
provider=self.provider,
|
||||
)
|
||||
assert exc_info.value.code == 1
|
||||
|
||||
def test_load_checks_to_execute_with_invalid_service_and_severity(self):
|
||||
"""Test that invalid service names with severity cause sys.exit(1)"""
|
||||
bulk_checks_metatada = {
|
||||
S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata()
|
||||
}
|
||||
service_list = ["invalid_service"]
|
||||
severities = [S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_SEVERITY]
|
||||
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
load_checks_to_execute(
|
||||
bulk_checks_metadata=bulk_checks_metatada,
|
||||
service_list=service_list,
|
||||
severities=severities,
|
||||
provider=self.provider,
|
||||
)
|
||||
assert exc_info.value.code == 1
|
||||
|
||||
def test_load_checks_to_execute_with_multiple_invalid_services(self):
|
||||
"""Test that multiple invalid service names cause sys.exit(1)"""
|
||||
bulk_checks_metatada = {
|
||||
S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata()
|
||||
}
|
||||
service_list = ["invalid_service_1", "invalid_service_2"]
|
||||
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
load_checks_to_execute(
|
||||
bulk_checks_metadata=bulk_checks_metatada,
|
||||
service_list=service_list,
|
||||
provider=self.provider,
|
||||
)
|
||||
assert exc_info.value.code == 1
|
||||
|
||||
def test_load_checks_to_execute_with_invalid_category(self):
|
||||
"""Test that invalid category names cause sys.exit(1)"""
|
||||
bulk_checks_metatada = {
|
||||
S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata()
|
||||
}
|
||||
categories = {"invalid_category"}
|
||||
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
load_checks_to_execute(
|
||||
bulk_checks_metadata=bulk_checks_metatada,
|
||||
categories=categories,
|
||||
provider=self.provider,
|
||||
)
|
||||
assert exc_info.value.code == 1
|
||||
|
||||
def test_load_checks_to_execute_with_multiple_invalid_categories(self):
|
||||
"""Test that multiple invalid category names cause sys.exit(1)"""
|
||||
bulk_checks_metatada = {
|
||||
S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata()
|
||||
}
|
||||
categories = {"invalid_category_1", "invalid_category_2"}
|
||||
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
load_checks_to_execute(
|
||||
bulk_checks_metadata=bulk_checks_metatada,
|
||||
categories=categories,
|
||||
provider=self.provider,
|
||||
)
|
||||
assert exc_info.value.code == 1
|
||||
|
||||
def test_load_checks_to_execute_with_mixed_valid_invalid_categories(self):
|
||||
"""Test that mix of valid and invalid categories cause sys.exit(1)"""
|
||||
bulk_checks_metatada = {
|
||||
S3_BUCKET_LEVEL_PUBLIC_ACCESS_BLOCK_NAME: self.get_custom_check_s3_metadata()
|
||||
}
|
||||
categories = {"internet-exposed", "invalid_category"}
|
||||
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
load_checks_to_execute(
|
||||
bulk_checks_metadata=bulk_checks_metatada,
|
||||
categories=categories,
|
||||
provider=self.provider,
|
||||
)
|
||||
assert exc_info.value.code == 1
|
||||
|
||||
@@ -135,5 +135,5 @@ class TestAWSENS:
|
||||
|
||||
mock_file.seek(0)
|
||||
content = mock_file.read()
|
||||
expected_csv = f"PROVIDER;DESCRIPTION;ACCOUNTID;REGION;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_IDGRUPOCONTROL;REQUIREMENTS_ATTRIBUTES_MARCO;REQUIREMENTS_ATTRIBUTES_CATEGORIA;REQUIREMENTS_ATTRIBUTES_DESCRIPCIONCONTROL;REQUIREMENTS_ATTRIBUTES_NIVEL;REQUIREMENTS_ATTRIBUTES_TIPO;REQUIREMENTS_ATTRIBUTES_DIMENSIONES;REQUIREMENTS_ATTRIBUTES_MODOEJECUCION;REQUIREMENTS_ATTRIBUTES_DEPENDENCIAS;STATUS;STATUSEXTENDED;RESOURCEID;CHECKID;MUTED;RESOURCENAME;FRAMEWORK;NAME\r\naws;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.;123456789012;eu-west-1;{datetime.now()};op.exp.8.aws.ct.3;Registro de actividad;op.exp.8;operacional;explotación;Habilitar la validación de archivos en todos los trails, evitando así que estos se vean modificados o eliminados.;alto;requisito;trazabilidad;automático;;PASS;;;service_test_check_id;False;;ENS;ENS RD 311/2022\r\naws;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.;;;{datetime.now()};op.exp.8.aws.ct.4;Registro de actividad;op.exp.8;operacional;explotación;Habilitar la validación de archivos en todos los trails, evitando así que estos se vean modificados o eliminados.;alto;requisito;trazabilidad;automático;;MANUAL;Manual check;manual_check;manual;False;Manual check;ENS;ENS RD 311/2022\r\n"
|
||||
expected_csv = f"PROVIDER;DESCRIPTION;ACCOUNTID;REGION;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_IDGRUPOCONTROL;REQUIREMENTS_ATTRIBUTES_MARCO;REQUIREMENTS_ATTRIBUTES_CATEGORIA;REQUIREMENTS_ATTRIBUTES_DESCRIPCIONCONTROL;REQUIREMENTS_ATTRIBUTES_NIVEL;REQUIREMENTS_ATTRIBUTES_TIPO;REQUIREMENTS_ATTRIBUTES_DIMENSIONES;REQUIREMENTS_ATTRIBUTES_MODOEJECUCION;REQUIREMENTS_ATTRIBUTES_DEPENDENCIAS;STATUS;STATUSEXTENDED;RESOURCEID;CHECKID;MUTED;RESOURCENAME;FRAMEWORK;NAME\r\naws;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.;123456789012;eu-west-1;{datetime.now()};op.exp.8.aws.ct.3;Registro de actividad;op.exp.8;operacional;explotación;Habilitar la validación de archivos en todos los trails, evitando así que estos se vean modificados o eliminados.;alto;requisito;trazabilidad;automático;;PASS;;;service_test_check_id;False;;ENS;ENS RD 311/2022 - Categoría Alta\r\naws;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.;;;{datetime.now()};op.exp.8.aws.ct.4;Registro de actividad;op.exp.8;operacional;explotación;Habilitar la validación de archivos en todos los trails, evitando así que estos se vean modificados o eliminados.;alto;requisito;trazabilidad;automático;;MANUAL;Manual check;manual_check;manual;False;Manual check;ENS;ENS RD 311/2022 - Categoría Alta\r\n"
|
||||
assert content == expected_csv
|
||||
|
||||
@@ -144,6 +144,6 @@ class TestAzureENS:
|
||||
|
||||
mock_file.seek(0)
|
||||
content = mock_file.read()
|
||||
expected_csv = f"PROVIDER;DESCRIPTION;SUBSCRIPTIONID;LOCATION;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_IDGRUPOCONTROL;REQUIREMENTS_ATTRIBUTES_MARCO;REQUIREMENTS_ATTRIBUTES_CATEGORIA;REQUIREMENTS_ATTRIBUTES_DESCRIPCIONCONTROL;REQUIREMENTS_ATTRIBUTES_NIVEL;REQUIREMENTS_ATTRIBUTES_TIPO;REQUIREMENTS_ATTRIBUTES_DIMENSIONES;REQUIREMENTS_ATTRIBUTES_MODOEJECUCION;REQUIREMENTS_ATTRIBUTES_DEPENDENCIAS;STATUS;STATUSEXTENDED;RESOURCEID;CHECKID;MUTED;RESOURCENAME;FRAMEWORK;NAME\r\nazure;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.;123456789012;global;{datetime.now()};op.exp.8.azure.ct.3;Registro de actividad;op.exp.8;operacional;explotación;Habilitar la validación de archivos en todos los trails, evitando así que estos se vean modificados o eliminados.;alto;requisito;trazabilidad;automático;;PASS;;;service_test_check_id;False;;ENS;ENS RD 311/2022\r\nazure;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.;;;{datetime.now()};op.exp.8.azure.ct.4;Registro de actividad;op.exp.8;operacional;explotación;Habilitar la validación de archivos en todos los trails, evitando así que estos se vean modificados o eliminados.;alto;requisito;trazabilidad;automático;;MANUAL;Manual check;manual_check;manual;False;Manual check;ENS;ENS RD 311/2022\r\n"
|
||||
expected_csv = f"PROVIDER;DESCRIPTION;SUBSCRIPTIONID;LOCATION;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_IDGRUPOCONTROL;REQUIREMENTS_ATTRIBUTES_MARCO;REQUIREMENTS_ATTRIBUTES_CATEGORIA;REQUIREMENTS_ATTRIBUTES_DESCRIPCIONCONTROL;REQUIREMENTS_ATTRIBUTES_NIVEL;REQUIREMENTS_ATTRIBUTES_TIPO;REQUIREMENTS_ATTRIBUTES_DIMENSIONES;REQUIREMENTS_ATTRIBUTES_MODOEJECUCION;REQUIREMENTS_ATTRIBUTES_DEPENDENCIAS;STATUS;STATUSEXTENDED;RESOURCEID;CHECKID;MUTED;RESOURCENAME;FRAMEWORK;NAME\r\nazure;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.;123456789012;global;{datetime.now()};op.exp.8.azure.ct.3;Registro de actividad;op.exp.8;operacional;explotación;Habilitar la validación de archivos en todos los trails, evitando así que estos se vean modificados o eliminados.;alto;requisito;trazabilidad;automático;;PASS;;;service_test_check_id;False;;ENS;ENS RD 311/2022 - Categoría Alta\r\nazure;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.;;;{datetime.now()};op.exp.8.azure.ct.4;Registro de actividad;op.exp.8;operacional;explotación;Habilitar la validación de archivos en todos los trails, evitando así que estos se vean modificados o eliminados.;alto;requisito;trazabilidad;automático;;MANUAL;Manual check;manual_check;manual;False;Manual check;ENS;ENS RD 311/2022 - Categoría Alta\r\n"
|
||||
|
||||
assert content == expected_csv
|
||||
|
||||
@@ -144,6 +144,6 @@ class TestGCPENS:
|
||||
|
||||
mock_file.seek(0)
|
||||
content = mock_file.read()
|
||||
expected_csv = f"PROVIDER;DESCRIPTION;PROJECTID;LOCATION;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_IDGRUPOCONTROL;REQUIREMENTS_ATTRIBUTES_MARCO;REQUIREMENTS_ATTRIBUTES_CATEGORIA;REQUIREMENTS_ATTRIBUTES_DESCRIPCIONCONTROL;REQUIREMENTS_ATTRIBUTES_NIVEL;REQUIREMENTS_ATTRIBUTES_TIPO;REQUIREMENTS_ATTRIBUTES_DIMENSIONES;REQUIREMENTS_ATTRIBUTES_MODOEJECUCION;REQUIREMENTS_ATTRIBUTES_DEPENDENCIAS;STATUS;STATUSEXTENDED;RESOURCEID;CHECKID;MUTED;RESOURCENAME;FRAMEWORK;NAME\r\ngcp;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.;123456789012;global;{datetime.now()};op.exp.8.gcp.ct.3;Registro de actividad;op.exp.8;operacional;explotación;Habilitar la validación de archivos en todos los trails, evitando así que estos se vean modificados o eliminados.;alto;requisito;trazabilidad;automático;;PASS;;;service_test_check_id;False;;ENS;ENS RD 311/2022\r\ngcp;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.;;;{datetime.now()};op.exp.8.gcp.ct.4;Registro de actividad;op.exp.8;operacional;explotación;Habilitar la validación de archivos en todos los trails, evitando así que estos se vean modificados o eliminados.;alto;requisito;trazabilidad;automático;;MANUAL;Manual check;manual_check;manual;False;Manual check;ENS;ENS RD 311/2022\r\n"
|
||||
expected_csv = f"PROVIDER;DESCRIPTION;PROJECTID;LOCATION;ASSESSMENTDATE;REQUIREMENTS_ID;REQUIREMENTS_DESCRIPTION;REQUIREMENTS_ATTRIBUTES_IDGRUPOCONTROL;REQUIREMENTS_ATTRIBUTES_MARCO;REQUIREMENTS_ATTRIBUTES_CATEGORIA;REQUIREMENTS_ATTRIBUTES_DESCRIPCIONCONTROL;REQUIREMENTS_ATTRIBUTES_NIVEL;REQUIREMENTS_ATTRIBUTES_TIPO;REQUIREMENTS_ATTRIBUTES_DIMENSIONES;REQUIREMENTS_ATTRIBUTES_MODOEJECUCION;REQUIREMENTS_ATTRIBUTES_DEPENDENCIAS;STATUS;STATUSEXTENDED;RESOURCEID;CHECKID;MUTED;RESOURCENAME;FRAMEWORK;NAME\r\ngcp;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.;123456789012;global;{datetime.now()};op.exp.8.gcp.ct.3;Registro de actividad;op.exp.8;operacional;explotación;Habilitar la validación de archivos en todos los trails, evitando así que estos se vean modificados o eliminados.;alto;requisito;trazabilidad;automático;;PASS;;;service_test_check_id;False;;ENS;ENS RD 311/2022 - Categoría Alta\r\ngcp;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.;;;{datetime.now()};op.exp.8.gcp.ct.4;Registro de actividad;op.exp.8;operacional;explotación;Habilitar la validación de archivos en todos los trails, evitando así que estos se vean modificados o eliminados.;alto;requisito;trazabilidad;automático;;MANUAL;Manual check;manual_check;manual;False;Manual check;ENS;ENS RD 311/2022 - Categoría Alta\r\n"
|
||||
|
||||
assert content == expected_csv
|
||||
|
||||
@@ -490,7 +490,7 @@ MITRE_ATTACK_GCP = Compliance(
|
||||
|
||||
ENS_RD2022_AWS = Compliance(
|
||||
Framework="ENS",
|
||||
Name="ENS RD 311/2022",
|
||||
Name="ENS RD 311/2022 - Categoría Alta",
|
||||
Provider="AWS",
|
||||
Version="RD2022",
|
||||
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.",
|
||||
@@ -538,7 +538,7 @@ ENS_RD2022_AWS = Compliance(
|
||||
|
||||
ENS_RD2022_AZURE = Compliance(
|
||||
Framework="ENS",
|
||||
Name="ENS RD 311/2022",
|
||||
Name="ENS RD 311/2022 - Categoría Alta",
|
||||
Provider="Azure",
|
||||
Version="RD2022",
|
||||
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.",
|
||||
@@ -585,7 +585,7 @@ ENS_RD2022_AZURE = Compliance(
|
||||
)
|
||||
ENS_RD2022_GCP = Compliance(
|
||||
Framework="ENS",
|
||||
Name="ENS RD 311/2022",
|
||||
Name="ENS RD 311/2022 - Categoría Alta",
|
||||
Provider="GCP",
|
||||
Version="RD2022",
|
||||
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.",
|
||||
|
||||
@@ -216,6 +216,14 @@ class TestPowerShellSession:
|
||||
result = session.json_parse_output('prefix [{"key": "value"}] suffix')
|
||||
assert result == [{"key": "value"}]
|
||||
|
||||
result = session.json_parse_output(
|
||||
'INFO {context data} {"key": "value", "list": [1, 2]} extra'
|
||||
)
|
||||
assert result == {"key": "value", "list": [1, 2]}
|
||||
|
||||
result = session.json_parse_output('{"key": "value"} trailing {log}')
|
||||
assert result == {"key": "value"}
|
||||
|
||||
# Test non-JSON text returns empty dict
|
||||
result = session.json_parse_output("just some text")
|
||||
assert result == {}
|
||||
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
from unittest import mock
|
||||
|
||||
from prowler.providers.github.services.organization.organization_service import Org
|
||||
from tests.providers.github.github_fixtures import set_mocked_github_provider
|
||||
|
||||
|
||||
class Test_organization_repository_creation_limited:
|
||||
def test_no_organizations(self):
|
||||
organization_client = mock.MagicMock
|
||||
organization_client.organizations = {}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_github_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client",
|
||||
new=organization_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import (
|
||||
organization_repository_creation_limited,
|
||||
)
|
||||
|
||||
check = organization_repository_creation_limited()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_repository_creation_disabled_globally(self):
|
||||
organization_client = mock.MagicMock
|
||||
org_name = "test-organization"
|
||||
organization_client.organizations = {
|
||||
1: Org(
|
||||
id=1,
|
||||
name=org_name,
|
||||
mfa_required=None,
|
||||
members_can_create_repositories=False,
|
||||
),
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_github_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client",
|
||||
new=organization_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import (
|
||||
organization_repository_creation_limited,
|
||||
)
|
||||
|
||||
check = organization_repository_creation_limited()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].resource_name == org_name
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Organization {org_name} has disabled repository creation for members."
|
||||
)
|
||||
|
||||
def test_repository_creation_allowed_for_members(self):
|
||||
organization_client = mock.MagicMock
|
||||
org_name = "test-organization"
|
||||
organization_client.organizations = {
|
||||
1: Org(
|
||||
id=1,
|
||||
name=org_name,
|
||||
mfa_required=None,
|
||||
members_can_create_repositories=True,
|
||||
members_can_create_public_repositories=True,
|
||||
),
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_github_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client",
|
||||
new=organization_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import (
|
||||
organization_repository_creation_limited,
|
||||
)
|
||||
|
||||
check = organization_repository_creation_limited()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert "public repositories" in result[0].status_extended
|
||||
assert "repositories of any type" in result[0].status_extended
|
||||
|
||||
def test_repository_creation_disabled_per_visibility(self):
|
||||
organization_client = mock.MagicMock
|
||||
org_name = "test-organization"
|
||||
organization_client.organizations = {
|
||||
1: Org(
|
||||
id=1,
|
||||
name=org_name,
|
||||
mfa_required=None,
|
||||
members_can_create_public_repositories=False,
|
||||
members_can_create_private_repositories=False,
|
||||
members_can_create_internal_repositories=False,
|
||||
),
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_github_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client",
|
||||
new=organization_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import (
|
||||
organization_repository_creation_limited,
|
||||
)
|
||||
|
||||
check = organization_repository_creation_limited()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Organization {org_name} has disabled repository creation for members."
|
||||
)
|
||||
|
||||
def test_repository_creation_disabled_via_members_allowed_type(self):
|
||||
organization_client = mock.MagicMock
|
||||
org_name = "test-organization"
|
||||
organization_client.organizations = {
|
||||
1: Org(
|
||||
id=1,
|
||||
name=org_name,
|
||||
mfa_required=None,
|
||||
members_allowed_repository_creation_type="none",
|
||||
),
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_github_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited.organization_client",
|
||||
new=organization_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.github.services.organization.organization_repository_creation_limited.organization_repository_creation_limited import (
|
||||
organization_repository_creation_limited,
|
||||
)
|
||||
|
||||
check = organization_repository_creation_limited()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Organization {org_name} has disabled repository creation for members."
|
||||
)
|
||||
@@ -17,6 +17,10 @@ SAMPLE_TRIVY_OUTPUT = {
|
||||
"Severity": "LOW",
|
||||
"PrimaryURL": "https://avd.aquasec.com/misconfig/aws/s3/avd-aws-0001",
|
||||
"RuleID": "AVD-AWS-0001",
|
||||
"CauseMetadata": {
|
||||
"StartLine": 10,
|
||||
"EndLine": 15,
|
||||
},
|
||||
},
|
||||
{
|
||||
"ID": "AVD-AWS-0002",
|
||||
@@ -27,6 +31,10 @@ SAMPLE_TRIVY_OUTPUT = {
|
||||
"Severity": "LOW",
|
||||
"PrimaryURL": "https://avd.aquasec.com/misconfig/aws/s3/avd-aws-0002",
|
||||
"RuleID": "AVD-AWS-0002",
|
||||
"CauseMetadata": {
|
||||
"StartLine": 20,
|
||||
"EndLine": 25,
|
||||
},
|
||||
},
|
||||
],
|
||||
"Vulnerabilities": [],
|
||||
@@ -46,6 +54,10 @@ SAMPLE_TRIVY_OUTPUT = {
|
||||
"Severity": "LOW",
|
||||
"PrimaryURL": "https://avd.aquasec.com/misconfig/aws/s3/avd-aws-0003",
|
||||
"RuleID": "AVD-AWS-0003",
|
||||
"CauseMetadata": {
|
||||
"StartLine": 30,
|
||||
"EndLine": 35,
|
||||
},
|
||||
}
|
||||
],
|
||||
"Vulnerabilities": [],
|
||||
@@ -67,6 +79,10 @@ SAMPLE_FAILED_CHECK = {
|
||||
"Severity": "low",
|
||||
"PrimaryURL": "https://avd.aquasec.com/misconfig/aws/s3/avd-aws-0001",
|
||||
"RuleID": "AVD-AWS-0001",
|
||||
"CauseMetadata": {
|
||||
"StartLine": 10,
|
||||
"EndLine": 15,
|
||||
},
|
||||
}
|
||||
|
||||
SAMPLE_PASSED_CHECK = {
|
||||
@@ -78,6 +94,10 @@ SAMPLE_PASSED_CHECK = {
|
||||
"Severity": "low",
|
||||
"PrimaryURL": "https://avd.aquasec.com/misconfig/aws/s3/avd-aws-0003",
|
||||
"RuleID": "AVD-AWS-0003",
|
||||
"CauseMetadata": {
|
||||
"StartLine": 30,
|
||||
"EndLine": 35,
|
||||
},
|
||||
}
|
||||
|
||||
# Additional sample checks
|
||||
|
||||
@@ -936,7 +936,8 @@ class Testm365PowerShell:
|
||||
assert result is True
|
||||
|
||||
session.execute.assert_called_once_with(
|
||||
"Connect-ExchangeOnline -Certificate $certificate -AppId $clientID -Organization $tenantDomain"
|
||||
"Connect-ExchangeOnline -Certificate $certificate -AppId $clientID -Organization $tenantDomain",
|
||||
timeout=M365PowerShell.CONNECT_TIMEOUT,
|
||||
)
|
||||
|
||||
session.close()
|
||||
@@ -960,7 +961,8 @@ class Testm365PowerShell:
|
||||
assert result is False
|
||||
|
||||
session.execute.assert_called_once_with(
|
||||
"Connect-ExchangeOnline -Certificate $certificate -AppId $clientID -Organization $tenantDomain"
|
||||
"Connect-ExchangeOnline -Certificate $certificate -AppId $clientID -Organization $tenantDomain",
|
||||
timeout=M365PowerShell.CONNECT_TIMEOUT,
|
||||
)
|
||||
|
||||
session.close()
|
||||
@@ -979,7 +981,7 @@ class Testm365PowerShell:
|
||||
session = M365PowerShell(credentials, identity)
|
||||
|
||||
# Mock successful Teams connection - the method returns bool
|
||||
def mock_execute_side_effect(command):
|
||||
def mock_execute_side_effect(command, *_, **__):
|
||||
if "Connect-MicrosoftTeams" in command:
|
||||
# Return result that contains the identity_id for success
|
||||
return "Connected successfully test_identity_id"
|
||||
@@ -991,7 +993,8 @@ class Testm365PowerShell:
|
||||
assert result is True
|
||||
|
||||
session.execute.assert_called_once_with(
|
||||
"Connect-MicrosoftTeams -Certificate $certificate -ApplicationId $clientID -TenantId $tenantID"
|
||||
"Connect-MicrosoftTeams -Certificate $certificate -ApplicationId $clientID -TenantId $tenantID",
|
||||
timeout=M365PowerShell.CONNECT_TIMEOUT,
|
||||
)
|
||||
|
||||
session.close()
|
||||
@@ -1007,7 +1010,7 @@ class Testm365PowerShell:
|
||||
session = M365PowerShell(credentials, identity)
|
||||
|
||||
# Mock failed Teams connection
|
||||
def mock_execute_side_effect(command, json_parse=False):
|
||||
def mock_execute_side_effect(command, **kwargs):
|
||||
if "Connect-MicrosoftTeams" in command:
|
||||
raise Exception("Connection failed: Authentication error")
|
||||
return ""
|
||||
@@ -1090,7 +1093,7 @@ class Testm365PowerShell:
|
||||
# Mock certificate variable check and teams connection
|
||||
execute_calls = []
|
||||
|
||||
def mock_execute_side_effect(command, json_parse=False):
|
||||
def mock_execute_side_effect(command):
|
||||
execute_calls.append(command)
|
||||
if "Write-Output $certificate" in command:
|
||||
return "certificate_content" # Non-empty means certificate exists
|
||||
@@ -1123,7 +1126,7 @@ class Testm365PowerShell:
|
||||
# Mock certificate variable check and exchange connection
|
||||
execute_calls = []
|
||||
|
||||
def mock_execute_side_effect(command, json_parse=False):
|
||||
def mock_execute_side_effect(command):
|
||||
execute_calls.append(command)
|
||||
if "Write-Output $certificate" in command:
|
||||
return "certificate_content" # Non-empty means certificate exists
|
||||
|
||||
Regular → Executable
+134
-1
@@ -1 +1,134 @@
|
||||
npm run healthcheck
|
||||
#!/bin/bash
|
||||
|
||||
# Prowler UI - Pre-Commit Hook
|
||||
# Optionally validates ONLY staged files against AGENTS.md standards using Claude Code
|
||||
# Controlled by CODE_REVIEW_ENABLED in .env
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "🚀 Prowler UI - Pre-Commit Hook"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# Load .env file (look in git root directory)
|
||||
GIT_ROOT=$(git rev-parse --show-toplevel)
|
||||
if [ -f "$GIT_ROOT/ui/.env" ]; then
|
||||
CODE_REVIEW_ENABLED=$(grep "^CODE_REVIEW_ENABLED" "$GIT_ROOT/ui/.env" | cut -d'=' -f2 | tr -d ' ')
|
||||
elif [ -f "$GIT_ROOT/.env" ]; then
|
||||
CODE_REVIEW_ENABLED=$(grep "^CODE_REVIEW_ENABLED" "$GIT_ROOT/.env" | cut -d'=' -f2 | tr -d ' ')
|
||||
elif [ -f ".env" ]; then
|
||||
CODE_REVIEW_ENABLED=$(grep "^CODE_REVIEW_ENABLED" .env | cut -d'=' -f2 | tr -d ' ')
|
||||
else
|
||||
CODE_REVIEW_ENABLED="false"
|
||||
fi
|
||||
|
||||
# Normalize the value to lowercase
|
||||
CODE_REVIEW_ENABLED=$(echo "$CODE_REVIEW_ENABLED" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
echo -e "${BLUE}ℹ️ Code Review Status: ${CODE_REVIEW_ENABLED}${NC}"
|
||||
echo ""
|
||||
|
||||
# Get staged files (what will be committed)
|
||||
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(tsx?|jsx?)$' || true)
|
||||
|
||||
if [ "$CODE_REVIEW_ENABLED" = "true" ]; then
|
||||
if [ -z "$STAGED_FILES" ]; then
|
||||
echo -e "${YELLOW}⚠️ No TypeScript/JavaScript files staged to validate${NC}"
|
||||
echo ""
|
||||
else
|
||||
echo -e "${YELLOW}🔍 Running Claude Code standards validation...${NC}"
|
||||
echo ""
|
||||
echo -e "${BLUE}📋 Files to validate:${NC}"
|
||||
echo "$STAGED_FILES" | sed 's/^/ - /'
|
||||
echo ""
|
||||
|
||||
echo -e "${BLUE}📤 Sending to Claude Code for validation...${NC}"
|
||||
echo ""
|
||||
|
||||
# Build prompt with git diff of changes AND full context
|
||||
VALIDATION_PROMPT=$(
|
||||
cat <<'PROMPT_EOF'
|
||||
You are a code reviewer for the Prowler UI project. Analyze the code changes (git diff with full context) below and validate they comply with AGENTS.md standards.
|
||||
|
||||
**CRITICAL: You MUST check BOTH the changed lines AND the surrounding context for violations.**
|
||||
|
||||
**RULES TO CHECK:**
|
||||
1. React Imports: NO `import * as React` or `import React, {` → Use `import { useState }`
|
||||
2. TypeScript: NO union types like `type X = "a" | "b"` → Use const-based: `const X = {...} as const`
|
||||
3. Tailwind: NO `var()` or hex colors in className → Use `className="bg-card-bg"`
|
||||
4. cn(): ONLY for conditionals → NOT for static classes
|
||||
5. React 19: NO `useMemo`/`useCallback` without reason
|
||||
6. Zod v4: Use `.min(1)` not `.nonempty()`, `z.email()` not `z.string().email()`. All inputs must be validated with Zod.
|
||||
7. File Org: 1 feature = local, 2+ features = shared
|
||||
8. Directives: Server Actions need "use server", clients need "use client"
|
||||
9. Implement DRY, KISS principles. (example: reusable components, avoid repetition)
|
||||
10. Layout must work for all the responsive breakpoints (mobile, tablet, desktop)
|
||||
11. ANY types cannot be used - CRITICAL: Check for `: any` in all visible lines
|
||||
12. Use the components inside components/shadcn if possible
|
||||
13. Check Accessibility best practices (like alt tags in images, semantic HTML, Aria labels, etc.)
|
||||
|
||||
=== GIT DIFF WITH CONTEXT ===
|
||||
PROMPT_EOF
|
||||
)
|
||||
|
||||
# Add git diff to prompt with more context (U5 = 5 lines before/after)
|
||||
VALIDATION_PROMPT="$VALIDATION_PROMPT
|
||||
$(git diff --cached -U5)"
|
||||
|
||||
VALIDATION_PROMPT="$VALIDATION_PROMPT
|
||||
|
||||
=== END DIFF ===
|
||||
|
||||
**IMPORTANT: Your response MUST start with exactly one of these lines:**
|
||||
STATUS: PASSED
|
||||
STATUS: FAILED
|
||||
|
||||
**If FAILED:** List each violation with File, Line Number, Rule Number, and Issue.
|
||||
**If PASSED:** Confirm all visible code (including context) complies with AGENTS.md standards.
|
||||
|
||||
**Start your response now with STATUS:**"
|
||||
|
||||
# Send to Claude Code
|
||||
if VALIDATION_OUTPUT=$(echo "$VALIDATION_PROMPT" | claude 2>&1); then
|
||||
echo "$VALIDATION_OUTPUT"
|
||||
echo ""
|
||||
|
||||
# Check result - STRICT MODE: fail if status unclear
|
||||
if echo "$VALIDATION_OUTPUT" | grep -q "^STATUS: PASSED"; then
|
||||
echo ""
|
||||
echo -e "${GREEN}✅ VALIDATION PASSED${NC}"
|
||||
echo ""
|
||||
elif echo "$VALIDATION_OUTPUT" | grep -q "^STATUS: FAILED"; then
|
||||
echo ""
|
||||
echo -e "${RED}❌ VALIDATION FAILED${NC}"
|
||||
echo -e "${RED}Fix violations before committing${NC}"
|
||||
echo ""
|
||||
exit 1
|
||||
else
|
||||
echo ""
|
||||
echo -e "${RED}❌ VALIDATION ERROR${NC}"
|
||||
echo -e "${RED}Could not determine validation status from Claude Code response${NC}"
|
||||
echo -e "${YELLOW}Response must start with 'STATUS: PASSED' or 'STATUS: FAILED'${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}To bypass validation temporarily, set CODE_REVIEW_ENABLED=false in .env${NC}"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ Claude Code not available${NC}"
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}⏭️ Code review disabled (CODE_REVIEW_ENABLED=false)${NC}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
@@ -9,6 +9,7 @@ All notable changes to the **Prowler UI** are documented in this file.
|
||||
- RSS feeds support [(#9109)](https://github.com/prowler-cloud/prowler/pull/9109)
|
||||
- Customer Support menu item [(#9143)](https://github.com/prowler-cloud/prowler/pull/9143)
|
||||
- IaC (Infrastructure as Code) provider support for scanning remote repositories [(#8751)](https://github.com/prowler-cloud/prowler/pull/8751)
|
||||
- External resource link to IaC findings for direct navigation to source code in Git repositories [(#9151)](https://github.com/prowler-cloud/prowler/pull/9151)
|
||||
|
||||
### 🔄 Changed
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ WORKDIR /app
|
||||
|
||||
# Install dependencies based on the preferred package manager
|
||||
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
|
||||
COPY scripts ./scripts
|
||||
RUN \
|
||||
if [ -f package-lock.json ]; then npm install; \
|
||||
else echo "Lockfile not found." && exit 1; \
|
||||
|
||||
@@ -87,6 +87,12 @@ You can use one of them `npm`, `yarn`, `pnpm`, `bun`, Example using `npm`:
|
||||
npm install
|
||||
```
|
||||
|
||||
**Note:** The `npm install` command will automatically configure Git hooks for code quality checks. If you experience issues, you can manually configure them:
|
||||
|
||||
```bash
|
||||
git config core.hooksPath "ui/.husky"
|
||||
```
|
||||
|
||||
#### Run the development server
|
||||
|
||||
```bash
|
||||
@@ -112,3 +118,48 @@ After modifying the `.npmrc` file, you need to run `pnpm install` again to ensur
|
||||
- [TypeScript](https://www.typescriptlang.org/)
|
||||
- [Framer Motion](https://www.framer.com/motion/)
|
||||
- [next-themes](https://github.com/pacocoursey/next-themes)
|
||||
|
||||
## Git Hooks & Code Review
|
||||
|
||||
This project uses Git hooks to maintain code quality. When you commit changes to TypeScript/JavaScript files, the pre-commit hook can optionally validate them against our coding standards using Claude Code.
|
||||
|
||||
### Enabling Code Review
|
||||
|
||||
To enable automatic code review on commits, add this to your `.env` file in the project root:
|
||||
|
||||
```bash
|
||||
CODE_REVIEW_ENABLED=true
|
||||
```
|
||||
|
||||
When enabled, the hook will:
|
||||
- ✅ Validate your staged changes against `AGENTS.md` standards
|
||||
- ✅ Check for common issues (any types, incorrect imports, styling violations, etc.)
|
||||
- ✅ Block commits that don't comply with the standards
|
||||
- ✅ Provide helpful feedback on how to fix issues
|
||||
|
||||
### Disabling Code Review
|
||||
|
||||
To disable code review (faster commits, useful for quick iterations):
|
||||
|
||||
```bash
|
||||
CODE_REVIEW_ENABLED=false
|
||||
```
|
||||
|
||||
Or remove the variable from your `.env` file.
|
||||
|
||||
### Requirements
|
||||
|
||||
- [Claude Code CLI](https://github.com/anthropics/claude-code) installed and authenticated
|
||||
- `.env` file in the project root with `CODE_REVIEW_ENABLED` set
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
If hooks aren't running after commits:
|
||||
|
||||
```bash
|
||||
# Verify hooks are configured
|
||||
git config --get core.hooksPath # Should output: ui/.husky
|
||||
|
||||
# Reconfigure if needed
|
||||
git config core.hooksPath "ui/.husky"
|
||||
```
|
||||
|
||||
@@ -30,7 +30,7 @@ const PROVIDER_ICON: Record<ProviderType, ReactNode> = {
|
||||
m365: <M365ProviderBadge width={18} height={18} />,
|
||||
github: <GitHubProviderBadge width={18} height={18} />,
|
||||
iac: <IacProviderBadge width={18} height={18} />,
|
||||
oci: <OracleCloudProviderBadge width={18} height={18} />,
|
||||
oraclecloud: <OracleCloudProviderBadge width={18} height={18} />,
|
||||
};
|
||||
|
||||
interface AccountsSelectorProps {
|
||||
|
||||
@@ -91,7 +91,7 @@ const PROVIDER_DATA: Record<
|
||||
label: "Infrastructure as Code",
|
||||
icon: IacProviderBadge,
|
||||
},
|
||||
oci: {
|
||||
oraclecloud: {
|
||||
label: "Oracle Cloud Infrastructure",
|
||||
icon: OracleCloudProviderBadge,
|
||||
},
|
||||
|
||||
@@ -49,7 +49,7 @@ const providerDisplayData: Record<
|
||||
label: "Infrastructure as Code",
|
||||
component: <CustomProviderInputIac />,
|
||||
},
|
||||
oci: {
|
||||
oraclecloud: {
|
||||
label: "Oracle Cloud Infrastructure",
|
||||
component: <CustomProviderInputOracleCloud />,
|
||||
},
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import { Snippet } from "@heroui/snippet";
|
||||
import { Tooltip } from "@heroui/tooltip";
|
||||
import { ExternalLink, Link } from "lucide-react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
|
||||
import { CodeSnippet } from "@/components/ui/code-snippet/code-snippet";
|
||||
import { CustomSection } from "@/components/ui/custom";
|
||||
import { CustomLink } from "@/components/ui/custom/custom-link";
|
||||
import {
|
||||
CopyLinkButton,
|
||||
EntityInfoShort,
|
||||
InfoField,
|
||||
} from "@/components/ui/entities";
|
||||
import { EntityInfoShort, InfoField } from "@/components/ui/entities";
|
||||
import { DateWithTime } from "@/components/ui/entities/date-with-time";
|
||||
import { SeverityBadge } from "@/components/ui/table/severity-badge";
|
||||
import { buildGitFileUrl, extractLineRangeFromUid } from "@/lib/iac-utils";
|
||||
import { FindingProps, ProviderType } from "@/types";
|
||||
|
||||
import { Muted } from "../muted";
|
||||
@@ -60,14 +59,32 @@ export const FindingDetail = ({
|
||||
params.set("id", findingDetails.id);
|
||||
const url = `${window.location.origin}${currentUrl.pathname}?${params.toString()}`;
|
||||
|
||||
// Build Git URL for IaC findings
|
||||
const gitUrl =
|
||||
providerDetails.provider === "iac"
|
||||
? buildGitFileUrl(
|
||||
providerDetails.uid,
|
||||
resource.name,
|
||||
extractLineRangeFromUid(attributes.uid) || "",
|
||||
)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 rounded-lg">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="dark:text-prowler-theme-pale/90 line-clamp-2 text-lg leading-tight font-medium text-gray-800">
|
||||
<h2 className="dark:text-prowler-theme-pale/90 line-clamp-2 flex items-center gap-2 text-lg leading-tight font-medium text-gray-800">
|
||||
{renderValue(attributes.check_metadata.checktitle)}
|
||||
<CopyLinkButton url={url} />
|
||||
<Tooltip content="Copy finding link to clipboard" size="sm">
|
||||
<button
|
||||
onClick={() => navigator.clipboard.writeText(url)}
|
||||
className="text-bg-data-info inline-flex cursor-pointer transition-opacity hover:opacity-80"
|
||||
aria-label="Copy finding link to clipboard"
|
||||
>
|
||||
<Link size={16} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</h2>
|
||||
</div>
|
||||
<div className="flex items-center gap-x-4">
|
||||
@@ -238,7 +255,30 @@ export const FindingDetail = ({
|
||||
</CustomSection>
|
||||
|
||||
{/* Resource Details */}
|
||||
<CustomSection title="Resource Details">
|
||||
<CustomSection
|
||||
title={
|
||||
providerDetails.provider === "iac" ? (
|
||||
<span className="flex items-center gap-2">
|
||||
Resource Details
|
||||
{gitUrl && (
|
||||
<Tooltip content="Go to Resource in the Repository" size="sm">
|
||||
<a
|
||||
href={gitUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-bg-data-info inline-flex cursor-pointer"
|
||||
aria-label="Open resource in repository"
|
||||
>
|
||||
<ExternalLink size={16} className="inline" />
|
||||
</a>
|
||||
</Tooltip>
|
||||
)}
|
||||
</span>
|
||||
) : (
|
||||
"Resource Details"
|
||||
)
|
||||
}
|
||||
>
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<InfoField label="Resource Name">
|
||||
{renderValue(resource.name)}
|
||||
|
||||
@@ -41,7 +41,7 @@ export const ProvidersOverview = ({
|
||||
return <GitHubProviderBadge width={30} height={30} />;
|
||||
case "iac":
|
||||
return <IacProviderBadge width={30} height={30} />;
|
||||
case "oci":
|
||||
case "oraclecloud":
|
||||
return <OracleCloudProviderBadge width={30} height={30} />;
|
||||
default:
|
||||
return null;
|
||||
@@ -56,7 +56,7 @@ export const ProvidersOverview = ({
|
||||
kubernetes: "Kubernetes",
|
||||
github: "GitHub",
|
||||
iac: "IaC",
|
||||
oci: "OCI",
|
||||
oraclecloud: "OCI",
|
||||
};
|
||||
|
||||
const providers = PROVIDER_TYPES.map((providerType) => ({
|
||||
|
||||
@@ -18,7 +18,7 @@ const providerTypeLabels: Record<ProviderType, string> = {
|
||||
kubernetes: "Kubernetes",
|
||||
github: "GitHub",
|
||||
iac: "Infrastructure as Code",
|
||||
oci: "Oracle Cloud Infrastructure",
|
||||
oraclecloud: "Oracle Cloud Infrastructure",
|
||||
};
|
||||
|
||||
interface EnhancedProviderSelectorProps {
|
||||
|
||||
@@ -88,7 +88,7 @@ export const RadioGroupProvider: React.FC<RadioGroupProviderProps> = ({
|
||||
</CustomRadio>
|
||||
<CustomRadio
|
||||
description="Oracle Cloud Infrastructure"
|
||||
value="oci"
|
||||
value="oraclecloud"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<OracleCloudProviderBadge size={26} />
|
||||
|
||||
@@ -167,7 +167,7 @@ export const BaseCredentialsForm = ({
|
||||
control={form.control as unknown as Control<IacCredentials>}
|
||||
/>
|
||||
)}
|
||||
{providerType === "oci" && (
|
||||
{providerType === "oraclecloud" && (
|
||||
<OracleCloudCredentialsForm
|
||||
control={form.control as unknown as Control<OCICredentials>}
|
||||
/>
|
||||
|
||||
@@ -56,7 +56,7 @@ const getProviderFieldDetails = (providerType?: ProviderType) => {
|
||||
label: "Repository URL",
|
||||
placeholder: "e.g. https://github.com/user/repo",
|
||||
};
|
||||
case "oci":
|
||||
case "oraclecloud":
|
||||
return {
|
||||
label: "Tenancy OCID",
|
||||
placeholder: "e.g. ocid1.tenancy.oc1..aaaaaaa...",
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
import { Snippet } from "@heroui/snippet";
|
||||
import { Spinner } from "@heroui/spinner";
|
||||
import { InfoIcon } from "lucide-react";
|
||||
import { Tooltip } from "@heroui/tooltip";
|
||||
import { ExternalLink, InfoIcon } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { getFindingById } from "@/actions/findings";
|
||||
@@ -17,6 +18,7 @@ import {
|
||||
} from "@/components/ui/entities";
|
||||
import { SeverityBadge, StatusFindingBadge } from "@/components/ui/table";
|
||||
import { createDict } from "@/lib";
|
||||
import { buildGitFileUrl } from "@/lib/iac-utils";
|
||||
import { FindingProps, ProviderType, ResourceProps } from "@/types";
|
||||
|
||||
const renderValue = (value: string | null | undefined) => {
|
||||
@@ -162,6 +164,12 @@ export const ResourceDetail = ({
|
||||
const providerData = resource.relationships.provider.data.attributes;
|
||||
const allFindings = findingsData;
|
||||
|
||||
// Build Git URL for IaC resources
|
||||
const gitUrl =
|
||||
providerData.provider === "iac"
|
||||
? buildGitFileUrl(providerData.uid, attributes.name, "")
|
||||
: null;
|
||||
|
||||
if (selectedFindingId) {
|
||||
const findingTitle =
|
||||
findingDetails?.attributes?.check_metadata?.checktitle ||
|
||||
@@ -186,7 +194,30 @@ export const ResourceDetail = ({
|
||||
return (
|
||||
<div className="flex flex-col gap-6 rounded-lg">
|
||||
{/* Resource Details section */}
|
||||
<CustomSection title="Resource Details">
|
||||
<CustomSection
|
||||
title={
|
||||
providerData.provider === "iac" ? (
|
||||
<span className="flex items-center gap-2">
|
||||
Resource Details
|
||||
{gitUrl && (
|
||||
<Tooltip content="Go to Resource in the Repository" size="sm">
|
||||
<a
|
||||
href={gitUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-bg-data-info inline-flex cursor-pointer"
|
||||
aria-label="Open resource in repository"
|
||||
>
|
||||
<ExternalLink size={16} className="inline" />
|
||||
</a>
|
||||
</Tooltip>
|
||||
)}
|
||||
</span>
|
||||
) : (
|
||||
"Resource Details"
|
||||
)
|
||||
}
|
||||
>
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<InfoField label="Resource UID" variant="simple">
|
||||
<Snippet className="bg-gray-50 py-1 dark:bg-slate-800" hideSymbol>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { type ReactNode } from "react";
|
||||
|
||||
interface CustomSectionProps {
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
action?: React.ReactNode;
|
||||
title: string | ReactNode;
|
||||
children: ReactNode;
|
||||
action?: ReactNode;
|
||||
}
|
||||
|
||||
export const CustomSection = ({
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { Tooltip } from "@heroui/tooltip";
|
||||
import { CheckCheck, ExternalLink } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
type CopyLinkButtonProps = {
|
||||
url: string;
|
||||
};
|
||||
|
||||
export const CopyLinkButton = ({ url }: CopyLinkButtonProps) => {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(url);
|
||||
setCopied(true);
|
||||
|
||||
setTimeout(() => setCopied(false), 500);
|
||||
} catch (err) {
|
||||
console.error("Failed to copy URL to clipboard:", err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip content="Copy URL to clipboard" size="sm">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCopy}
|
||||
className="ml-2 inline-flex cursor-pointer flex-row items-center gap-2 p-0"
|
||||
aria-label="Copy URL to clipboard"
|
||||
>
|
||||
{copied ? (
|
||||
<CheckCheck size={16} className="inline" />
|
||||
) : (
|
||||
<ExternalLink size={16} className="inline" />
|
||||
)}
|
||||
</button>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
@@ -28,7 +28,7 @@ export const getProviderLogo = (provider: ProviderType) => {
|
||||
return <GitHubProviderBadge width={35} height={35} />;
|
||||
case "iac":
|
||||
return <IacProviderBadge width={35} height={35} />;
|
||||
case "oci":
|
||||
case "oraclecloud":
|
||||
return <OracleCloudProviderBadge width={35} height={35} />;
|
||||
default:
|
||||
return null;
|
||||
@@ -51,7 +51,7 @@ export const getProviderName = (provider: ProviderType): string => {
|
||||
return "GitHub";
|
||||
case "iac":
|
||||
return "Infrastructure as Code";
|
||||
case "oci":
|
||||
case "oraclecloud":
|
||||
return "Oracle Cloud Infrastructure";
|
||||
default:
|
||||
return "Unknown Provider";
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from "./copy-link-button";
|
||||
export * from "./date-with-time";
|
||||
export * from "./entity-info-short";
|
||||
export * from "./get-provider-logo";
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
# Code Review - Quick Start
|
||||
|
||||
## 3 Steps to Enable
|
||||
|
||||
### 1. Open `.env`
|
||||
```bash
|
||||
nano ui/.env
|
||||
# or your favorite editor
|
||||
```
|
||||
|
||||
### 2. Find this line
|
||||
```bash
|
||||
CODE_REVIEW_ENABLED=false
|
||||
```
|
||||
|
||||
### 3. Change it to
|
||||
```bash
|
||||
CODE_REVIEW_ENABLED=true
|
||||
```
|
||||
|
||||
**Done! ✅**
|
||||
|
||||
---
|
||||
|
||||
## What Happens Now
|
||||
|
||||
Every time you `git commit`:
|
||||
|
||||
```
|
||||
✅ If your code complies with AGENTS.md standards:
|
||||
→ Commit executes normally
|
||||
|
||||
❌ If there are standard violations:
|
||||
→ Commit is BLOCKED
|
||||
→ You see the errors in the terminal
|
||||
→ Fix the code
|
||||
→ Commit again
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example
|
||||
|
||||
```bash
|
||||
$ git commit -m "feat: add new component"
|
||||
|
||||
🏁 Prowler UI - Pre-Commit Hook
|
||||
|
||||
ℹ️ Code Review Status: true
|
||||
|
||||
🔍 Running Claude Code standards validation...
|
||||
|
||||
📋 Files to validate:
|
||||
- components/my-feature.tsx
|
||||
|
||||
📤 Sending to Claude Code for validation...
|
||||
|
||||
STATUS: FAILED
|
||||
- File: components/my-feature.tsx:45
|
||||
Rule: React Imports
|
||||
Issue: Using 'import * as React'
|
||||
Expected: import { useState } from "react"
|
||||
|
||||
❌ VALIDATION FAILED
|
||||
Fix violations before committing
|
||||
|
||||
# Fix the file and commit again
|
||||
$ git commit -m "feat: add new component"
|
||||
|
||||
🏁 Prowler UI - Pre-Commit Hook
|
||||
|
||||
ℹ️ Code Review Status: true
|
||||
|
||||
🔍 Running Claude Code standards validation...
|
||||
|
||||
✅ VALIDATION PASSED
|
||||
|
||||
# Commit successful ✅
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Disable Temporarily
|
||||
|
||||
If you need to commit without validation:
|
||||
|
||||
```bash
|
||||
# Option 1: Change in .env
|
||||
CODE_REVIEW_ENABLED=false
|
||||
|
||||
# Option 2: Bypass (use with caution!)
|
||||
git commit --no-verify
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What Gets Validated
|
||||
|
||||
- ✅ Correct React imports
|
||||
- ✅ TypeScript patterns (const-based types)
|
||||
- ✅ Tailwind CSS (no var() or hex in className)
|
||||
- ✅ cn() utility (only for conditionals)
|
||||
- ✅ No useMemo/useCallback without reason
|
||||
- ✅ Zod v4 syntax
|
||||
- ✅ File organization
|
||||
- ✅ Directives "use client"/"use server"
|
||||
|
||||
---
|
||||
|
||||
## More Info
|
||||
|
||||
Read `CODE_REVIEW_SETUP.md` for:
|
||||
- Troubleshooting
|
||||
- Complete details
|
||||
- Advanced configuration
|
||||
@@ -0,0 +1,296 @@
|
||||
# Code Review Setup - Prowler UI
|
||||
|
||||
Guide to set up automatic code validation with Claude Code in the pre-commit hook.
|
||||
|
||||
## Overview
|
||||
|
||||
The code review system works like this:
|
||||
|
||||
1. **When you enable `CODE_REVIEW_ENABLED=true` in `.env`**
|
||||
- When you `git commit`, the pre-commit hook runs
|
||||
- Only validates TypeScript/JavaScript files you're committing
|
||||
- Uses Claude Code to check if they comply with AGENTS.md
|
||||
- If there are violations → **BLOCKS the commit**
|
||||
- If everything is fine → Continues normally
|
||||
|
||||
2. **When `CODE_REVIEW_ENABLED=false` (default)**
|
||||
- The pre-commit hook does not run validation
|
||||
- No standards validation
|
||||
- Developers can commit without restrictions
|
||||
|
||||
## Installation
|
||||
|
||||
### 1. Ensure Claude Code is in your PATH
|
||||
|
||||
```bash
|
||||
# Verify that claude is available in terminal
|
||||
which claude
|
||||
|
||||
# If it doesn't appear, check your Claude Code CLI installation
|
||||
```
|
||||
|
||||
### 2. Enable validation in `.env`
|
||||
|
||||
In `/ui/.env`, find the "Code Review Configuration" section:
|
||||
|
||||
```bash
|
||||
#### Code Review Configuration ####
|
||||
# Enable Claude Code standards validation on pre-commit hook
|
||||
# Set to 'true' to validate changes against AGENTS.md standards via Claude Code
|
||||
# Set to 'false' to skip validation
|
||||
CODE_REVIEW_ENABLED=false # ← Change this to 'true'
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `CODE_REVIEW_ENABLED=true` → Enables validation
|
||||
- `CODE_REVIEW_ENABLED=false` → Disables validation (default)
|
||||
|
||||
### 3. The hook is ready
|
||||
|
||||
The `.husky/pre-commit` file already contains the logic. You don't need to install anything else.
|
||||
|
||||
## How It Works
|
||||
|
||||
### Normal Flow (with validation enabled)
|
||||
|
||||
```bash
|
||||
$ git commit -m "feat: add new component"
|
||||
|
||||
# Pre-commit hook executes automatically
|
||||
🚀 Prowler UI - Pre-Commit Hook
|
||||
ℹ️ Code Review Status: true
|
||||
|
||||
📋 Files to validate:
|
||||
- components/new-feature.tsx
|
||||
- types/new-feature.ts
|
||||
|
||||
📤 Sending to Claude Code for validation...
|
||||
|
||||
# Claude analyzes the files...
|
||||
|
||||
=== VALIDATION REPORT ===
|
||||
STATUS: PASSED
|
||||
All files comply with AGENTS.md standards.
|
||||
|
||||
✅ VALIDATION PASSED
|
||||
# Commit continues ✅
|
||||
```
|
||||
|
||||
### If There Are Violations
|
||||
|
||||
```bash
|
||||
$ git commit -m "feat: add new component"
|
||||
|
||||
# Claude detects issues...
|
||||
|
||||
=== VALIDATION REPORT ===
|
||||
STATUS: FAILED
|
||||
|
||||
- File: components/new-feature.tsx:15
|
||||
Rule: React Imports
|
||||
Issue: Using 'import * as React' instead of named imports
|
||||
Expected: import { useState } from "react"
|
||||
|
||||
❌ VALIDATION FAILED
|
||||
|
||||
Please fix the violations before committing:
|
||||
1. Review the violations listed above
|
||||
2. Fix the code according to AGENTS.md standards
|
||||
3. Commit your changes
|
||||
4. Try again
|
||||
|
||||
# Commit is BLOCKED ❌
|
||||
```
|
||||
|
||||
## What Gets Validated
|
||||
|
||||
The system verifies that files comply with:
|
||||
|
||||
### 1. React Imports
|
||||
```typescript
|
||||
// ❌ WRONG
|
||||
import * as React from "react"
|
||||
import React, { useState } from "react"
|
||||
|
||||
// ✅ CORRECT
|
||||
import { useState } from "react"
|
||||
```
|
||||
|
||||
### 2. TypeScript Type Patterns
|
||||
```typescript
|
||||
// ❌ WRONG
|
||||
type SortOption = "high-low" | "low-high"
|
||||
|
||||
// ✅ CORRECT
|
||||
const SORT_OPTIONS = {
|
||||
HIGH_LOW: "high-low",
|
||||
LOW_HIGH: "low-high",
|
||||
} as const
|
||||
type SortOption = typeof SORT_OPTIONS[keyof typeof SORT_OPTIONS]
|
||||
```
|
||||
|
||||
### 3. Tailwind CSS
|
||||
```typescript
|
||||
// ❌ WRONG
|
||||
className="bg-[var(--color)]"
|
||||
className="text-[#ffffff]"
|
||||
|
||||
// ✅ CORRECT
|
||||
className="bg-card-bg text-white"
|
||||
```
|
||||
|
||||
### 4. cn() Utility
|
||||
```typescript
|
||||
// ❌ WRONG
|
||||
className={cn("flex items-center")}
|
||||
|
||||
// ✅ CORRECT
|
||||
className={cn("h-3 w-3", isCircle ? "rounded-full" : "rounded-sm")}
|
||||
```
|
||||
|
||||
### 5. React 19 Hooks
|
||||
```typescript
|
||||
// ❌ WRONG
|
||||
const memoized = useMemo(() => value, [])
|
||||
|
||||
// ✅ CORRECT
|
||||
// Don't use useMemo (React Compiler handles it)
|
||||
const value = expensiveCalculation()
|
||||
```
|
||||
|
||||
### 6. Zod v4 Syntax
|
||||
```typescript
|
||||
// ❌ WRONG
|
||||
z.string().email()
|
||||
z.string().nonempty()
|
||||
|
||||
// ✅ CORRECT
|
||||
z.email()
|
||||
z.string().min(1)
|
||||
```
|
||||
|
||||
### 7. File Organization
|
||||
```
|
||||
// ❌ WRONG
|
||||
Code used by 2+ features in feature-specific folder
|
||||
|
||||
// ✅ CORRECT
|
||||
Code used by 1 feature → local in that feature
|
||||
Code used by 2+ features → in shared/global
|
||||
```
|
||||
|
||||
### 8. Use Directives
|
||||
```typescript
|
||||
// ❌ WRONG
|
||||
export async function updateUser() { } // Missing "use server"
|
||||
|
||||
// ✅ CORRECT
|
||||
"use server"
|
||||
export async function updateUser() { }
|
||||
```
|
||||
|
||||
## Disable Temporarily
|
||||
|
||||
If you need to commit without validation temporarily:
|
||||
|
||||
```bash
|
||||
# Option 1: Change in .env
|
||||
CODE_REVIEW_ENABLED=false
|
||||
git commit
|
||||
|
||||
# Option 2: Use git hook bypass
|
||||
git commit --no-verify
|
||||
|
||||
# Option 3: Disable the hook
|
||||
chmod -x .husky/pre-commit
|
||||
git commit
|
||||
chmod +x .husky/pre-commit
|
||||
```
|
||||
|
||||
**⚠️ Note:** `--no-verify` skips ALL hooks.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Claude Code CLI not found"
|
||||
|
||||
```
|
||||
⚠️ Claude Code CLI not found in PATH
|
||||
To enable: ensure Claude Code is in PATH and CODE_REVIEW_ENABLED=true
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Check where claude-code is installed
|
||||
which claude-code
|
||||
|
||||
# If not found, add to your ~/.zshrc:
|
||||
export PATH="$HOME/.local/bin:$PATH" # or where it's installed
|
||||
|
||||
# Reload the terminal
|
||||
source ~/.zshrc
|
||||
```
|
||||
|
||||
### "Validation inconclusive"
|
||||
|
||||
If Claude Code cannot determine the status:
|
||||
|
||||
```
|
||||
⚠️ Could not determine validation status
|
||||
Allowing commit (validation inconclusive)
|
||||
```
|
||||
|
||||
The commit is allowed automatically. If you want to be stricter, you can:
|
||||
|
||||
1. Manually review files against AGENTS.md
|
||||
2. Report the analysis problem to Claude
|
||||
|
||||
### Build fails after validation
|
||||
|
||||
```
|
||||
❌ Build failed
|
||||
```
|
||||
|
||||
If validation passes but build fails:
|
||||
|
||||
1. Check the build error
|
||||
2. Fix it locally
|
||||
3. Commit and try again
|
||||
|
||||
## View the Full Report
|
||||
|
||||
Reports are saved in temporary files that are deleted afterward. To see the detailed report in real-time, watch the hook output:
|
||||
|
||||
```bash
|
||||
git commit 2>&1 | tee commit-report.txt
|
||||
```
|
||||
|
||||
This will save everything to `commit-report.txt`.
|
||||
|
||||
## For the Team
|
||||
|
||||
### Enable on your machine
|
||||
|
||||
```bash
|
||||
cd ui
|
||||
# Edit .env locally and set:
|
||||
CODE_REVIEW_ENABLED=true
|
||||
```
|
||||
|
||||
### Recommended Flow
|
||||
|
||||
1. **During development**: `CODE_REVIEW_ENABLED=false`
|
||||
- Iterate faster
|
||||
- Build check still runs
|
||||
|
||||
2. **Before final commit**: `CODE_REVIEW_ENABLED=true`
|
||||
- Verify you meet standards
|
||||
- Prevent PRs rejected for violations
|
||||
|
||||
3. **In CI/CD**: You could add additional validation
|
||||
- (future) Server-side validation in GitHub Actions
|
||||
|
||||
## Questions?
|
||||
|
||||
If you have questions about the standards being validated, check:
|
||||
- `AGENTS.md` - Complete architecture guide
|
||||
- `CLAUDE.md` - Project-specific instructions
|
||||
@@ -0,0 +1,241 @@
|
||||
# Code Review System Documentation
|
||||
|
||||
Complete documentation for the Claude Code-powered pre-commit validation system.
|
||||
|
||||
## Quick Navigation
|
||||
|
||||
**Want to get started in 3 steps?**
|
||||
→ Read: [`CODE_REVIEW_QUICK_START.md`](./CODE_REVIEW_QUICK_START.md)
|
||||
|
||||
**Want complete technical details?**
|
||||
→ Read: [`CODE_REVIEW_SETUP.md`](./CODE_REVIEW_SETUP.md)
|
||||
|
||||
---
|
||||
|
||||
## What This System Does
|
||||
|
||||
Automatically validates code against AGENTS.md standards when you commit using Claude Code.
|
||||
|
||||
```
|
||||
git commit
|
||||
↓
|
||||
(Optional) Claude Code validation
|
||||
↓
|
||||
If violations found → Commit is BLOCKED ❌
|
||||
If code complies → Commit continues ✅
|
||||
```
|
||||
|
||||
**Key Feature:** Configurable with a single variable in `.env`
|
||||
- `CODE_REVIEW_ENABLED=true` → Validates (recommended before commits)
|
||||
- `CODE_REVIEW_ENABLED=false` → Skip validation (default, for iteration)
|
||||
|
||||
---
|
||||
|
||||
## File Guide
|
||||
|
||||
| File | Purpose | Read Time |
|
||||
|------|---------|-----------|
|
||||
| [`CODE_REVIEW_QUICK_START.md`](./CODE_REVIEW_QUICK_START.md) | 3-step setup & examples | 5 min |
|
||||
| [`CODE_REVIEW_SETUP.md`](./CODE_REVIEW_SETUP.md) | Complete technical guide | 15 min |
|
||||
|
||||
---
|
||||
|
||||
## What Gets Validated
|
||||
|
||||
When validation is enabled, the system checks:
|
||||
|
||||
✅ **React Imports**
|
||||
- Must use: `import { useState } from "react"`
|
||||
- Not: `import * as React` or `import React, {`
|
||||
|
||||
✅ **TypeScript Types**
|
||||
- Must use: `const STATUS = {...} as const; type Status = typeof STATUS[...]`
|
||||
- Not: `type Status = "a" | "b"`
|
||||
|
||||
✅ **Tailwind CSS**
|
||||
- Must use: `className="bg-card-bg text-white"`
|
||||
- Not: `className="bg-[var(...)]"` or `className="text-[#fff]"`
|
||||
|
||||
✅ **cn() Utility**
|
||||
- Must use for: `cn("h-3", isActive && "bg-blue")`
|
||||
- Not for: `cn("static-class")`
|
||||
|
||||
✅ **React 19 Hooks**
|
||||
- No: `useMemo()` / `useCallback()` without documented reason
|
||||
- Use: Nothing (React Compiler handles optimization)
|
||||
|
||||
✅ **Zod v4 Syntax**
|
||||
- Must use: `z.email()`, `.min(1)`
|
||||
- Not: `z.string().email()`, `.nonempty()`
|
||||
|
||||
✅ **File Organization**
|
||||
- 1 feature uses → Keep local in feature folder
|
||||
- 2+ features use → Move to shared/global
|
||||
|
||||
✅ **Directives**
|
||||
- Server Actions must have: `"use server"`
|
||||
- Client Components must have: `"use client"`
|
||||
|
||||
---
|
||||
|
||||
## Installation (For Your Team)
|
||||
|
||||
### Step 1: Decide if you want validation
|
||||
- **Optional:** Each developer decides
|
||||
- **Team policy:** Consider making it standard before commits
|
||||
|
||||
### Step 2: Enable in your environment
|
||||
```bash
|
||||
# Edit ui/.env
|
||||
CODE_REVIEW_ENABLED=true
|
||||
```
|
||||
|
||||
### Step 3: Done!
|
||||
Your next `git commit` will validate automatically.
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
| Question | Answer |
|
||||
|----------|--------|
|
||||
| How do I enable it? | Change `CODE_REVIEW_ENABLED=true` in `.env` |
|
||||
| How do I disable it? | Change `CODE_REVIEW_ENABLED=false` in `.env` |
|
||||
| How do I bypass? | Use `git commit --no-verify` (emergency only) |
|
||||
| What if Claude Code isn't found? | Check PATH: `which claude` |
|
||||
| What if hook doesn't run? | Check executable: `chmod +x .husky/pre-commit` |
|
||||
| How do I test it? | Enable validation and commit code with violations to test |
|
||||
| What if I don't have Claude Code? | Validation is skipped gracefully |
|
||||
|
||||
---
|
||||
|
||||
## Key Features
|
||||
|
||||
✅ **No Setup Required**
|
||||
- Uses Claude Code already in your PATH
|
||||
- No API keys needed
|
||||
- Works offline (if Claude Code supports it)
|
||||
|
||||
✅ **Smart Validation**
|
||||
- Only checks files being committed
|
||||
- Not the entire codebase
|
||||
- Fast: ~10-30 seconds with validation enabled
|
||||
|
||||
✅ **Flexible**
|
||||
- Can be enabled/disabled per developer
|
||||
- Can be disabled temporarily with `git commit --no-verify`
|
||||
- Default is disabled (doesn't interrupt workflow)
|
||||
|
||||
✅ **Clear Feedback**
|
||||
- Shows exactly what violates standards
|
||||
- Shows file:line references
|
||||
- Explains how to fix each issue
|
||||
|
||||
✅ **Well Documented**
|
||||
- 5 different documentation files
|
||||
- For different needs and levels
|
||||
- Examples and troubleshooting included
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Developer commits code │
|
||||
└────────────────┬────────────────────────┘
|
||||
↓
|
||||
┌─────────────────┐
|
||||
│ Pre-Commit Hook │
|
||||
│ (.husky/pre-commit)
|
||||
└────────┬────────┘
|
||||
↓
|
||||
Read CODE_REVIEW_ENABLED from .env
|
||||
↓
|
||||
┌──────────────────────────┐
|
||||
│ If false (disabled) │
|
||||
└────────┬─────────────────┘
|
||||
↓
|
||||
exit 0 (OK)
|
||||
↓
|
||||
Commit continues ✅
|
||||
|
||||
┌──────────────────────────┐
|
||||
│ If true (enabled) │
|
||||
└────────┬─────────────────┘
|
||||
↓
|
||||
Extract staged files
|
||||
(git diff --cached)
|
||||
↓
|
||||
Build prompt with git diff
|
||||
↓
|
||||
Send to: claude < prompt
|
||||
↓
|
||||
Analyze against AGENTS.md
|
||||
↓
|
||||
Return: STATUS: PASSED or FAILED
|
||||
↓
|
||||
Parse with: grep "^STATUS:"
|
||||
↓
|
||||
┌──────────────────┐
|
||||
│ PASSED detected │
|
||||
└────────┬─────────┘
|
||||
↓
|
||||
exit 0 (OK)
|
||||
↓
|
||||
Commit continues ✅
|
||||
|
||||
┌──────────────────┐
|
||||
│ FAILED detected │
|
||||
└────────┬─────────┘
|
||||
↓
|
||||
Show violations
|
||||
↓
|
||||
exit 1 (FAIL)
|
||||
↓
|
||||
Commit is BLOCKED ❌
|
||||
↓
|
||||
Developer fixes code
|
||||
Developer commits again
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. **Read:** [`CODE_REVIEW_QUICK_START.md`](./CODE_REVIEW_QUICK_START.md) (5 minutes)
|
||||
2. **Enable:** Set `CODE_REVIEW_ENABLED=true` in your `ui/.env`
|
||||
3. **Test:** Commit some code and see validation in action
|
||||
4. **For help:** See the troubleshooting section in [`CODE_REVIEW_SETUP.md`](./CODE_REVIEW_SETUP.md)
|
||||
|
||||
---
|
||||
|
||||
## Implementation Details
|
||||
|
||||
- **Files Modified:** 1 (`.husky/pre-commit`)
|
||||
- **Files Created:** 3 (documentation)
|
||||
- **Hook Size:** ~120 lines of bash
|
||||
- **Dependencies:** Claude Code CLI (already available)
|
||||
- **Setup Time:** 1 minute
|
||||
- **Default:** Disabled (no workflow interruption)
|
||||
|
||||
---
|
||||
|
||||
## Questions?
|
||||
|
||||
- **How to enable?** → `CODE_REVIEW_QUICK_START.md`
|
||||
- **How does it work?** → `CODE_REVIEW_SETUP.md`
|
||||
- **Troubleshooting?** → See troubleshooting section in `CODE_REVIEW_SETUP.md`
|
||||
|
||||
---
|
||||
|
||||
## Status
|
||||
|
||||
✅ **Ready to Use**
|
||||
|
||||
The system is fully implemented, documented, and tested. You can enable it immediately with a single variable change.
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** November 6, 2024
|
||||
**Status:** Complete Implementation
|
||||
@@ -157,7 +157,7 @@ export const useCredentialsForm = ({
|
||||
};
|
||||
}
|
||||
return baseDefaults;
|
||||
case "oci":
|
||||
case "oraclecloud":
|
||||
return {
|
||||
...baseDefaults,
|
||||
[ProviderCredentialFields.OCI_USER]: "",
|
||||
|
||||
@@ -37,10 +37,10 @@ export const getProviderHelpText = (provider: string) => {
|
||||
text: "Need help scanning your Infrastructure as Code repository?",
|
||||
link: "https://goto.prowler.com/provider-iac",
|
||||
};
|
||||
case "oci":
|
||||
case "oraclecloud":
|
||||
return {
|
||||
text: "Need help connecting your Oracle Cloud account?",
|
||||
link: "https://goto.prowler.com/provider-oci",
|
||||
link: "https://goto.prowler.com/provider-oraclecloud",
|
||||
};
|
||||
default:
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
/**
|
||||
* Extracts line range from a Finding UID
|
||||
* Finding UID format: {CheckID}-{resource_name}-{line_range}
|
||||
* Example: "AVD-AWS-0001-main.tf-10:15" -> "10:15"
|
||||
*
|
||||
* @param findingUid - The finding UID
|
||||
* @returns Line range string or null if not found
|
||||
*/
|
||||
export function extractLineRangeFromUid(findingUid: string): string | null {
|
||||
if (!findingUid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Split by dash and get the last part (line range)
|
||||
const parts = findingUid.split("-");
|
||||
const lastPart = parts[parts.length - 1];
|
||||
|
||||
// Check if the last part is a line range in format "number:number"
|
||||
// This ensures we don't confuse numeric filenames with line ranges
|
||||
if (/^\d+:\d+$/.test(lastPart)) {
|
||||
return lastPart;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a Git repository URL with file path and line numbers
|
||||
* Supports GitHub, GitLab, Bitbucket, and generic Git URLs
|
||||
*
|
||||
* @param repoUrl - Repository URL (can be HTTPS or git@ format)
|
||||
* @param filePath - Path to the file in the repository
|
||||
* @param lineRange - Line range in format "10-15" or "10:15" or "10"
|
||||
* @returns Complete URL to the file with line numbers, or null if URL cannot be built
|
||||
*/
|
||||
export function buildGitFileUrl(
|
||||
repoUrl: string,
|
||||
filePath: string,
|
||||
lineRange: string,
|
||||
): string | null {
|
||||
if (!repoUrl || !filePath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// Normalize the repository URL
|
||||
let normalizedUrl = repoUrl.trim();
|
||||
|
||||
// Convert git@ format to HTTPS (best effort)
|
||||
if (normalizedUrl.startsWith("git@")) {
|
||||
// git@github.com:user/repo.git -> https://github.com/user/repo
|
||||
normalizedUrl = normalizedUrl
|
||||
.replace(/^git@/, "https://")
|
||||
.replace(/\.git$/, "")
|
||||
.replace(/:([^:]+)$/, "/$1"); // Replace last : with /
|
||||
}
|
||||
|
||||
// Remove .git suffix if present
|
||||
normalizedUrl = normalizedUrl.replace(/\.git$/, "");
|
||||
|
||||
// Parse URL to determine provider
|
||||
const url = new URL(normalizedUrl);
|
||||
const hostname = url.hostname.toLowerCase();
|
||||
|
||||
// Clean up file path (remove leading slashes)
|
||||
const cleanFilePath = filePath.replace(/^\/+/, "");
|
||||
|
||||
// Parse line range
|
||||
const { startLine, endLine } = parseLineRange(lineRange);
|
||||
|
||||
// Build URL based on Git provider
|
||||
if (hostname.includes("github")) {
|
||||
return buildGitHubUrl(normalizedUrl, cleanFilePath, startLine, endLine);
|
||||
} else if (hostname.includes("gitlab")) {
|
||||
return buildGitLabUrl(normalizedUrl, cleanFilePath, startLine, endLine);
|
||||
} else if (hostname.includes("bitbucket")) {
|
||||
return buildBitbucketUrl(
|
||||
normalizedUrl,
|
||||
cleanFilePath,
|
||||
startLine,
|
||||
endLine,
|
||||
);
|
||||
} else {
|
||||
// Generic Git provider - try GitHub format as fallback
|
||||
return buildGitHubUrl(normalizedUrl, cleanFilePath, startLine, endLine);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error building Git file URL:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses line range string into start and end line numbers
|
||||
*/
|
||||
function parseLineRange(lineRange: string): {
|
||||
startLine: number | null;
|
||||
endLine: number | null;
|
||||
} {
|
||||
if (!lineRange || lineRange === "file") {
|
||||
return { startLine: null, endLine: null };
|
||||
}
|
||||
|
||||
// Handle formats: "10-15", "10:15", "10"
|
||||
// Safe regex: anchored pattern for line numbers only (no ReDoS risk)
|
||||
// eslint-disable-next-line security/detect-unsafe-regex
|
||||
const match = lineRange.match(/^(\d+)[-:]?(\d+)?$/);
|
||||
if (match) {
|
||||
const startLine = parseInt(match[1], 10);
|
||||
const endLine = match[2] ? parseInt(match[2], 10) : startLine;
|
||||
return { startLine, endLine };
|
||||
}
|
||||
|
||||
return { startLine: null, endLine: null };
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds GitHub-style URL
|
||||
* Format: https://github.com/user/repo/blob/main/path/file.tf#L10-L15
|
||||
*/
|
||||
function buildGitHubUrl(
|
||||
baseUrl: string,
|
||||
filePath: string,
|
||||
startLine: number | null,
|
||||
endLine: number | null,
|
||||
): string {
|
||||
// Assume main/master branch for simplicity
|
||||
const branch = "main";
|
||||
let url = `${baseUrl}/blob/${branch}/${filePath}`;
|
||||
|
||||
if (startLine !== null) {
|
||||
if (endLine !== null && endLine !== startLine) {
|
||||
url += `#L${startLine}-L${endLine}`;
|
||||
} else {
|
||||
url += `#L${startLine}`;
|
||||
}
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds GitLab-style URL
|
||||
* Format: https://gitlab.com/user/repo/-/blob/main/path/file.tf#L10-15
|
||||
*/
|
||||
function buildGitLabUrl(
|
||||
baseUrl: string,
|
||||
filePath: string,
|
||||
startLine: number | null,
|
||||
endLine: number | null,
|
||||
): string {
|
||||
const branch = "main";
|
||||
let url = `${baseUrl}/-/blob/${branch}/${filePath}`;
|
||||
|
||||
if (startLine !== null) {
|
||||
if (endLine !== null && endLine !== startLine) {
|
||||
url += `#L${startLine}-${endLine}`;
|
||||
} else {
|
||||
url += `#L${startLine}`;
|
||||
}
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds Bitbucket-style URL
|
||||
* Format: https://bitbucket.org/user/repo/src/main/path/file.tf#lines-10:15
|
||||
*/
|
||||
function buildBitbucketUrl(
|
||||
baseUrl: string,
|
||||
filePath: string,
|
||||
startLine: number | null,
|
||||
endLine: number | null,
|
||||
): string {
|
||||
const branch = "main";
|
||||
let url = `${baseUrl}/src/${branch}/${filePath}`;
|
||||
|
||||
if (startLine !== null) {
|
||||
if (endLine !== null && endLine !== startLine) {
|
||||
url += `#lines-${startLine}:${endLine}`;
|
||||
} else {
|
||||
url += `#lines-${startLine}`;
|
||||
}
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
@@ -304,7 +304,7 @@ export const buildSecretConfig = (
|
||||
secretType: "static",
|
||||
secret: buildIacSecret(formData),
|
||||
}),
|
||||
oci: () => ({
|
||||
oraclecloud: () => ({
|
||||
secretType: "static",
|
||||
secret: buildOracleCloudSecret(formData, providerUid),
|
||||
}),
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
"start": "next start",
|
||||
"start:standalone": "node .next/standalone/server.js",
|
||||
"deps:log": "node scripts/update-dependency-log.js",
|
||||
"postinstall": "node -e \"const fs=require('fs'); if(fs.existsSync('scripts/update-dependency-log.js')) require('./scripts/update-dependency-log.js'); else console.log('skip deps:log (script missing)');\"",
|
||||
"postinstall": "node scripts/postinstall.js",
|
||||
"typecheck": "tsc",
|
||||
"healthcheck": "npm run typecheck && npm run lint:check",
|
||||
"lint:check": "eslint . --ext .ts,.tsx -c .eslintrc.cjs",
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Post-install script for Prowler UI
|
||||
*
|
||||
* This script runs after npm install to:
|
||||
* 1. Update dependency log (if the script exists)
|
||||
* 2. Setup git hooks (if the script exists)
|
||||
*/
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
function runScriptIfExists(scriptPath, scriptName) {
|
||||
const fullPath = path.join(__dirname, scriptPath);
|
||||
|
||||
if (fs.existsSync(fullPath)) {
|
||||
try {
|
||||
require(fullPath);
|
||||
} catch (error) {
|
||||
console.warn(`⚠️ Error running ${scriptName}:`, error.message);
|
||||
}
|
||||
} else {
|
||||
console.log(`Skip ${scriptName} (script missing)`);
|
||||
}
|
||||
}
|
||||
|
||||
// Run dependency log update
|
||||
runScriptIfExists("./update-dependency-log.js", "deps:log");
|
||||
|
||||
// Run git hooks setup
|
||||
runScriptIfExists("./setup-git-hooks.js", "setup-git-hooks");
|
||||
Executable
+54
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Setup Git Hooks for Prowler UI
|
||||
*
|
||||
* This script configures Git to use Husky hooks located in ui/.husky
|
||||
* It runs automatically after npm install via the postinstall script.
|
||||
*/
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
const path = require('path');
|
||||
|
||||
try {
|
||||
// Get the git root directory
|
||||
const gitRoot = execSync('git rev-parse --show-toplevel', {
|
||||
encoding: 'utf8',
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
}).trim();
|
||||
|
||||
// Check if we're in a git repository
|
||||
if (!gitRoot) {
|
||||
console.log('⚠️ Not in a git repository. Skipping git hooks setup.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Get current hooks path
|
||||
let currentHooksPath;
|
||||
try {
|
||||
currentHooksPath = execSync('git config --get core.hooksPath', {
|
||||
encoding: 'utf8',
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
}).trim();
|
||||
} catch {
|
||||
// core.hooksPath not set yet
|
||||
currentHooksPath = null;
|
||||
}
|
||||
|
||||
const expectedHooksPath = 'ui/.husky';
|
||||
|
||||
// Only configure if not already set correctly
|
||||
if (currentHooksPath !== expectedHooksPath) {
|
||||
execSync(`git config core.hooksPath "${expectedHooksPath}"`, {
|
||||
stdio: 'inherit'
|
||||
});
|
||||
console.log('✅ Git hooks configured: core.hooksPath = ui/.husky');
|
||||
} else {
|
||||
console.log('✅ Git hooks already configured correctly');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
// Don't fail the installation if git hooks setup fails
|
||||
console.warn('⚠️ Could not setup git hooks:', error.message);
|
||||
console.warn(' You may need to run manually: git config core.hooksPath "ui/.husky"');
|
||||
}
|
||||
@@ -4,9 +4,10 @@ const { heroui } = require("@heroui/theme");
|
||||
module.exports = {
|
||||
darkMode: ["class"],
|
||||
content: [
|
||||
"./components/**/*.{ts,jsx,tsx,mdx}",
|
||||
"./app/**/*.{ts,jsx,tsx,mdx}",
|
||||
"./components/**/*.{ts,jsx,tsx}",
|
||||
"./app/**/*.{ts,jsx,tsx}",
|
||||
"./node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}",
|
||||
"!./docs/**/*",
|
||||
],
|
||||
prefix: "",
|
||||
theme: {
|
||||
|
||||
@@ -116,7 +116,7 @@ export const addProviderFormSchema = z
|
||||
providerUid: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
providerType: z.literal("oci"),
|
||||
providerType: z.literal("oraclecloud"),
|
||||
[ProviderCredentialFields.PROVIDER_ALIAS]: z.string(),
|
||||
providerUid: z.string(),
|
||||
}),
|
||||
@@ -210,7 +210,7 @@ export const addCredentialsFormSchema = (
|
||||
.string()
|
||||
.optional(),
|
||||
}
|
||||
: providerType === "oci"
|
||||
: providerType === "oraclecloud"
|
||||
? {
|
||||
[ProviderCredentialFields.OCI_USER]: z
|
||||
.string()
|
||||
|
||||
@@ -6,7 +6,7 @@ export const PROVIDER_TYPES = [
|
||||
"m365",
|
||||
"github",
|
||||
"iac",
|
||||
"oci",
|
||||
"oraclecloud",
|
||||
] as const;
|
||||
|
||||
export type ProviderType = (typeof PROVIDER_TYPES)[number];
|
||||
|
||||
Reference in New Issue
Block a user