mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-06-10 21:42:29 +00:00
Compare commits
184 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| faa8a15182 | |||
| fc25a5a90b | |||
| 8a5405e490 | |||
| 492e9f24a2 | |||
| f7e27402aa | |||
| 747b97fe87 | |||
| d5e2d75c9b | |||
| 82d53c5158 | |||
| 326fddd206 | |||
| 63b59e4d42 | |||
| a790a5060e | |||
| ff35fd90fa | |||
| 7469377079 | |||
| c8441f8d38 | |||
| abf4eb0ffc | |||
| 93717cc830 | |||
| b629bc81f8 | |||
| f628897fe1 | |||
| 54b82a78e3 | |||
| 377faf145f | |||
| 69e316948f | |||
| 62cbff4f53 | |||
| 5582265e9d | |||
| fb5ea3c324 | |||
| 9b5f676f50 | |||
| 88cfc0fa7e | |||
| 665bfa2f13 | |||
| b89b1a64f4 | |||
| 9ba657c261 | |||
| bce958b8e6 | |||
| 914012de2b | |||
| 8d1c476aed | |||
| 567c729e9e | |||
| 3f03dd20e4 | |||
| 1c778354da | |||
| 3a149fa459 | |||
| f3b121950d | |||
| 43c13b7ba1 | |||
| 9447b33800 | |||
| 2934752eeb | |||
| dd6d8c71fd | |||
| 80267c389b | |||
| acfbaf75d5 | |||
| 5f54377407 | |||
| 552aa64741 | |||
| d64f611f51 | |||
| a96cc92d77 | |||
| 3858cccc41 | |||
| 072828512a | |||
| a73ffe5642 | |||
| 8e784a5b6d | |||
| 1b6f9332f1 | |||
| db8b472729 | |||
| 867b371522 | |||
| c0d7c9fc7d | |||
| bb4685cf90 | |||
| 6a95426749 | |||
| ef6af8e84d | |||
| 763130f253 | |||
| 1256c040e9 | |||
| 18b7b48a99 | |||
| 627c11503f | |||
| 712ba84f06 | |||
| 5186e029b3 | |||
| 5bfaedf903 | |||
| 5061da6897 | |||
| c159a28016 | |||
| 82a1b1c921 | |||
| bf2210d0f4 | |||
| 8f0772cb94 | |||
| 5b57079ecd | |||
| 350d759517 | |||
| edd793c9f5 | |||
| 545c2dc685 | |||
| 84955c066c | |||
| 06dd03b170 | |||
| 47bc2ed2dc | |||
| 44281afc54 | |||
| 4d2859d145 | |||
| 45d44a1669 | |||
| ddd83b340e | |||
| ccdb54d7c3 | |||
| bcc246d950 | |||
| 62139e252a | |||
| 86950c3a0a | |||
| f4865ef68d | |||
| ea7209e7ae | |||
| 998c551cf3 | |||
| e6f29b0116 | |||
| eb90bb39dc | |||
| ad189b35ad | |||
| 7d2989a233 | |||
| 862137ae7d | |||
| c86e082d9a | |||
| 80fe048f97 | |||
| f2bffb3ce7 | |||
| cbe2f9eef8 | |||
| 688f41f570 | |||
| a29197637e | |||
| 7a2712a37f | |||
| 189f5cfd8c | |||
| e509480892 | |||
| 7f7955351a | |||
| 46f1db21a8 | |||
| fbe7bc6951 | |||
| f658507847 | |||
| 374078683b | |||
| 114c4e0886 | |||
| 67c62766d4 | |||
| 3f2947158d | |||
| 278a7cb356 | |||
| 890158a79c | |||
| 4dc1602b77 | |||
| bbba0abac9 | |||
| d04fd807c6 | |||
| 3456df4cf1 | |||
| f56aaa791e | |||
| 465a758770 | |||
| 0f7c0c1b2c | |||
| bf8d10b6f6 | |||
| 20d04553d6 | |||
| b56d62e3c4 | |||
| 9a332dcba1 | |||
| 166d9f8823 | |||
| 42f5eed75f | |||
| 01a7db18dd | |||
| d4507465a3 | |||
| 3ac92ed10a | |||
| 43c76ca85c | |||
| 54d87fa96a | |||
| f041f17268 | |||
| 31c80a6967 | |||
| 783ce136f4 | |||
| f829145781 | |||
| 389337f8cd | |||
| a0713c2d66 | |||
| f94d3cbce4 | |||
| 8d8994b468 | |||
| 784a9097a5 | |||
| b9601626e3 | |||
| dc80b011f2 | |||
| ee7d32d460 | |||
| 43fd9ee94e | |||
| 8821a91f3f | |||
| 98d9256f92 | |||
| b35495eaa7 | |||
| 74d6b614b3 | |||
| dd63c16a74 | |||
| 4280266a96 | |||
| b1f02098ff | |||
| 95189b574a | |||
| c5d23503bf | |||
| 77950f6069 | |||
| ec5f2b3753 | |||
| 9e7104fb7f | |||
| 6b3b6ca45e | |||
| 20b8b0b24e | |||
| 4e11540458 | |||
| ee87f2676d | |||
| 74a90aab98 | |||
| 48ff9a5100 | |||
| 3dfd578ee5 | |||
| 0db46cdc81 | |||
| fdac58d031 | |||
| df9d4ce856 | |||
| e6ae4e97e8 | |||
| 10a4c28922 | |||
| 8a828c6e51 | |||
| d7b40905ff | |||
| f9a3b5f3cd | |||
| b73b89242f | |||
| 23a0f6e8de | |||
| 87967abc3f | |||
| ce60c286dc | |||
| 90fd9b0eb8 | |||
| ca262a6797 | |||
| c056d39775 | |||
| 1c4426ea4b | |||
| 36520bd7a1 | |||
| badf0ace76 | |||
| f1f61249e0 | |||
| b371cac18c | |||
| 1846535d8d | |||
| d7d9118b9b |
@@ -3,7 +3,7 @@
|
||||
# For production, it is recommended to use a secure method to store these variables and change the default secret keys.
|
||||
|
||||
#### Prowler UI Configuration ####
|
||||
PROWLER_UI_VERSION="stable"
|
||||
PROWLER_UI_VERSION="latest"
|
||||
SITE_URL=http://localhost:3000
|
||||
API_BASE_URL=http://prowler-api:8080/api/v1
|
||||
NEXT_PUBLIC_API_DOCS_URL=http://prowler-api:8080/api/v1/docs
|
||||
@@ -31,28 +31,21 @@ VALKEY_PORT=6379
|
||||
VALKEY_DB=0
|
||||
|
||||
# API scan settings
|
||||
|
||||
# The path to the directory where scan output should be stored
|
||||
DJANGO_TMP_OUTPUT_DIRECTORY = "/tmp/prowler_api_output"
|
||||
|
||||
# The maximum number of findings to process in a single batch
|
||||
DJANGO_FINDINGS_BATCH_SIZE = 1000
|
||||
|
||||
# The AWS access key to be used when uploading scan output to an S3 bucket
|
||||
# The AWS access key to be used when uploading scan artifacts to an S3 bucket
|
||||
# If left empty, default AWS credentials resolution behavior will be used
|
||||
DJANGO_OUTPUT_S3_AWS_ACCESS_KEY_ID=""
|
||||
DJANGO_ARTIFACTS_AWS_ACCESS_KEY_ID=""
|
||||
|
||||
# The AWS secret key to be used when uploading scan output to an S3 bucket
|
||||
DJANGO_OUTPUT_S3_AWS_SECRET_ACCESS_KEY=""
|
||||
# The AWS secret key to be used when uploading scan artifacts to an S3 bucket
|
||||
DJANGO_ARTIFACTS_AWS_SECRET_ACCESS_KEY=""
|
||||
|
||||
# An optional AWS session token
|
||||
DJANGO_OUTPUT_S3_AWS_SESSION_TOKEN=""
|
||||
DJANGO_ARTIFACTS_AWS_SESSION_TOKEN=""
|
||||
|
||||
# The AWS region where your S3 bucket is located (e.g., "us-east-1")
|
||||
DJANGO_OUTPUT_S3_AWS_DEFAULT_REGION=""
|
||||
DJANGO_ARTIFACTS_AWS_DEFAULT_REGION=""
|
||||
|
||||
# The name of the S3 bucket where scan output should be stored
|
||||
DJANGO_OUTPUT_S3_AWS_OUTPUT_BUCKET=""
|
||||
# The name of the S3 bucket where scan artifacts should be stored
|
||||
DJANGO_ARTIFACTS_AWS_S3_OUTPUT_BUCKET=""
|
||||
|
||||
# Django settings
|
||||
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,prowler-api
|
||||
@@ -116,11 +109,3 @@ jQIDAQAB
|
||||
# openssl rand -base64 32
|
||||
DJANGO_SECRETS_ENCRYPTION_KEY="oE/ltOhp/n1TdbHjVmzcjDPLcLA41CVI/4Rk+UB5ESc="
|
||||
DJANGO_BROKER_VISIBILITY_TIMEOUT=86400
|
||||
DJANGO_SENTRY_DSN=
|
||||
|
||||
# Sentry settings
|
||||
SENTRY_ENVIRONMENT=local
|
||||
SENTRY_RELEASE=local
|
||||
|
||||
#### Prowler release version ####
|
||||
NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.4.5
|
||||
|
||||
@@ -92,13 +92,3 @@ component/api:
|
||||
component/ui:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "ui/**"
|
||||
|
||||
compliance:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "prowler/compliance/**"
|
||||
- any-glob-to-any-file: "prowler/lib/outputs/compliance/**"
|
||||
- any-glob-to-any-file: "tests/lib/outputs/compliance/**"
|
||||
|
||||
review-django-migrations:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "api/src/backend/api/migrations/**"
|
||||
|
||||
@@ -63,12 +63,6 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set short git commit SHA
|
||||
id: vars
|
||||
run: |
|
||||
shortSha=$(git rev-parse --short ${{ github.sha }})
|
||||
echo "SHORT_SHA=${shortSha}" >> $GITHUB_ENV
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
@@ -88,7 +82,6 @@ jobs:
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ env.LATEST_TAG }}
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ env.SHORT_SHA }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
@@ -103,12 +96,3 @@ jobs:
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ env.STABLE_TAG }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Trigger deployment
|
||||
if: github.event_name == 'push'
|
||||
uses: peter-evans/repository-dispatch@v3
|
||||
with:
|
||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||
repository: ${{ secrets.CLOUD_DISPATCH }}
|
||||
event-type: prowler-api-deploy
|
||||
client-payload: '{"sha": "${{ github.sha }}", "short_sha": "${{ env.SHORT_SHA }}"}'
|
||||
|
||||
@@ -6,7 +6,6 @@ on:
|
||||
- "master"
|
||||
- "v5.*"
|
||||
paths:
|
||||
- ".github/workflows/api-pull-request.yml"
|
||||
- "api/**"
|
||||
pull_request:
|
||||
branches:
|
||||
@@ -15,6 +14,7 @@ on:
|
||||
paths:
|
||||
- "api/**"
|
||||
|
||||
|
||||
env:
|
||||
POSTGRES_HOST: localhost
|
||||
POSTGRES_PORT: 5432
|
||||
@@ -26,8 +26,7 @@ env:
|
||||
VALKEY_HOST: localhost
|
||||
VALKEY_PORT: 6379
|
||||
VALKEY_DB: 0
|
||||
API_WORKING_DIR: ./api
|
||||
IMAGE_NAME: prowler-api
|
||||
|
||||
|
||||
jobs:
|
||||
test:
|
||||
@@ -172,18 +171,3 @@ jobs:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
flags: api
|
||||
test-container-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Build Container
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ${{ env.API_WORKING_DIR }}
|
||||
push: false
|
||||
tags: ${{ env.IMAGE_NAME }}:latest
|
||||
outputs: type=docker
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
name: Prowler - Conventional Commit
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- "opened"
|
||||
- "edited"
|
||||
- "synchronize"
|
||||
branches:
|
||||
- "master"
|
||||
- "v3"
|
||||
- "v4.*"
|
||||
- "v5.*"
|
||||
|
||||
jobs:
|
||||
conventional-commit-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: conventional-commit-check
|
||||
id: conventional-commit-check
|
||||
uses: agenthunt/conventional-commit-checker-action@v2.0.0
|
||||
with:
|
||||
pr-title-regex: '^([^\s(]+)(?:\(([^)]+)\))?: (.+)'
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: TruffleHog OSS
|
||||
uses: trufflesecurity/trufflehog@v3.88.14
|
||||
uses: trufflesecurity/trufflehog@v3.88.5
|
||||
with:
|
||||
path: ./
|
||||
base: ${{ github.event.repository.default_branch }}
|
||||
|
||||
@@ -39,8 +39,6 @@ jobs:
|
||||
.backportrc.json
|
||||
.env
|
||||
docker-compose*
|
||||
examples/**
|
||||
.gitignore
|
||||
|
||||
- name: Install poetry
|
||||
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
|
||||
|
||||
@@ -63,12 +63,6 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set short git commit SHA
|
||||
id: vars
|
||||
run: |
|
||||
shortSha=$(git rev-parse --short ${{ github.sha }})
|
||||
echo "SHORT_SHA=${shortSha}" >> $GITHUB_ENV
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
@@ -84,13 +78,10 @@ jobs:
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ${{ env.WORKING_DIRECTORY }}
|
||||
build-args: |
|
||||
NEXT_PUBLIC_PROWLER_RELEASE_VERSION=${{ env.SHORT_SHA }}
|
||||
# Set push: false for testing
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ env.LATEST_TAG }}
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ env.SHORT_SHA }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
@@ -99,20 +90,9 @@ jobs:
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ${{ env.WORKING_DIRECTORY }}
|
||||
build-args: |
|
||||
NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v${{ env.RELEASE_TAG }}
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ env.RELEASE_TAG }}
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ env.STABLE_TAG }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Trigger deployment
|
||||
if: github.event_name == 'push'
|
||||
uses: peter-evans/repository-dispatch@v3
|
||||
with:
|
||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||
repository: ${{ secrets.CLOUD_DISPATCH }}
|
||||
event-type: prowler-ui-deploy
|
||||
client-payload: '{"sha": "${{ github.sha }}", "short_sha": "${{ env.SHORT_SHA }}"}'
|
||||
|
||||
@@ -6,7 +6,6 @@ on:
|
||||
- "master"
|
||||
- "v5.*"
|
||||
paths:
|
||||
- ".github/workflows/ui-pull-request.yml"
|
||||
- "ui/**"
|
||||
pull_request:
|
||||
branches:
|
||||
@@ -14,9 +13,6 @@ on:
|
||||
- "v5.*"
|
||||
paths:
|
||||
- 'ui/**'
|
||||
env:
|
||||
UI_WORKING_DIR: ./ui
|
||||
IMAGE_NAME: prowler-ui
|
||||
|
||||
jobs:
|
||||
test-and-coverage:
|
||||
@@ -43,20 +39,3 @@ jobs:
|
||||
- name: Build the application
|
||||
working-directory: ./ui
|
||||
run: npm run build
|
||||
test-container-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Build Container
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ${{ env.UI_WORKING_DIR }}
|
||||
# Always build using `prod` target
|
||||
target: prod
|
||||
push: false
|
||||
tags: ${{ env.IMAGE_NAME }}:latest
|
||||
outputs: type=docker
|
||||
build-args: |
|
||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_51LwpXXXX
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ tags
|
||||
*.DS_Store
|
||||
|
||||
# Prowler output
|
||||
/output
|
||||
output/
|
||||
|
||||
# Prowler found secrets
|
||||
secrets-*/
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM python:3.12.9-alpine3.20
|
||||
FROM python:3.12.8-alpine3.20
|
||||
|
||||
LABEL maintainer="https://github.com/prowler-cloud/prowler"
|
||||
|
||||
|
||||
@@ -71,11 +71,10 @@ It contains hundreds of controls covering CIS, NIST 800, NIST CSF, CISA, RBI, Fe
|
||||
|
||||
| Provider | Checks | Services | [Compliance Frameworks](https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/compliance/) | [Categories](https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/misc/#categories) |
|
||||
|---|---|---|---|---|
|
||||
| AWS | 564 | 82 | 33 | 10 |
|
||||
| GCP | 77 | 13 | 5 | 3 |
|
||||
| Azure | 140 | 18 | 6 | 3 |
|
||||
| AWS | 564 | 82 | 30 | 10 |
|
||||
| GCP | 77 | 13 | 4 | 3 |
|
||||
| Azure | 140 | 18 | 5 | 3 |
|
||||
| Kubernetes | 83 | 7 | 2 | 7 |
|
||||
| Microsoft365 | 5 | 2 | 1 | 0 |
|
||||
|
||||
> You can list the checks, services, compliance frameworks and categories with `prowler <provider> --list-checks`, `prowler <provider> --list-services`, `prowler <provider> --list-compliance` and `prowler <provider> --list-categories`.
|
||||
|
||||
@@ -119,7 +118,7 @@ docker compose up -d
|
||||
git clone https://github.com/prowler-cloud/prowler
|
||||
cd prowler/api
|
||||
poetry install
|
||||
eval $(poetry env activate)
|
||||
poetry shell
|
||||
set -a
|
||||
source .env
|
||||
docker compose up postgres valkey -d
|
||||
@@ -127,11 +126,6 @@ cd src/backend
|
||||
python manage.py migrate --database admin
|
||||
gunicorn -c config/guniconf.py config.wsgi:application
|
||||
```
|
||||
> [!IMPORTANT]
|
||||
> Starting from Poetry v2.0.0, `poetry shell` has been deprecated in favor of `poetry env activate`.
|
||||
>
|
||||
> If your poetry version is below 2.0.0 you must keep using `poetry shell` to activate your environment.
|
||||
> In case you have any doubts, consult the Poetry environment activation guide: https://python-poetry.org/docs/managing-environments/#activating-the-environment
|
||||
|
||||
> Now, you can access the API documentation at http://localhost:8080/api/v1/docs.
|
||||
|
||||
@@ -141,7 +135,7 @@ gunicorn -c config/guniconf.py config.wsgi:application
|
||||
git clone https://github.com/prowler-cloud/prowler
|
||||
cd prowler/api
|
||||
poetry install
|
||||
eval $(poetry env activate)
|
||||
poetry shell
|
||||
set -a
|
||||
source .env
|
||||
cd src/backend
|
||||
@@ -154,7 +148,7 @@ python -m celery -A config.celery worker -l info -E
|
||||
git clone https://github.com/prowler-cloud/prowler
|
||||
cd prowler/api
|
||||
poetry install
|
||||
eval $(poetry env activate)
|
||||
poetry shell
|
||||
set -a
|
||||
source .env
|
||||
cd src/backend
|
||||
@@ -175,7 +169,7 @@ npm start
|
||||
|
||||
## Prowler CLI
|
||||
### Pip package
|
||||
Prowler CLI is available as a project in [PyPI](https://pypi.org/project/prowler-cloud/), thus can be installed using pip with Python > 3.9.1, < 3.13:
|
||||
Prowler CLI is available as a project in [PyPI](https://pypi.org/project/prowler-cloud/), thus can be installed using pip with Python >= 3.9, < 3.13:
|
||||
|
||||
```console
|
||||
pip install prowler
|
||||
@@ -205,21 +199,15 @@ The container images are available here:
|
||||
|
||||
### From GitHub
|
||||
|
||||
Python > 3.9.1, < 3.13 is required with pip and poetry:
|
||||
Python >= 3.9, < 3.13 is required with pip and poetry:
|
||||
|
||||
``` console
|
||||
git clone https://github.com/prowler-cloud/prowler
|
||||
cd prowler
|
||||
eval $(poetry env activate)
|
||||
poetry shell
|
||||
poetry install
|
||||
python prowler.py -v
|
||||
```
|
||||
> [!IMPORTANT]
|
||||
> Starting from Poetry v2.0.0, `poetry shell` has been deprecated in favor of `poetry env activate`.
|
||||
>
|
||||
> If your poetry version is below 2.0.0 you must keep using `poetry shell` to activate your environment.
|
||||
> In case you have any doubts, consult the Poetry environment activation guide: https://python-poetry.org/docs/managing-environments/#activating-the-environment
|
||||
|
||||
> If you want to clone Prowler from Windows, use `git config core.longpaths true` to allow long file paths.
|
||||
# 📐✏️ High level architecture
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ DJANGO_SECRETS_ENCRYPTION_KEY=""
|
||||
DJANGO_MANAGE_DB_PARTITIONS=[True|False]
|
||||
DJANGO_CELERY_DEADLOCK_ATTEMPTS=5
|
||||
DJANGO_BROKER_VISIBILITY_TIMEOUT=86400
|
||||
DJANGO_SENTRY_DSN=
|
||||
|
||||
# PostgreSQL settings
|
||||
# If running django and celery on host, use 'localhost', else use 'postgres-db'
|
||||
@@ -40,19 +39,3 @@ POSTGRES_DB=prowler_db
|
||||
VALKEY_HOST=[localhost|valkey]
|
||||
VALKEY_PORT=6379
|
||||
VALKEY_DB=0
|
||||
|
||||
# Sentry settings
|
||||
SENTRY_ENVIRONMENT=local
|
||||
SENTRY_RELEASE=local
|
||||
|
||||
# Social login credentials
|
||||
DJANGO_GOOGLE_OAUTH_CLIENT_ID=""
|
||||
DJANGO_GOOGLE_OAUTH_CLIENT_SECRET=""
|
||||
DJANGO_GOOGLE_OAUTH_CALLBACK_URL=""
|
||||
|
||||
DJANGO_GITHUB_OAUTH_CLIENT_ID=""
|
||||
DJANGO_GITHUB_OAUTH_CLIENT_SECRET=""
|
||||
DJANGO_GITHUB_OAUTH_CALLBACK_URL=""
|
||||
|
||||
# Deletion Task Batch Size
|
||||
DJANGO_DELETION_BATCH_SIZE=5000
|
||||
|
||||
+3
-35
@@ -4,51 +4,19 @@ All notable changes to the **Prowler API** are documented in this file.
|
||||
|
||||
---
|
||||
|
||||
## [v1.5.4] (Prowler v5.4.4)
|
||||
|
||||
### Fixed
|
||||
- Fixed a bug with periodic tasks when trying to delete a provider ([#7466])(https://github.com/prowler-cloud/prowler/pull/7466).
|
||||
|
||||
---
|
||||
|
||||
## [v1.5.3] (Prowler v5.4.3)
|
||||
|
||||
### Fixed
|
||||
- Added duplicated scheduled scans handling ([#7401])(https://github.com/prowler-cloud/prowler/pull/7401).
|
||||
- Added environment variable to configure the deletion task batch size ([#7423])(https://github.com/prowler-cloud/prowler/pull/7423).
|
||||
|
||||
---
|
||||
|
||||
## [v1.5.2] (Prowler v5.4.2)
|
||||
|
||||
### Changed
|
||||
- Refactored deletion logic and implemented retry mechanism for deletion tasks [(#7349)](https://github.com/prowler-cloud/prowler/pull/7349).
|
||||
|
||||
---
|
||||
|
||||
## [v1.5.1] (Prowler v5.4.1)
|
||||
|
||||
### Fixed
|
||||
- Added a handled response in case local files are missing [(#7183)](https://github.com/prowler-cloud/prowler/pull/7183).
|
||||
- Fixed a race condition when deleting export files after the S3 upload [(#7172)](https://github.com/prowler-cloud/prowler/pull/7172).
|
||||
- Handled exception when a provider has no secret in test connection [(#7283)](https://github.com/prowler-cloud/prowler/pull/7283).
|
||||
## [Unreleased]
|
||||
|
||||
|
||||
---
|
||||
|
||||
## [v1.5.0] (Prowler v5.4.0)
|
||||
## [v1.5.0] (Prowler v5.4.0) - 2025-XX-XX
|
||||
|
||||
### Added
|
||||
- Social login integration with Google and GitHub [(#6906)](https://github.com/prowler-cloud/prowler/pull/6906)
|
||||
- Add API scan report system, now all scans launched from the API will generate a compressed file with the report in OCSF, CSV and HTML formats [(#6878)](https://github.com/prowler-cloud/prowler/pull/6878).
|
||||
- Configurable Sentry integration [(#6874)](https://github.com/prowler-cloud/prowler/pull/6874)
|
||||
|
||||
### Changed
|
||||
- Optimized `GET /findings` endpoint to improve response time and size [(#7019)](https://github.com/prowler-cloud/prowler/pull/7019).
|
||||
|
||||
---
|
||||
|
||||
## [v1.4.0] (Prowler v5.3.0)
|
||||
## [v1.4.0] (Prowler v5.3.0) - 2025-02-10
|
||||
|
||||
### Changed
|
||||
- Daily scheduled scan instances are now created beforehand with `SCHEDULED` state [(#6700)](https://github.com/prowler-cloud/prowler/pull/6700).
|
||||
|
||||
@@ -269,66 +269,3 @@ poetry shell
|
||||
cd src/backend
|
||||
pytest
|
||||
```
|
||||
|
||||
# Custom commands
|
||||
|
||||
Django provides a way to create custom commands that can be run from the command line.
|
||||
|
||||
> These commands can be found in: ```prowler/api/src/backend/api/management/commands```
|
||||
|
||||
To run a custom command, you need to be in the `prowler/api/src/backend` directory and run:
|
||||
|
||||
```console
|
||||
poetry shell
|
||||
python manage.py <command_name>
|
||||
```
|
||||
|
||||
## Generate dummy data
|
||||
|
||||
```console
|
||||
python manage.py findings --tenant
|
||||
<TENANT_ID> --findings <NUM_FINDINGS> --re
|
||||
sources <NUM_RESOURCES> --batch <TRANSACTION_BATCH_SIZE> --alias <ALIAS>
|
||||
```
|
||||
|
||||
This command creates, for a given tenant, a provider, scan and a set of findings and resources related altogether.
|
||||
|
||||
> Scan progress and state are updated in real time.
|
||||
> - 0-33%: Create resources.
|
||||
> - 33-66%: Create findings.
|
||||
> - 66%: Create resource-finding mapping.
|
||||
>
|
||||
> The last step is required to access the findings details, since the UI needs that to print all the information.
|
||||
|
||||
### Example
|
||||
|
||||
```console
|
||||
~/backend $ poetry run python manage.py findings --tenant
|
||||
fffb1893-3fc7-4623-a5d9-fae47da1c528 --findings 25000 --re
|
||||
sources 1000 --batch 5000 --alias test-script
|
||||
|
||||
Starting data population
|
||||
Tenant: fffb1893-3fc7-4623-a5d9-fae47da1c528
|
||||
Alias: test-script
|
||||
Resources: 1000
|
||||
Findings: 25000
|
||||
Batch size: 5000
|
||||
|
||||
|
||||
Creating resources...
|
||||
100%|███████████████████████| 1/1 [00:00<00:00, 7.72it/s]
|
||||
Resources created successfully.
|
||||
|
||||
|
||||
Creating findings...
|
||||
100%|███████████████████████| 5/5 [00:05<00:00, 1.09s/it]
|
||||
Findings created successfully.
|
||||
|
||||
|
||||
Creating resource-finding mappings...
|
||||
100%|███████████████████████| 5/5 [00:02<00:00, 1.81it/s]
|
||||
Resource-finding mappings created successfully.
|
||||
|
||||
|
||||
Successfully populated test data.
|
||||
```
|
||||
|
||||
@@ -28,7 +28,7 @@ start_prod_server() {
|
||||
|
||||
start_worker() {
|
||||
echo "Starting the worker..."
|
||||
poetry run python -m celery -A config.celery worker -l "${DJANGO_LOGGING_LEVEL:-info}" -Q celery,scans,scan-reports,deletion -E --max-tasks-per-child 1
|
||||
poetry run python -m celery -A config.celery worker -l "${DJANGO_LOGGING_LEVEL:-info}" -Q celery,scans -E --max-tasks-per-child 1
|
||||
}
|
||||
|
||||
start_worker_beat() {
|
||||
|
||||
Generated
+374
-437
File diff suppressed because it is too large
Load Diff
+2
-5
@@ -8,11 +8,10 @@ description = "Prowler's API (Django/DRF)"
|
||||
license = "Apache-2.0"
|
||||
name = "prowler-api"
|
||||
package-mode = false
|
||||
version = "1.5.4"
|
||||
version = "1.4.0"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
celery = {extras = ["pytest"], version = "^5.4.0"}
|
||||
dj-rest-auth = {extras = ["with_social", "jwt"], version = "7.0.1"}
|
||||
django = "5.1.5"
|
||||
django-celery-beat = "^2.7.0"
|
||||
django-celery-results = "^2.5.1"
|
||||
@@ -28,12 +27,11 @@ drf-nested-routers = "^0.94.1"
|
||||
drf-spectacular = "0.27.2"
|
||||
drf-spectacular-jsonapi = "0.5.1"
|
||||
gunicorn = "23.0.0"
|
||||
prowler = {git = "https://github.com/prowler-cloud/prowler.git", branch = "v5.4"}
|
||||
prowler = {git = "https://github.com/prowler-cloud/prowler.git", branch = "PRWLR-5956-Export-Artifacts-only"}
|
||||
psycopg2-binary = "2.9.9"
|
||||
pytest-celery = {extras = ["redis"], version = "^1.0.1"}
|
||||
# Needed for prowler compatibility
|
||||
python = ">=3.11,<3.13"
|
||||
sentry-sdk = {extras = ["django"], version = "^2.20.0"}
|
||||
uuid6 = "2024.7.10"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
@@ -52,7 +50,6 @@ pytest-randomly = "3.15.0"
|
||||
pytest-xdist = "3.6.1"
|
||||
ruff = "0.5.0"
|
||||
safety = "3.2.9"
|
||||
tqdm = "4.67.1"
|
||||
vulture = "2.14"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
|
||||
from django.db import transaction
|
||||
|
||||
from api.db_router import MainRouter
|
||||
from api.db_utils import rls_transaction
|
||||
from api.models import Membership, Role, Tenant, User, UserRoleRelationship
|
||||
|
||||
|
||||
class ProwlerSocialAccountAdapter(DefaultSocialAccountAdapter):
|
||||
@staticmethod
|
||||
def get_user_by_email(email: str):
|
||||
try:
|
||||
return User.objects.get(email=email)
|
||||
except User.DoesNotExist:
|
||||
return None
|
||||
|
||||
def pre_social_login(self, request, sociallogin):
|
||||
# Link existing accounts with the same email address
|
||||
email = sociallogin.account.extra_data.get("email")
|
||||
if email:
|
||||
existing_user = self.get_user_by_email(email)
|
||||
if existing_user:
|
||||
sociallogin.connect(request, existing_user)
|
||||
|
||||
def save_user(self, request, sociallogin, form=None):
|
||||
"""
|
||||
Called after the user data is fully populated from the provider
|
||||
and is about to be saved to the DB for the first time.
|
||||
"""
|
||||
with transaction.atomic(using=MainRouter.admin_db):
|
||||
user = super().save_user(request, sociallogin, form)
|
||||
user.save(using=MainRouter.admin_db)
|
||||
|
||||
tenant = Tenant.objects.using(MainRouter.admin_db).create(
|
||||
name=f"{user.email.split('@')[0]} default tenant"
|
||||
)
|
||||
with rls_transaction(str(tenant.id)):
|
||||
Membership.objects.using(MainRouter.admin_db).create(
|
||||
user=user, tenant=tenant, role=Membership.RoleChoices.OWNER
|
||||
)
|
||||
role = Role.objects.using(MainRouter.admin_db).create(
|
||||
name="admin",
|
||||
tenant_id=tenant.id,
|
||||
manage_users=True,
|
||||
manage_account=True,
|
||||
manage_billing=True,
|
||||
manage_providers=True,
|
||||
manage_integrations=True,
|
||||
manage_scans=True,
|
||||
unlimited_visibility=True,
|
||||
)
|
||||
UserRoleRelationship.objects.using(MainRouter.admin_db).create(
|
||||
user=user,
|
||||
role=role,
|
||||
tenant_id=tenant.id,
|
||||
)
|
||||
return user
|
||||
@@ -1,29 +1,22 @@
|
||||
ALLOWED_APPS = ("django", "socialaccount", "account", "authtoken", "silk")
|
||||
|
||||
|
||||
class MainRouter:
|
||||
default_db = "default"
|
||||
admin_db = "admin"
|
||||
|
||||
def db_for_read(self, model, **hints): # noqa: F841
|
||||
model_table_name = model._meta.db_table
|
||||
if model_table_name.startswith("django_") or any(
|
||||
model_table_name.startswith(f"{app}_") for app in ALLOWED_APPS
|
||||
if model_table_name.startswith("django_") or model_table_name.startswith(
|
||||
"silk_"
|
||||
):
|
||||
return self.admin_db
|
||||
return None
|
||||
|
||||
def db_for_write(self, model, **hints): # noqa: F841
|
||||
model_table_name = model._meta.db_table
|
||||
if any(model_table_name.startswith(f"{app}_") for app in ALLOWED_APPS):
|
||||
if model_table_name.startswith("django_") or model_table_name.startswith(
|
||||
"silk_"
|
||||
):
|
||||
return self.admin_db
|
||||
return None
|
||||
|
||||
def allow_migrate(self, db, app_label, model_name=None, **hints): # noqa: F841
|
||||
return db == self.admin_db
|
||||
|
||||
def allow_relation(self, obj1, obj2, **hints): # noqa: F841
|
||||
# Allow relations if both objects are in either "default" or "admin" db connectors
|
||||
if {obj1._state.db, obj2._state.db} <= {self.default_db, self.admin_db}:
|
||||
return True
|
||||
return None
|
||||
|
||||
@@ -6,7 +6,6 @@ from datetime import datetime, timedelta, timezone
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import BaseUserManager
|
||||
from django.db import connection, models, transaction
|
||||
from django_celery_beat.models import PeriodicTask
|
||||
from psycopg2 import connect as psycopg2_connect
|
||||
from psycopg2.extensions import AsIs, new_type, register_adapter, register_type
|
||||
from rest_framework_json_api.serializers import ValidationError
|
||||
@@ -106,12 +105,11 @@ def generate_random_token(length: int = 14, symbols: str | None = None) -> str:
|
||||
return "".join(secrets.choice(symbols or _symbols) for _ in range(length))
|
||||
|
||||
|
||||
def batch_delete(tenant_id, queryset, batch_size=settings.DJANGO_DELETION_BATCH_SIZE):
|
||||
def batch_delete(queryset, batch_size=5000):
|
||||
"""
|
||||
Deletes objects in batches and returns the total number of deletions and a summary.
|
||||
|
||||
Args:
|
||||
tenant_id (str): Tenant ID the queryset belongs to.
|
||||
queryset (QuerySet): The queryset of objects to delete.
|
||||
batch_size (int): The number of objects to delete in each batch.
|
||||
|
||||
@@ -122,16 +120,15 @@ def batch_delete(tenant_id, queryset, batch_size=settings.DJANGO_DELETION_BATCH_
|
||||
deletion_summary = {}
|
||||
|
||||
while True:
|
||||
with rls_transaction(tenant_id, POSTGRES_TENANT_VAR):
|
||||
# Get a batch of IDs to delete
|
||||
batch_ids = set(
|
||||
queryset.values_list("id", flat=True).order_by("id")[:batch_size]
|
||||
)
|
||||
if not batch_ids:
|
||||
# No more objects to delete
|
||||
break
|
||||
# Get a batch of IDs to delete
|
||||
batch_ids = set(
|
||||
queryset.values_list("id", flat=True).order_by("id")[:batch_size]
|
||||
)
|
||||
if not batch_ids:
|
||||
# No more objects to delete
|
||||
break
|
||||
|
||||
deleted_count, deleted_info = queryset.filter(id__in=batch_ids).delete()
|
||||
deleted_count, deleted_info = queryset.filter(id__in=batch_ids).delete()
|
||||
|
||||
total_deleted += deleted_count
|
||||
for model_label, count in deleted_info.items():
|
||||
@@ -140,18 +137,6 @@ def batch_delete(tenant_id, queryset, batch_size=settings.DJANGO_DELETION_BATCH_
|
||||
return total_deleted, deletion_summary
|
||||
|
||||
|
||||
def delete_related_daily_task(provider_id: str):
|
||||
"""
|
||||
Deletes the periodic task associated with a specific provider.
|
||||
|
||||
Args:
|
||||
provider_id (str): The unique identifier for the provider
|
||||
whose related periodic task should be deleted.
|
||||
"""
|
||||
task_name = f"scan-perform-scheduled-{provider_id}"
|
||||
PeriodicTask.objects.filter(name=task_name).delete()
|
||||
|
||||
|
||||
# Postgres Enums
|
||||
|
||||
|
||||
|
||||
@@ -447,7 +447,9 @@ class FindingFilter(FilterSet):
|
||||
)
|
||||
|
||||
return (
|
||||
queryset.filter(id__gte=start).filter(id__lt=end).filter(scan_id=value_uuid)
|
||||
queryset.filter(id__gte=start)
|
||||
.filter(id__lt=end)
|
||||
.filter(scan__id=value_uuid)
|
||||
)
|
||||
|
||||
def filter_scan_id_in(self, queryset, name, value):
|
||||
@@ -472,32 +474,31 @@ class FindingFilter(FilterSet):
|
||||
]
|
||||
)
|
||||
if start == end:
|
||||
return queryset.filter(id__gte=start).filter(scan_id__in=uuid_list)
|
||||
return queryset.filter(id__gte=start).filter(scan__id__in=uuid_list)
|
||||
else:
|
||||
return (
|
||||
queryset.filter(id__gte=start)
|
||||
.filter(id__lt=end)
|
||||
.filter(scan_id__in=uuid_list)
|
||||
.filter(scan__id__in=uuid_list)
|
||||
)
|
||||
|
||||
def filter_inserted_at(self, queryset, name, value):
|
||||
datetime_value = self.maybe_date_to_datetime(value)
|
||||
start = uuid7_start(datetime_to_uuid7(datetime_value))
|
||||
end = uuid7_start(datetime_to_uuid7(datetime_value + timedelta(days=1)))
|
||||
value = self.maybe_date_to_datetime(value)
|
||||
start = uuid7_start(datetime_to_uuid7(value))
|
||||
|
||||
return queryset.filter(id__gte=start, id__lt=end)
|
||||
return queryset.filter(id__gte=start).filter(inserted_at__date=value)
|
||||
|
||||
def filter_inserted_at_gte(self, queryset, name, value):
|
||||
datetime_value = self.maybe_date_to_datetime(value)
|
||||
start = uuid7_start(datetime_to_uuid7(datetime_value))
|
||||
value = self.maybe_date_to_datetime(value)
|
||||
start = uuid7_start(datetime_to_uuid7(value))
|
||||
|
||||
return queryset.filter(id__gte=start)
|
||||
return queryset.filter(id__gte=start).filter(inserted_at__gte=value)
|
||||
|
||||
def filter_inserted_at_lte(self, queryset, name, value):
|
||||
datetime_value = self.maybe_date_to_datetime(value)
|
||||
end = uuid7_start(datetime_to_uuid7(datetime_value + timedelta(days=1)))
|
||||
value = self.maybe_date_to_datetime(value)
|
||||
end = uuid7_start(datetime_to_uuid7(value))
|
||||
|
||||
return queryset.filter(id__lt=end)
|
||||
return queryset.filter(id__lte=end).filter(inserted_at__lte=value)
|
||||
|
||||
def filter_resource_tag(self, queryset, name, value):
|
||||
overall_query = Q()
|
||||
|
||||
@@ -122,22 +122,6 @@
|
||||
"scanner_args": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "api.provider",
|
||||
"pk": "7791914f-d646-4fe2-b2ed-73f2c6499a36",
|
||||
"fields": {
|
||||
"tenant": "12646005-9067-4d2a-a098-8bb378604362",
|
||||
"inserted_at": "2024-10-18T10:45:26.352Z",
|
||||
"updated_at": "2024-10-18T11:16:23.533Z",
|
||||
"provider": "kubernetes",
|
||||
"uid": "gke_lucky-coast-419309_us-central1_autopilot-cluster-2",
|
||||
"alias": "k8s_testing_2",
|
||||
"connected": true,
|
||||
"connection_last_checked_at": "2024-10-18T11:16:23.503Z",
|
||||
"metadata": {},
|
||||
"scanner_args": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "api.providersecret",
|
||||
"pk": "11491b47-75ae-4f71-ad8d-3e630a72182e",
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
"unique_resource_count": 1,
|
||||
"duration": 5,
|
||||
"scanner_args": {
|
||||
"checks_to_execute": ["accessanalyzer_enabled"]
|
||||
"checks_to_execute": [
|
||||
"accessanalyzer_enabled"
|
||||
]
|
||||
},
|
||||
"inserted_at": "2024-09-01T17:25:27.050Z",
|
||||
"started_at": "2024-09-01T17:25:27.050Z",
|
||||
@@ -31,7 +33,9 @@
|
||||
"unique_resource_count": 1,
|
||||
"duration": 20,
|
||||
"scanner_args": {
|
||||
"checks_to_execute": ["accessanalyzer_enabled"]
|
||||
"checks_to_execute": [
|
||||
"accessanalyzer_enabled"
|
||||
]
|
||||
},
|
||||
"inserted_at": "2024-09-02T17:24:27.050Z",
|
||||
"started_at": "2024-09-02T17:24:27.050Z",
|
||||
@@ -51,7 +55,9 @@
|
||||
"unique_resource_count": 10,
|
||||
"duration": 10,
|
||||
"scanner_args": {
|
||||
"checks_to_execute": ["cloudsql_instance_automated_backups"]
|
||||
"checks_to_execute": [
|
||||
"cloudsql_instance_automated_backups"
|
||||
]
|
||||
},
|
||||
"inserted_at": "2024-09-02T19:26:27.050Z",
|
||||
"started_at": "2024-09-02T19:26:27.050Z",
|
||||
@@ -71,7 +77,9 @@
|
||||
"unique_resource_count": 1,
|
||||
"duration": 35,
|
||||
"scanner_args": {
|
||||
"checks_to_execute": ["accessanalyzer_enabled"]
|
||||
"checks_to_execute": [
|
||||
"accessanalyzer_enabled"
|
||||
]
|
||||
},
|
||||
"inserted_at": "2024-09-02T19:27:27.050Z",
|
||||
"started_at": "2024-09-02T19:27:27.050Z",
|
||||
@@ -89,7 +97,9 @@
|
||||
"name": "test scheduled aws scan",
|
||||
"state": "available",
|
||||
"scanner_args": {
|
||||
"checks_to_execute": ["cloudformation_stack_outputs_find_secrets"]
|
||||
"checks_to_execute": [
|
||||
"cloudformation_stack_outputs_find_secrets"
|
||||
]
|
||||
},
|
||||
"scheduled_at": "2030-09-02T19:20:27.050Z",
|
||||
"inserted_at": "2024-09-02T19:24:27.050Z",
|
||||
@@ -168,7 +178,9 @@
|
||||
"unique_resource_count": 19,
|
||||
"progress": 100,
|
||||
"scanner_args": {
|
||||
"checks_to_execute": ["accessanalyzer_enabled"]
|
||||
"checks_to_execute": [
|
||||
"accessanalyzer_enabled"
|
||||
]
|
||||
},
|
||||
"duration": 7,
|
||||
"scheduled_at": null,
|
||||
@@ -178,56 +190,6 @@
|
||||
"completed_at": "2024-10-18T10:46:05.127Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "api.scan",
|
||||
"pk": "6dd8925f-a52d-48de-a546-d2d90db30ab1",
|
||||
"fields": {
|
||||
"tenant": "12646005-9067-4d2a-a098-8bb378604362",
|
||||
"name": "real scan azure",
|
||||
"provider": "1b59e032-3eb6-4694-93a5-df84cd9b3ce2",
|
||||
"trigger": "manual",
|
||||
"state": "completed",
|
||||
"unique_resource_count": 20,
|
||||
"progress": 100,
|
||||
"scanner_args": {
|
||||
"checks_to_execute": [
|
||||
"accessanalyzer_enabled",
|
||||
"account_security_contact_information_is_registered"
|
||||
]
|
||||
},
|
||||
"duration": 4,
|
||||
"scheduled_at": null,
|
||||
"inserted_at": "2024-10-18T11:16:21.358Z",
|
||||
"updated_at": "2024-10-18T11:16:26.060Z",
|
||||
"started_at": "2024-10-18T11:16:21.593Z",
|
||||
"completed_at": "2024-10-18T11:16:26.060Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "api.scan",
|
||||
"pk": "4ca7ce89-3236-41a8-a369-8937bc152af5",
|
||||
"fields": {
|
||||
"tenant": "12646005-9067-4d2a-a098-8bb378604362",
|
||||
"name": "real scan k8s",
|
||||
"provider": "7791914f-d646-4fe2-b2ed-73f2c6499a36",
|
||||
"trigger": "manual",
|
||||
"state": "completed",
|
||||
"unique_resource_count": 20,
|
||||
"progress": 100,
|
||||
"scanner_args": {
|
||||
"checks_to_execute": [
|
||||
"accessanalyzer_enabled",
|
||||
"account_security_contact_information_is_registered"
|
||||
]
|
||||
},
|
||||
"duration": 4,
|
||||
"scheduled_at": null,
|
||||
"inserted_at": "2024-10-18T11:16:21.358Z",
|
||||
"updated_at": "2024-10-18T11:16:26.060Z",
|
||||
"started_at": "2024-10-18T11:16:21.593Z",
|
||||
"completed_at": "2024-10-18T11:16:26.060Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "api.scan",
|
||||
"pk": "01929f57-c0ee-7553-be0b-cbde006fb6f7",
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
import random
|
||||
from datetime import datetime, timezone
|
||||
from math import ceil
|
||||
from uuid import uuid4
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from tqdm import tqdm
|
||||
|
||||
from api.db_utils import rls_transaction
|
||||
from api.models import (
|
||||
Finding,
|
||||
Provider,
|
||||
Resource,
|
||||
ResourceFindingMapping,
|
||||
Scan,
|
||||
StatusChoices,
|
||||
)
|
||||
from prowler.lib.check.models import CheckMetadata
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Populates the database with test data for performance testing."
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"--tenant",
|
||||
type=str,
|
||||
required=True,
|
||||
help="Tenant id for which the data will be populated.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--resources",
|
||||
type=int,
|
||||
required=True,
|
||||
help="The number of resources to create.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--findings",
|
||||
type=int,
|
||||
required=True,
|
||||
help="The number of findings to create.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--batch", type=int, required=True, help="The batch size for bulk creation."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--alias",
|
||||
type=str,
|
||||
required=False,
|
||||
help="Optional alias for the provider and scan",
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
tenant_id = options["tenant"]
|
||||
num_resources = options["resources"]
|
||||
num_findings = options["findings"]
|
||||
batch_size = options["batch"]
|
||||
alias = options["alias"] or "Testing"
|
||||
uid_token = str(uuid4())
|
||||
|
||||
self.stdout.write(self.style.NOTICE("Starting data population"))
|
||||
self.stdout.write(self.style.NOTICE(f"\tTenant: {tenant_id}"))
|
||||
self.stdout.write(self.style.NOTICE(f"\tAlias: {alias}"))
|
||||
self.stdout.write(self.style.NOTICE(f"\tResources: {num_resources}"))
|
||||
self.stdout.write(self.style.NOTICE(f"\tFindings: {num_findings}"))
|
||||
self.stdout.write(self.style.NOTICE(f"\tBatch size: {batch_size}\n\n"))
|
||||
|
||||
# Resource metadata
|
||||
possible_regions = [
|
||||
"us-east-1",
|
||||
"us-east-2",
|
||||
"us-west-1",
|
||||
"us-west-2",
|
||||
"ca-central-1",
|
||||
"eu-central-1",
|
||||
"eu-west-1",
|
||||
"eu-west-2",
|
||||
"eu-west-3",
|
||||
"ap-southeast-1",
|
||||
"ap-southeast-2",
|
||||
"ap-northeast-1",
|
||||
"ap-northeast-2",
|
||||
"ap-south-1",
|
||||
"sa-east-1",
|
||||
]
|
||||
possible_services = []
|
||||
possible_types = []
|
||||
|
||||
bulk_check_metadata = CheckMetadata.get_bulk(provider="aws")
|
||||
for check_metadata in bulk_check_metadata.values():
|
||||
if check_metadata.ServiceName not in possible_services:
|
||||
possible_services.append(check_metadata.ServiceName)
|
||||
if (
|
||||
check_metadata.ResourceType
|
||||
and check_metadata.ResourceType not in possible_types
|
||||
):
|
||||
possible_types.append(check_metadata.ResourceType)
|
||||
|
||||
with rls_transaction(tenant_id):
|
||||
provider, _ = Provider.all_objects.get_or_create(
|
||||
tenant_id=tenant_id,
|
||||
provider="aws",
|
||||
connected=True,
|
||||
uid=str(random.randint(100000000000, 999999999999)),
|
||||
defaults={
|
||||
"alias": alias,
|
||||
},
|
||||
)
|
||||
|
||||
with rls_transaction(tenant_id):
|
||||
scan = Scan.all_objects.create(
|
||||
tenant_id=tenant_id,
|
||||
provider=provider,
|
||||
name=alias,
|
||||
trigger="manual",
|
||||
state="executing",
|
||||
progress=0,
|
||||
started_at=datetime.now(timezone.utc),
|
||||
)
|
||||
scan_state = "completed"
|
||||
|
||||
try:
|
||||
# Create resources
|
||||
resources = []
|
||||
|
||||
for i in range(num_resources):
|
||||
resources.append(
|
||||
Resource(
|
||||
tenant_id=tenant_id,
|
||||
provider_id=provider.id,
|
||||
uid=f"testing-{uid_token}-{i}",
|
||||
name=f"Testing {uid_token}-{i}",
|
||||
region=random.choice(possible_regions),
|
||||
service=random.choice(possible_services),
|
||||
type=random.choice(possible_types),
|
||||
)
|
||||
)
|
||||
|
||||
num_batches = ceil(len(resources) / batch_size)
|
||||
self.stdout.write(self.style.WARNING("Creating resources..."))
|
||||
for i in tqdm(range(0, len(resources), batch_size), total=num_batches):
|
||||
with rls_transaction(tenant_id):
|
||||
Resource.all_objects.bulk_create(resources[i : i + batch_size])
|
||||
self.stdout.write(self.style.SUCCESS("Resources created successfully.\n\n"))
|
||||
|
||||
with rls_transaction(tenant_id):
|
||||
scan.progress = 33
|
||||
scan.save()
|
||||
|
||||
# Create Findings
|
||||
findings = []
|
||||
possible_deltas = ["new", "changed", None]
|
||||
possible_severities = ["critical", "high", "medium", "low"]
|
||||
findings_resources_mapping = []
|
||||
|
||||
for i in range(num_findings):
|
||||
severity = random.choice(possible_severities)
|
||||
check_id = random.randint(1, 1000)
|
||||
assigned_resource_num = random.randint(0, len(resources) - 1)
|
||||
assigned_resource = resources[assigned_resource_num]
|
||||
findings_resources_mapping.append(assigned_resource_num)
|
||||
|
||||
findings.append(
|
||||
Finding(
|
||||
tenant_id=tenant_id,
|
||||
scan=scan,
|
||||
uid=f"testing-{uid_token}-{i}",
|
||||
delta=random.choice(possible_deltas),
|
||||
check_id=f"check-{check_id}",
|
||||
status=random.choice(list(StatusChoices)),
|
||||
severity=severity,
|
||||
impact=severity,
|
||||
raw_result={},
|
||||
check_metadata={
|
||||
"checktitle": f"Test title for check {check_id}",
|
||||
"risk": f"Testing risk {uid_token}-{i}",
|
||||
"provider": "aws",
|
||||
"severity": severity,
|
||||
"categories": ["category1", "category2", "category3"],
|
||||
"description": "This is a random description that should not matter for testing purposes.",
|
||||
"servicename": assigned_resource.service,
|
||||
"resourcetype": assigned_resource.type,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
num_batches = ceil(len(findings) / batch_size)
|
||||
self.stdout.write(self.style.WARNING("Creating findings..."))
|
||||
for i in tqdm(range(0, len(findings), batch_size), total=num_batches):
|
||||
with rls_transaction(tenant_id):
|
||||
Finding.all_objects.bulk_create(findings[i : i + batch_size])
|
||||
self.stdout.write(self.style.SUCCESS("Findings created successfully.\n\n"))
|
||||
|
||||
with rls_transaction(tenant_id):
|
||||
scan.progress = 66
|
||||
scan.save()
|
||||
|
||||
# Create ResourceFindingMapping
|
||||
mappings = []
|
||||
for index, f in enumerate(findings):
|
||||
mappings.append(
|
||||
ResourceFindingMapping(
|
||||
tenant_id=tenant_id,
|
||||
resource=resources[findings_resources_mapping[index]],
|
||||
finding=f,
|
||||
)
|
||||
)
|
||||
|
||||
num_batches = ceil(len(mappings) / batch_size)
|
||||
self.stdout.write(
|
||||
self.style.WARNING("Creating resource-finding mappings...")
|
||||
)
|
||||
for i in tqdm(range(0, len(mappings), batch_size), total=num_batches):
|
||||
with rls_transaction(tenant_id):
|
||||
ResourceFindingMapping.objects.bulk_create(
|
||||
mappings[i : i + batch_size]
|
||||
)
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
"Resource-finding mappings created successfully.\n\n"
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
self.stdout.write(self.style.ERROR(f"Failed to populate test data: {e}"))
|
||||
scan_state = "failed"
|
||||
finally:
|
||||
scan.completed_at = datetime.now(timezone.utc)
|
||||
scan.duration = int(
|
||||
(datetime.now(timezone.utc) - scan.started_at).total_seconds()
|
||||
)
|
||||
scan.progress = 100
|
||||
scan.state = scan_state
|
||||
scan.unique_resource_count = num_resources
|
||||
with rls_transaction(tenant_id):
|
||||
scan.save()
|
||||
|
||||
self.stdout.write(self.style.NOTICE("Successfully populated test data."))
|
||||
@@ -1,109 +0,0 @@
|
||||
from functools import partial
|
||||
|
||||
from django.db import connection, migrations
|
||||
|
||||
|
||||
def create_index_on_partitions(
|
||||
apps, schema_editor, parent_table: str, index_name: str, index_details: str
|
||||
):
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT inhrelid::regclass::text
|
||||
FROM pg_inherits
|
||||
WHERE inhparent = %s::regclass;
|
||||
""",
|
||||
[parent_table],
|
||||
)
|
||||
partitions = [row[0] for row in cursor.fetchall()]
|
||||
# Iterate over partitions and create index concurrently.
|
||||
# Note: PostgreSQL does not allow CONCURRENTLY inside a transaction,
|
||||
# so we need atomic = False for this migration.
|
||||
for partition in partitions:
|
||||
sql = (
|
||||
f"CREATE INDEX CONCURRENTLY IF NOT EXISTS {partition.replace('.', '_')}_{index_name} ON {partition} "
|
||||
f"{index_details};"
|
||||
)
|
||||
schema_editor.execute(sql)
|
||||
|
||||
|
||||
def drop_index_on_partitions(apps, schema_editor, parent_table: str, index_name: str):
|
||||
with schema_editor.connection.cursor() as cursor:
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT inhrelid::regclass::text
|
||||
FROM pg_inherits
|
||||
WHERE inhparent = %s::regclass;
|
||||
""",
|
||||
[parent_table],
|
||||
)
|
||||
partitions = [row[0] for row in cursor.fetchall()]
|
||||
|
||||
# Iterate over partitions and drop index concurrently.
|
||||
for partition in partitions:
|
||||
partition_index = f"{partition.replace('.', '_')}_{index_name}"
|
||||
sql = f"DROP INDEX CONCURRENTLY IF EXISTS {partition_index};"
|
||||
schema_editor.execute(sql)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
atomic = False
|
||||
|
||||
dependencies = [
|
||||
("api", "0009_increase_provider_uid_maximum_length"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
partial(
|
||||
create_index_on_partitions,
|
||||
parent_table="findings",
|
||||
index_name="findings_tenant_and_id_idx",
|
||||
index_details="(tenant_id, id)",
|
||||
),
|
||||
reverse_code=partial(
|
||||
drop_index_on_partitions,
|
||||
parent_table="findings",
|
||||
index_name="findings_tenant_and_id_idx",
|
||||
),
|
||||
),
|
||||
migrations.RunPython(
|
||||
partial(
|
||||
create_index_on_partitions,
|
||||
parent_table="findings",
|
||||
index_name="find_tenant_scan_idx",
|
||||
index_details="(tenant_id, scan_id)",
|
||||
),
|
||||
reverse_code=partial(
|
||||
drop_index_on_partitions,
|
||||
parent_table="findings",
|
||||
index_name="find_tenant_scan_idx",
|
||||
),
|
||||
),
|
||||
migrations.RunPython(
|
||||
partial(
|
||||
create_index_on_partitions,
|
||||
parent_table="findings",
|
||||
index_name="find_tenant_scan_id_idx",
|
||||
index_details="(tenant_id, scan_id, id)",
|
||||
),
|
||||
reverse_code=partial(
|
||||
drop_index_on_partitions,
|
||||
parent_table="findings",
|
||||
index_name="find_tenant_scan_id_idx",
|
||||
),
|
||||
),
|
||||
migrations.RunPython(
|
||||
partial(
|
||||
create_index_on_partitions,
|
||||
parent_table="findings",
|
||||
index_name="find_delta_new_idx",
|
||||
index_details="(tenant_id, id) where delta = 'new'",
|
||||
),
|
||||
reverse_code=partial(
|
||||
drop_index_on_partitions,
|
||||
parent_table="findings",
|
||||
index_name="find_delta_new_idx",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 5.1.5 on 2025-02-07 10:59
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("api", "0009_increase_provider_uid_maximum_length"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="scan",
|
||||
name="output_path",
|
||||
field=models.CharField(blank=True, max_length=200, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="scan",
|
||||
name="upload_to_s3",
|
||||
field=models.BooleanField(blank=True, null=True),
|
||||
),
|
||||
]
|
||||
@@ -1,49 +0,0 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("api", "0010_findings_performance_indexes_partitions"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddIndex(
|
||||
model_name="finding",
|
||||
index=models.Index(
|
||||
fields=["tenant_id", "id"], name="findings_tenant_and_id_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="finding",
|
||||
index=models.Index(
|
||||
fields=["tenant_id", "scan_id"], name="find_tenant_scan_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="finding",
|
||||
index=models.Index(
|
||||
fields=["tenant_id", "scan_id", "id"], name="find_tenant_scan_id_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="finding",
|
||||
index=models.Index(
|
||||
condition=models.Q(("delta", "new")),
|
||||
fields=["tenant_id", "id"],
|
||||
name="find_delta_new_idx",
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="resourcetagmapping",
|
||||
index=models.Index(
|
||||
fields=["tenant_id", "resource_id"], name="resource_tag_tenant_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="resource",
|
||||
index=models.Index(
|
||||
fields=["tenant_id", "service", "region", "type"],
|
||||
name="resource_tenant_metadata_idx",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -1,15 +0,0 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("api", "0011_findings_performance_indexes_parent"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="scan",
|
||||
name="output_location",
|
||||
field=models.CharField(blank=True, max_length=200, null=True),
|
||||
),
|
||||
]
|
||||
@@ -414,7 +414,8 @@ class Scan(RowLevelSecurityProtectedModel):
|
||||
scheduler_task = models.ForeignKey(
|
||||
PeriodicTask, on_delete=models.CASCADE, null=True, blank=True
|
||||
)
|
||||
output_location = models.CharField(blank=True, null=True, max_length=200)
|
||||
output_path = models.CharField(blank=True, null=True, max_length=200)
|
||||
upload_to_s3 = models.BooleanField(blank=True, null=True)
|
||||
# TODO: mutelist foreign key
|
||||
|
||||
class Meta(RowLevelSecurityProtectedModel.Meta):
|
||||
@@ -553,10 +554,6 @@ class Resource(RowLevelSecurityProtectedModel):
|
||||
fields=["uid", "region", "service", "name"],
|
||||
name="resource_uid_reg_serv_name_idx",
|
||||
),
|
||||
models.Index(
|
||||
fields=["tenant_id", "service", "region", "type"],
|
||||
name="resource_tenant_metadata_idx",
|
||||
),
|
||||
GinIndex(fields=["text_search"], name="gin_resources_search_idx"),
|
||||
]
|
||||
|
||||
@@ -604,12 +601,6 @@ class ResourceTagMapping(RowLevelSecurityProtectedModel):
|
||||
),
|
||||
]
|
||||
|
||||
indexes = [
|
||||
models.Index(
|
||||
fields=["tenant_id", "resource_id"], name="resource_tag_tenant_idx"
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
class Finding(PostgresPartitionedModel, RowLevelSecurityProtectedModel):
|
||||
"""
|
||||
@@ -708,17 +699,7 @@ class Finding(PostgresPartitionedModel, RowLevelSecurityProtectedModel):
|
||||
],
|
||||
name="findings_filter_idx",
|
||||
),
|
||||
models.Index(fields=["tenant_id", "id"], name="findings_tenant_and_id_idx"),
|
||||
GinIndex(fields=["text_search"], name="gin_findings_search_idx"),
|
||||
models.Index(fields=["tenant_id", "scan_id"], name="find_tenant_scan_idx"),
|
||||
models.Index(
|
||||
fields=["tenant_id", "scan_id", "id"], name="find_tenant_scan_id_idx"
|
||||
),
|
||||
models.Index(
|
||||
fields=["tenant_id", "id"],
|
||||
condition=Q(delta="new"),
|
||||
name="find_delta_new_idx",
|
||||
),
|
||||
]
|
||||
|
||||
class JSONAPIMeta:
|
||||
|
||||
@@ -88,9 +88,7 @@ class RowLevelSecurityConstraint(models.BaseConstraint):
|
||||
f"{grant_queries}{self.grant_sql_query.format(statement=statement)}"
|
||||
)
|
||||
|
||||
full_create_sql_query = (
|
||||
f"{self.rls_sql_query}" f"{policy_queries}" f"{grant_queries}"
|
||||
)
|
||||
full_create_sql_query = f"{self.rls_sql_query}{policy_queries}{grant_queries}"
|
||||
|
||||
table_name = model._meta.db_table
|
||||
if self.partition_name:
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from celery import states
|
||||
from celery.signals import before_task_publish
|
||||
from config.celery import celery_app
|
||||
from django.db.models.signals import post_delete
|
||||
from django.dispatch import receiver
|
||||
from django_celery_beat.models import PeriodicTask
|
||||
from django_celery_results.backends.database import DatabaseBackend
|
||||
|
||||
from api.db_utils import delete_related_daily_task
|
||||
from api.models import Provider
|
||||
from config.celery import celery_app
|
||||
|
||||
|
||||
def create_task_result_on_publish(sender=None, headers=None, **kwargs): # noqa: F841
|
||||
@@ -31,4 +31,5 @@ before_task_publish.connect(
|
||||
@receiver(post_delete, sender=Provider)
|
||||
def delete_provider_scan_task(sender, instance, **kwargs): # noqa: F841
|
||||
# Delete the associated periodic task when the provider is deleted
|
||||
delete_related_daily_task(instance.id)
|
||||
task_name = f"scan-perform-scheduled-{instance.id}"
|
||||
PeriodicTask.objects.filter(name=task_name).delete()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Prowler API
|
||||
version: 1.5.4
|
||||
version: 1.4.0
|
||||
description: |-
|
||||
Prowler API specification.
|
||||
|
||||
@@ -4136,12 +4136,8 @@ paths:
|
||||
responses:
|
||||
'200':
|
||||
description: Report obtained successfully
|
||||
'202':
|
||||
description: The task is in progress
|
||||
'403':
|
||||
description: There is a problem with credentials
|
||||
'404':
|
||||
description: The scan has no reports
|
||||
'423':
|
||||
description: There is a problem with the AWS credentials
|
||||
/api/v1/schedules/daily:
|
||||
post:
|
||||
operationId: schedules_daily_create
|
||||
@@ -5033,35 +5029,6 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TokenRefreshResponse'
|
||||
description: ''
|
||||
/api/v1/tokens/switch:
|
||||
post:
|
||||
operationId: tokens_switch_create
|
||||
description: Switch tenant by providing a valid tenant ID. The authenticated
|
||||
user must belong to the tenant.
|
||||
summary: Switch tenant using a valid tenant ID
|
||||
tags:
|
||||
- Token
|
||||
requestBody:
|
||||
content:
|
||||
application/vnd.api+json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TokenSwitchTenantRequest'
|
||||
application/x-www-form-urlencoded:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TokenSwitchTenantRequest'
|
||||
multipart/form-data:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TokenSwitchTenantRequest'
|
||||
required: true
|
||||
security:
|
||||
- jwtAuth: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/vnd.api+json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TokenSwitchTenantResponse'
|
||||
description: ''
|
||||
/api/v1/users:
|
||||
get:
|
||||
operationId: users_list
|
||||
@@ -10113,81 +10080,6 @@ components:
|
||||
$ref: '#/components/schemas/Token'
|
||||
required:
|
||||
- data
|
||||
TokenSwitchTenant:
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
additionalProperties: false
|
||||
properties:
|
||||
type:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/TokenSwitchTenantTypeEnum'
|
||||
description: The [type](https://jsonapi.org/format/#document-resource-object-identification)
|
||||
member is used to describe resource objects that share common attributes
|
||||
and relationships.
|
||||
attributes:
|
||||
type: object
|
||||
properties:
|
||||
tenant_id:
|
||||
type: string
|
||||
format: uuid
|
||||
writeOnly: true
|
||||
description: The tenant ID for which to request a new token.
|
||||
access:
|
||||
type: string
|
||||
readOnly: true
|
||||
refresh:
|
||||
type: string
|
||||
readOnly: true
|
||||
required:
|
||||
- tenant_id
|
||||
TokenSwitchTenantRequest:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
additionalProperties: false
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
description: The [type](https://jsonapi.org/format/#document-resource-object-identification)
|
||||
member is used to describe resource objects that share common attributes
|
||||
and relationships.
|
||||
enum:
|
||||
- tokens-switch-tenant
|
||||
attributes:
|
||||
type: object
|
||||
properties:
|
||||
tenant_id:
|
||||
type: string
|
||||
format: uuid
|
||||
writeOnly: true
|
||||
description: The tenant ID for which to request a new token.
|
||||
access:
|
||||
type: string
|
||||
readOnly: true
|
||||
minLength: 1
|
||||
refresh:
|
||||
type: string
|
||||
readOnly: true
|
||||
minLength: 1
|
||||
required:
|
||||
- tenant_id
|
||||
required:
|
||||
- data
|
||||
TokenSwitchTenantResponse:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/TokenSwitchTenant'
|
||||
required:
|
||||
- data
|
||||
TokenSwitchTenantTypeEnum:
|
||||
type: string
|
||||
enum:
|
||||
- tokens-switch-tenant
|
||||
TokenTypeEnum:
|
||||
type: string
|
||||
enum:
|
||||
|
||||
@@ -3,8 +3,6 @@ from conftest import TEST_PASSWORD, get_api_tokens, get_authorization_header
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from api.models import Membership, User
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_basic_authentication():
|
||||
@@ -179,122 +177,3 @@ def test_user_me_when_inviting_users(create_test_user, tenants_fixture, roles_fi
|
||||
user2_me = client.get(reverse("user-me"), headers=user2_headers)
|
||||
assert user2_me.status_code == 200
|
||||
assert user2_me.json()["data"]["attributes"]["email"] == user2_email
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestTokenSwitchTenant:
|
||||
def test_switch_tenant_with_valid_token(self, tenants_fixture, providers_fixture):
|
||||
client = APIClient()
|
||||
|
||||
test_user = "test_email@prowler.com"
|
||||
test_password = "test_password"
|
||||
|
||||
# Check that we can create a new user without any kind of authentication
|
||||
user_creation_response = client.post(
|
||||
reverse("user-list"),
|
||||
data={
|
||||
"data": {
|
||||
"type": "users",
|
||||
"attributes": {
|
||||
"name": "test",
|
||||
"email": test_user,
|
||||
"password": test_password,
|
||||
},
|
||||
}
|
||||
},
|
||||
format="vnd.api+json",
|
||||
)
|
||||
assert user_creation_response.status_code == 201
|
||||
|
||||
# Create a new relationship between this user and another tenant
|
||||
tenant_id = tenants_fixture[0].id
|
||||
user_instance = User.objects.get(email=test_user)
|
||||
Membership.objects.create(user=user_instance, tenant_id=tenant_id)
|
||||
|
||||
# Check that using our new user's credentials we can authenticate and get the providers
|
||||
access_token, _ = get_api_tokens(client, test_user, test_password)
|
||||
auth_headers = get_authorization_header(access_token)
|
||||
|
||||
user_me_response = client.get(
|
||||
reverse("user-me"),
|
||||
headers=auth_headers,
|
||||
)
|
||||
assert user_me_response.status_code == 200
|
||||
# Assert this user belongs to two tenants
|
||||
assert (
|
||||
user_me_response.json()["data"]["relationships"]["memberships"]["meta"][
|
||||
"count"
|
||||
]
|
||||
== 2
|
||||
)
|
||||
|
||||
provider_response = client.get(
|
||||
reverse("provider-list"),
|
||||
headers=auth_headers,
|
||||
)
|
||||
assert provider_response.status_code == 200
|
||||
# Empty response since there are no providers in this tenant
|
||||
assert not provider_response.json()["data"]
|
||||
|
||||
switch_tenant_response = client.post(
|
||||
reverse("token-switch"),
|
||||
data={
|
||||
"data": {
|
||||
"type": "tokens-switch-tenant",
|
||||
"attributes": {"tenant_id": tenant_id},
|
||||
}
|
||||
},
|
||||
headers=auth_headers,
|
||||
)
|
||||
assert switch_tenant_response.status_code == 200
|
||||
new_access_token = switch_tenant_response.json()["data"]["attributes"]["access"]
|
||||
new_auth_headers = get_authorization_header(new_access_token)
|
||||
|
||||
provider_response = client.get(
|
||||
reverse("provider-list"),
|
||||
headers=new_auth_headers,
|
||||
)
|
||||
assert provider_response.status_code == 200
|
||||
# Now it must be data because we switched to another tenant with providers
|
||||
assert provider_response.json()["data"]
|
||||
|
||||
def test_switch_tenant_with_invalid_token(self, create_test_user, tenants_fixture):
|
||||
client = APIClient()
|
||||
|
||||
access_token, refresh_token = get_api_tokens(
|
||||
client, create_test_user.email, TEST_PASSWORD
|
||||
)
|
||||
auth_headers = get_authorization_header(access_token)
|
||||
|
||||
invalid_token_response = client.post(
|
||||
reverse("token-switch"),
|
||||
data={
|
||||
"data": {
|
||||
"type": "tokens-switch-tenant",
|
||||
"attributes": {"tenant_id": "invalid_tenant_id"},
|
||||
}
|
||||
},
|
||||
headers=auth_headers,
|
||||
)
|
||||
assert invalid_token_response.status_code == 400
|
||||
assert invalid_token_response.json()["errors"][0]["code"] == "invalid"
|
||||
assert (
|
||||
invalid_token_response.json()["errors"][0]["detail"]
|
||||
== "Must be a valid UUID."
|
||||
)
|
||||
|
||||
invalid_tenant_response = client.post(
|
||||
reverse("token-switch"),
|
||||
data={
|
||||
"data": {
|
||||
"type": "tokens-switch-tenant",
|
||||
"attributes": {"tenant_id": tenants_fixture[-1].id},
|
||||
}
|
||||
},
|
||||
headers=auth_headers,
|
||||
)
|
||||
assert invalid_tenant_response.status_code == 400
|
||||
assert invalid_tenant_response.json()["errors"][0]["code"] == "invalid"
|
||||
assert invalid_tenant_response.json()["errors"][0]["detail"] == (
|
||||
"Tenant does not exist or user is not a " "member."
|
||||
)
|
||||
|
||||
@@ -131,10 +131,9 @@ class TestBatchDelete:
|
||||
return provider_count
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_batch_delete(self, tenants_fixture, create_test_providers):
|
||||
tenant_id = str(tenants_fixture[0].id)
|
||||
def test_batch_delete(self, create_test_providers):
|
||||
_, summary = batch_delete(
|
||||
tenant_id, Provider.objects.all(), batch_size=create_test_providers // 2
|
||||
Provider.objects.all(), batch_size=create_test_providers // 2
|
||||
)
|
||||
assert Provider.objects.all().count() == 0
|
||||
assert summary == {"api.Provider": create_test_providers}
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
import pytest
|
||||
from rest_framework.exceptions import NotFound, ValidationError
|
||||
|
||||
from api.db_router import MainRouter
|
||||
from api.exceptions import InvitationTokenExpiredException
|
||||
from api.models import Invitation, Provider
|
||||
from api.utils import (
|
||||
get_prowler_provider_kwargs,
|
||||
initialize_prowler_provider,
|
||||
merge_dicts,
|
||||
prowler_provider_connection_test,
|
||||
return_prowler_provider,
|
||||
validate_invitation,
|
||||
)
|
||||
from prowler.providers.aws.aws_provider import AwsProvider
|
||||
from prowler.providers.azure.azure_provider import AzureProvider
|
||||
from prowler.providers.gcp.gcp_provider import GcpProvider
|
||||
from prowler.providers.kubernetes.kubernetes_provider import KubernetesProvider
|
||||
from rest_framework.exceptions import ValidationError, NotFound
|
||||
|
||||
from api.db_router import MainRouter
|
||||
from api.exceptions import InvitationTokenExpiredException
|
||||
from api.models import Invitation
|
||||
from api.models import Provider
|
||||
from api.utils import (
|
||||
merge_dicts,
|
||||
return_prowler_provider,
|
||||
initialize_prowler_provider,
|
||||
prowler_provider_connection_test,
|
||||
get_prowler_provider_kwargs,
|
||||
)
|
||||
from api.utils import validate_invitation
|
||||
|
||||
|
||||
class TestMergeDicts:
|
||||
@@ -143,18 +144,6 @@ class TestProwlerProviderConnectionTest:
|
||||
key="value", provider_id="1234567890", raise_on_exception=False
|
||||
)
|
||||
|
||||
@pytest.mark.django_db
|
||||
@patch("api.utils.return_prowler_provider")
|
||||
def test_prowler_provider_connection_test_without_secret(
|
||||
self, mock_return_prowler_provider, providers_fixture
|
||||
):
|
||||
mock_return_prowler_provider.return_value = MagicMock()
|
||||
connection = prowler_provider_connection_test(providers_fixture[0])
|
||||
|
||||
assert connection.is_connected is False
|
||||
assert isinstance(connection.error, Provider.secret.RelatedObjectDoesNotExist)
|
||||
assert str(connection.error) == "Provider has no secret."
|
||||
|
||||
|
||||
class TestGetProwlerProviderKwargs:
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import glob
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from unittest.mock import ANY, Mock, patch
|
||||
|
||||
import jwt
|
||||
import pytest
|
||||
from botocore.exceptions import NoCredentialsError
|
||||
from conftest import API_JSON_CONTENT_TYPE, TEST_PASSWORD, TEST_USER
|
||||
from django.conf import settings
|
||||
from django.urls import reverse
|
||||
@@ -24,7 +20,6 @@ from api.models import (
|
||||
RoleProviderGroupRelationship,
|
||||
Scan,
|
||||
StateChoices,
|
||||
Task,
|
||||
User,
|
||||
UserRoleRelationship,
|
||||
)
|
||||
@@ -859,9 +854,9 @@ class TestProviderViewSet:
|
||||
|
||||
included_data = response.json()["included"]
|
||||
for expected_type in expected_resources:
|
||||
assert any(
|
||||
d.get("type") == expected_type for d in included_data
|
||||
), f"Expected type '{expected_type}' not found in included data"
|
||||
assert any(d.get("type") == expected_type for d in included_data), (
|
||||
f"Expected type '{expected_type}' not found in included data"
|
||||
)
|
||||
|
||||
def test_providers_retrieve(self, authenticated_client, providers_fixture):
|
||||
provider1, *_ = providers_fixture
|
||||
@@ -2084,9 +2079,9 @@ class TestScanViewSet:
|
||||
("started_at.gte", "2024-01-01", 3),
|
||||
("started_at.lte", "2024-01-01", 0),
|
||||
("trigger", Scan.TriggerChoices.MANUAL, 1),
|
||||
("state", StateChoices.AVAILABLE, 1),
|
||||
("state", StateChoices.AVAILABLE, 2),
|
||||
("state", StateChoices.FAILED, 1),
|
||||
("state.in", f"{StateChoices.FAILED},{StateChoices.AVAILABLE}", 2),
|
||||
("state.in", f"{StateChoices.FAILED},{StateChoices.AVAILABLE}", 3),
|
||||
("trigger", Scan.TriggerChoices.MANUAL, 1),
|
||||
]
|
||||
),
|
||||
@@ -2161,178 +2156,6 @@ class TestScanViewSet:
|
||||
response = authenticated_client.get(reverse("scan-list"), {"sort": "invalid"})
|
||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
|
||||
def test_report_executing(self, authenticated_client, scans_fixture):
|
||||
"""
|
||||
When the scan is still executing (state == EXECUTING), the view should return
|
||||
the task data with HTTP 202 and a Content-Location header.
|
||||
"""
|
||||
scan = scans_fixture[0]
|
||||
scan.state = StateChoices.EXECUTING
|
||||
scan.save()
|
||||
|
||||
task = Task.objects.create(tenant_id=scan.tenant_id)
|
||||
dummy_task_data = {"id": str(task.id), "state": StateChoices.EXECUTING}
|
||||
|
||||
scan.task = task
|
||||
scan.save()
|
||||
|
||||
with patch(
|
||||
"api.v1.views.TaskSerializer",
|
||||
return_value=type("DummySerializer", (), {"data": dummy_task_data}),
|
||||
):
|
||||
url = reverse("scan-report", kwargs={"pk": scan.id})
|
||||
response = authenticated_client.get(url)
|
||||
assert response.status_code == status.HTTP_202_ACCEPTED
|
||||
assert "Content-Location" in response
|
||||
assert dummy_task_data["id"] in response["Content-Location"]
|
||||
|
||||
def test_report_celery_task_executing(self, authenticated_client, scans_fixture):
|
||||
"""
|
||||
When the scan is not executing but a related celery task exists and is running,
|
||||
the view should return that task data with HTTP 202.
|
||||
"""
|
||||
scan = scans_fixture[0]
|
||||
scan.state = StateChoices.COMPLETED
|
||||
scan.output_location = "dummy"
|
||||
scan.save()
|
||||
|
||||
dummy_task = Task.objects.create(tenant_id=scan.tenant_id)
|
||||
dummy_task.id = "dummy-task-id"
|
||||
dummy_task_data = {"id": dummy_task.id, "state": StateChoices.EXECUTING}
|
||||
|
||||
with patch("api.v1.views.Task.objects.get", return_value=dummy_task), patch(
|
||||
"api.v1.views.TaskSerializer",
|
||||
return_value=type("DummySerializer", (), {"data": dummy_task_data}),
|
||||
):
|
||||
url = reverse("scan-report", kwargs={"pk": scan.id})
|
||||
response = authenticated_client.get(url)
|
||||
assert response.status_code == status.HTTP_202_ACCEPTED
|
||||
assert "Content-Location" in response
|
||||
assert dummy_task_data["id"] in response["Content-Location"]
|
||||
|
||||
def test_report_no_output_location(self, authenticated_client, scans_fixture):
|
||||
"""
|
||||
If the scan does not have an output_location, the view should return a 404.
|
||||
"""
|
||||
scan = scans_fixture[0]
|
||||
scan.state = StateChoices.COMPLETED
|
||||
scan.output_location = ""
|
||||
scan.save()
|
||||
|
||||
url = reverse("scan-report", kwargs={"pk": scan.id})
|
||||
response = authenticated_client.get(url)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
assert response.json()["errors"]["detail"] == "The scan has no reports."
|
||||
|
||||
def test_report_s3_no_credentials(
|
||||
self, authenticated_client, scans_fixture, monkeypatch
|
||||
):
|
||||
"""
|
||||
When output_location is an S3 URL and get_s3_client() raises a credentials exception,
|
||||
the view should return HTTP 403 with the proper error message.
|
||||
"""
|
||||
scan = scans_fixture[0]
|
||||
bucket = "test-bucket"
|
||||
key = "report.zip"
|
||||
scan.output_location = f"s3://{bucket}/{key}"
|
||||
scan.state = StateChoices.COMPLETED
|
||||
scan.save()
|
||||
|
||||
def fake_get_s3_client():
|
||||
raise NoCredentialsError()
|
||||
|
||||
monkeypatch.setattr("api.v1.views.get_s3_client", fake_get_s3_client)
|
||||
|
||||
url = reverse("scan-report", kwargs={"pk": scan.id})
|
||||
response = authenticated_client.get(url)
|
||||
assert response.status_code == status.HTTP_403_FORBIDDEN
|
||||
assert (
|
||||
response.json()["errors"]["detail"]
|
||||
== "There is a problem with credentials."
|
||||
)
|
||||
|
||||
def test_report_s3_success(self, authenticated_client, scans_fixture, monkeypatch):
|
||||
"""
|
||||
When output_location is an S3 URL and the S3 client returns the file successfully,
|
||||
the view should return the ZIP file with HTTP 200 and proper headers.
|
||||
"""
|
||||
scan = scans_fixture[0]
|
||||
bucket = "test-bucket"
|
||||
key = "report.zip"
|
||||
scan.output_location = f"s3://{bucket}/{key}"
|
||||
scan.state = StateChoices.COMPLETED
|
||||
scan.save()
|
||||
|
||||
monkeypatch.setattr(
|
||||
"api.v1.views.env", type("env", (), {"str": lambda self, key: bucket})()
|
||||
)
|
||||
|
||||
class FakeS3Client:
|
||||
def get_object(self, Bucket, Key):
|
||||
assert Bucket == bucket
|
||||
assert Key == key
|
||||
return {"Body": io.BytesIO(b"s3 zip content")}
|
||||
|
||||
monkeypatch.setattr("api.v1.views.get_s3_client", lambda: FakeS3Client())
|
||||
|
||||
url = reverse("scan-report", kwargs={"pk": scan.id})
|
||||
response = authenticated_client.get(url)
|
||||
assert response.status_code == 200
|
||||
expected_filename = os.path.basename("report.zip")
|
||||
content_disposition = response.get("Content-Disposition")
|
||||
assert content_disposition.startswith('attachment; filename="')
|
||||
assert f'filename="{expected_filename}"' in content_disposition
|
||||
assert response.content == b"s3 zip content"
|
||||
|
||||
def test_report_s3_success_no_local_files(
|
||||
self, authenticated_client, scans_fixture, monkeypatch
|
||||
):
|
||||
"""
|
||||
When output_location is a local path and glob.glob returns an empty list,
|
||||
the view should return HTTP 404 with detail "The scan has no reports."
|
||||
"""
|
||||
scan = scans_fixture[0]
|
||||
scan.output_location = "/tmp/nonexistent_report_pattern.zip"
|
||||
scan.state = StateChoices.COMPLETED
|
||||
scan.save()
|
||||
monkeypatch.setattr("api.v1.views.glob.glob", lambda pattern: [])
|
||||
|
||||
url = reverse("scan-report", kwargs={"pk": scan.id})
|
||||
response = authenticated_client.get(url)
|
||||
|
||||
assert response.status_code == 404
|
||||
assert response.json()["errors"]["detail"] == "The scan has no reports."
|
||||
|
||||
def test_report_local_file(
|
||||
self, authenticated_client, scans_fixture, tmp_path, monkeypatch
|
||||
):
|
||||
"""
|
||||
When output_location is a local file path, the view should read the file from disk
|
||||
and return it with proper headers.
|
||||
"""
|
||||
scan = scans_fixture[0]
|
||||
file_content = b"local zip file content"
|
||||
file_path = tmp_path / "report.zip"
|
||||
file_path.write_bytes(file_content)
|
||||
|
||||
scan.output_location = str(file_path)
|
||||
scan.state = StateChoices.COMPLETED
|
||||
scan.save()
|
||||
|
||||
monkeypatch.setattr(
|
||||
glob,
|
||||
"glob",
|
||||
lambda pattern: [str(file_path)] if pattern == str(file_path) else [],
|
||||
)
|
||||
|
||||
url = reverse("scan-report", kwargs={"pk": scan.id})
|
||||
response = authenticated_client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert response.content == file_content
|
||||
content_disposition = response.get("Content-Disposition")
|
||||
assert content_disposition.startswith('attachment; filename="')
|
||||
assert f'filename="{file_path.name}"' in content_disposition
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestTaskViewSet:
|
||||
@@ -2420,9 +2243,9 @@ class TestResourceViewSet:
|
||||
|
||||
included_data = response.json()["included"]
|
||||
for expected_type in expected_resources:
|
||||
assert any(
|
||||
d.get("type") == expected_type for d in included_data
|
||||
), f"Expected type '{expected_type}' not found in included data"
|
||||
assert any(d.get("type") == expected_type for d in included_data), (
|
||||
f"Expected type '{expected_type}' not found in included data"
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"filter_name, filter_value, expected_count",
|
||||
@@ -2612,7 +2435,7 @@ class TestFindingViewSet:
|
||||
[
|
||||
("resources", ["resources"]),
|
||||
("scan", ["scans"]),
|
||||
("resources,scan.provider", ["resources", "scans", "providers"]),
|
||||
("resources.provider,scan", ["resources", "scans", "providers"]),
|
||||
],
|
||||
)
|
||||
def test_findings_list_include(
|
||||
@@ -2628,9 +2451,9 @@ class TestFindingViewSet:
|
||||
|
||||
included_data = response.json()["included"]
|
||||
for expected_type in expected_resources:
|
||||
assert any(
|
||||
d.get("type") == expected_type for d in included_data
|
||||
), f"Expected type '{expected_type}' not found in included data"
|
||||
assert any(d.get("type") == expected_type for d in included_data), (
|
||||
f"Expected type '{expected_type}' not found in included data"
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"filter_name, filter_value, expected_count",
|
||||
@@ -2668,8 +2491,8 @@ class TestFindingViewSet:
|
||||
("search", "orange juice", 1),
|
||||
# full text search on resource
|
||||
("search", "ec2", 2),
|
||||
# full text search on finding tags (disabled for now)
|
||||
# ("search", "value2", 2),
|
||||
# full text search on finding tags
|
||||
("search", "value2", 2),
|
||||
# Temporary disabled until we implement tag filtering in the UI
|
||||
# ("resource_tag_key", "key", 2),
|
||||
# ("resource_tag_key__in", "key,key2", 2),
|
||||
@@ -2912,9 +2735,9 @@ class TestJWTFields:
|
||||
reverse("token-obtain"), data, format="json"
|
||||
)
|
||||
|
||||
assert (
|
||||
response.status_code == status.HTTP_200_OK
|
||||
), f"Unexpected status code: {response.status_code}"
|
||||
assert response.status_code == status.HTTP_200_OK, (
|
||||
f"Unexpected status code: {response.status_code}"
|
||||
)
|
||||
|
||||
access_token = response.data["attributes"]["access"]
|
||||
payload = jwt.decode(access_token, options={"verify_signature": False})
|
||||
@@ -2928,23 +2751,23 @@ class TestJWTFields:
|
||||
# Verify expected fields
|
||||
for field in expected_fields:
|
||||
assert field in payload, f"The field '{field}' is not in the JWT"
|
||||
assert (
|
||||
payload[field] == expected_fields[field]
|
||||
), f"The value of '{field}' does not match"
|
||||
assert payload[field] == expected_fields[field], (
|
||||
f"The value of '{field}' does not match"
|
||||
)
|
||||
|
||||
# Verify time fields are integers
|
||||
for time_field in ["exp", "iat", "nbf"]:
|
||||
assert time_field in payload, f"The field '{time_field}' is not in the JWT"
|
||||
assert isinstance(
|
||||
payload[time_field], int
|
||||
), f"The field '{time_field}' is not an integer"
|
||||
assert isinstance(payload[time_field], int), (
|
||||
f"The field '{time_field}' is not an integer"
|
||||
)
|
||||
|
||||
# Verify identification fields are non-empty strings
|
||||
for id_field in ["jti", "sub", "tenant_id"]:
|
||||
assert id_field in payload, f"The field '{id_field}' is not in the JWT"
|
||||
assert (
|
||||
isinstance(payload[id_field], str) and payload[id_field]
|
||||
), f"The field '{id_field}' is not a valid string"
|
||||
assert isinstance(payload[id_field], str) and payload[id_field], (
|
||||
f"The field '{id_field}' is not a valid string"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
||||
@@ -1,25 +1,15 @@
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
|
||||
from rest_framework.exceptions import NotFound, ValidationError
|
||||
|
||||
from api.db_router import MainRouter
|
||||
from api.exceptions import InvitationTokenExpiredException
|
||||
from api.models import Invitation, Provider
|
||||
from prowler.providers.aws.aws_provider import AwsProvider
|
||||
from prowler.providers.azure.azure_provider import AzureProvider
|
||||
from prowler.providers.common.models import Connection
|
||||
from prowler.providers.gcp.gcp_provider import GcpProvider
|
||||
from prowler.providers.kubernetes.kubernetes_provider import KubernetesProvider
|
||||
from rest_framework.exceptions import ValidationError, NotFound
|
||||
|
||||
|
||||
class CustomOAuth2Client(OAuth2Client):
|
||||
def __init__(self, client_id, secret, *args, **kwargs):
|
||||
# Remove any duplicate "scope_delimiter" from kwargs
|
||||
# Bug present in dj-rest-auth after version v7.0.1
|
||||
# https://github.com/iMerica/dj-rest-auth/issues/673
|
||||
kwargs.pop("scope_delimiter", None)
|
||||
super().__init__(client_id, secret, *args, **kwargs)
|
||||
from api.db_router import MainRouter
|
||||
from api.exceptions import InvitationTokenExpiredException
|
||||
from api.models import Provider, Invitation
|
||||
|
||||
|
||||
def merge_dicts(default_dict: dict, replacement_dict: dict) -> dict:
|
||||
@@ -130,10 +120,7 @@ def prowler_provider_connection_test(provider: Provider) -> Connection:
|
||||
Connection: A connection object representing the result of the connection test for the specified provider.
|
||||
"""
|
||||
prowler_provider = return_prowler_provider(provider)
|
||||
try:
|
||||
prowler_provider_kwargs = provider.secret.secret
|
||||
except Provider.secret.RelatedObjectDoesNotExist as secret_error:
|
||||
return Connection(is_connected=False, error=secret_error)
|
||||
prowler_provider_kwargs = provider.secret.secret
|
||||
return prowler_provider.test_connection(
|
||||
**prowler_provider_kwargs, provider_id=provider.uid, raise_on_exception=False
|
||||
)
|
||||
|
||||
@@ -106,7 +106,7 @@ def uuid7_end(uuid_obj: UUID, offset_months: int = 1) -> UUID:
|
||||
|
||||
Args:
|
||||
uuid_obj: A UUIDv7 object.
|
||||
offset_months: Number of months to offset from the given UUID's date. Defaults to 1 to handle if
|
||||
offset_days: Number of months to offset from the given UUID's date. Defaults to 1 to handle if
|
||||
partitions are not being used, if so the value will be the one set at FINDINGS_TABLE_PARTITION_MONTHS.
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -38,65 +38,7 @@ from api.rls import Tenant
|
||||
# Tokens
|
||||
|
||||
|
||||
def generate_tokens(user: User, tenant_id: str) -> dict:
|
||||
try:
|
||||
refresh = RefreshToken.for_user(user)
|
||||
except InvalidKeyError:
|
||||
# Handle invalid key error
|
||||
raise ValidationError(
|
||||
{
|
||||
"detail": "Token generation failed due to invalid key configuration. Provide valid "
|
||||
"DJANGO_TOKEN_SIGNING_KEY and DJANGO_TOKEN_VERIFYING_KEY in the environment."
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
raise ValidationError({"detail": str(e)})
|
||||
|
||||
# Post-process the tokens
|
||||
# Set the tenant_id
|
||||
refresh["tenant_id"] = tenant_id
|
||||
|
||||
# Set the nbf (not before) claim to the iat (issued at) claim. At this moment, simplejwt does not provide a
|
||||
# way to set the nbf claim
|
||||
refresh.payload["nbf"] = refresh["iat"]
|
||||
|
||||
# Get the access token
|
||||
access = refresh.access_token
|
||||
|
||||
if settings.SIMPLE_JWT["UPDATE_LAST_LOGIN"]:
|
||||
update_last_login(None, user)
|
||||
|
||||
return {"access": str(access), "refresh": str(refresh)}
|
||||
|
||||
|
||||
class BaseTokenSerializer(TokenObtainPairSerializer):
|
||||
def custom_validate(self, attrs, social: bool = False):
|
||||
email = attrs.get("email")
|
||||
password = attrs.get("password")
|
||||
tenant_id = str(attrs.get("tenant_id", ""))
|
||||
|
||||
# Authenticate user
|
||||
user = (
|
||||
User.objects.get(email=email)
|
||||
if social
|
||||
else authenticate(username=email, password=password)
|
||||
)
|
||||
if user is None:
|
||||
raise ValidationError("Invalid credentials")
|
||||
|
||||
if tenant_id:
|
||||
if not user.is_member_of_tenant(tenant_id):
|
||||
raise ValidationError("Tenant does not exist or user is not a member.")
|
||||
else:
|
||||
first_membership = user.memberships.order_by("date_joined").first()
|
||||
if first_membership is None:
|
||||
raise ValidationError("User has no memberships.")
|
||||
tenant_id = str(first_membership.tenant_id)
|
||||
|
||||
return generate_tokens(user, tenant_id)
|
||||
|
||||
|
||||
class TokenSerializer(BaseTokenSerializer):
|
||||
class TokenSerializer(TokenObtainPairSerializer):
|
||||
email = serializers.EmailField(write_only=True)
|
||||
password = serializers.CharField(write_only=True)
|
||||
tenant_id = serializers.UUIDField(
|
||||
@@ -114,25 +56,53 @@ class TokenSerializer(BaseTokenSerializer):
|
||||
resource_name = "tokens"
|
||||
|
||||
def validate(self, attrs):
|
||||
return super().custom_validate(attrs)
|
||||
email = attrs.get("email")
|
||||
password = attrs.get("password")
|
||||
tenant_id = str(attrs.get("tenant_id", ""))
|
||||
|
||||
# Authenticate user
|
||||
user = authenticate(username=email, password=password)
|
||||
if user is None:
|
||||
raise ValidationError("Invalid credentials")
|
||||
|
||||
class TokenSocialLoginSerializer(BaseTokenSerializer):
|
||||
email = serializers.EmailField(write_only=True)
|
||||
if tenant_id:
|
||||
if not user.is_member_of_tenant(tenant_id):
|
||||
raise ValidationError("Tenant does not exist or user is not a member.")
|
||||
else:
|
||||
first_membership = user.memberships.order_by("date_joined").first()
|
||||
if first_membership is None:
|
||||
raise ValidationError("User has no memberships.")
|
||||
tenant_id = str(first_membership.tenant_id)
|
||||
|
||||
# Output tokens
|
||||
refresh = serializers.CharField(read_only=True)
|
||||
access = serializers.CharField(read_only=True)
|
||||
# Generate tokens
|
||||
try:
|
||||
refresh = RefreshToken.for_user(user)
|
||||
except InvalidKeyError:
|
||||
# Handle invalid key error
|
||||
raise ValidationError(
|
||||
{
|
||||
"detail": "Token generation failed due to invalid key configuration. Provide valid "
|
||||
"DJANGO_TOKEN_SIGNING_KEY and DJANGO_TOKEN_VERIFYING_KEY in the environment."
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
raise ValidationError({"detail": str(e)})
|
||||
|
||||
class JSONAPIMeta:
|
||||
resource_name = "tokens"
|
||||
# Post-process the tokens
|
||||
# Set the tenant_id
|
||||
refresh["tenant_id"] = tenant_id
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields.pop("password", None)
|
||||
# Set the nbf (not before) claim to the iat (issued at) claim. At this moment, simplejwt does not provide a
|
||||
# way to set the nbf claim
|
||||
refresh.payload["nbf"] = refresh["iat"]
|
||||
|
||||
def validate(self, attrs):
|
||||
return super().custom_validate(attrs, social=True)
|
||||
# Get the access token
|
||||
access = refresh.access_token
|
||||
|
||||
if settings.SIMPLE_JWT["UPDATE_LAST_LOGIN"]:
|
||||
update_last_login(None, user)
|
||||
|
||||
return {"access": str(access), "refresh": str(refresh)}
|
||||
|
||||
|
||||
# TODO: Check if we can change the parent class to TokenRefreshSerializer from rest_framework_simplejwt.serializers
|
||||
@@ -170,30 +140,6 @@ class TokenRefreshSerializer(serializers.Serializer):
|
||||
raise ValidationError({"refresh": "Invalid or expired token"})
|
||||
|
||||
|
||||
class TokenSwitchTenantSerializer(serializers.Serializer):
|
||||
tenant_id = serializers.UUIDField(
|
||||
write_only=True, help_text="The tenant ID for which to request a new token."
|
||||
)
|
||||
access = serializers.CharField(read_only=True)
|
||||
refresh = serializers.CharField(read_only=True)
|
||||
|
||||
class JSONAPIMeta:
|
||||
resource_name = "tokens-switch-tenant"
|
||||
|
||||
def validate(self, attrs):
|
||||
request = self.context["request"]
|
||||
user = request.user
|
||||
|
||||
if not user.is_authenticated:
|
||||
raise ValidationError("Invalid or expired token.")
|
||||
|
||||
tenant_id = str(attrs.get("tenant_id"))
|
||||
if not user.is_member_of_tenant(tenant_id):
|
||||
raise ValidationError("Tenant does not exist or user is not a member.")
|
||||
|
||||
return generate_tokens(user, tenant_id)
|
||||
|
||||
|
||||
# Base
|
||||
|
||||
|
||||
@@ -745,43 +691,6 @@ class ProviderSerializer(RLSSerializer):
|
||||
}
|
||||
|
||||
|
||||
class ProviderIncludeSerializer(RLSSerializer):
|
||||
"""
|
||||
Serializer for the Provider model.
|
||||
"""
|
||||
|
||||
provider = ProviderEnumSerializerField()
|
||||
connection = serializers.SerializerMethodField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Provider
|
||||
fields = [
|
||||
"id",
|
||||
"inserted_at",
|
||||
"updated_at",
|
||||
"provider",
|
||||
"uid",
|
||||
"alias",
|
||||
"connection",
|
||||
# "scanner_args",
|
||||
]
|
||||
|
||||
@extend_schema_field(
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"connected": {"type": "boolean"},
|
||||
"last_checked_at": {"type": "string", "format": "date-time"},
|
||||
},
|
||||
}
|
||||
)
|
||||
def get_connection(self, obj):
|
||||
return {
|
||||
"connected": obj.connected,
|
||||
"last_checked_at": obj.connection_last_checked_at,
|
||||
}
|
||||
|
||||
|
||||
class ProviderCreateSerializer(RLSSerializer, BaseWriteSerializer):
|
||||
class Meta:
|
||||
model = Provider
|
||||
@@ -844,35 +753,6 @@ class ScanSerializer(RLSSerializer):
|
||||
]
|
||||
|
||||
|
||||
class ScanIncludeSerializer(RLSSerializer):
|
||||
trigger = serializers.ChoiceField(
|
||||
choices=Scan.TriggerChoices.choices, read_only=True
|
||||
)
|
||||
state = StateEnumSerializerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Scan
|
||||
fields = [
|
||||
"id",
|
||||
"name",
|
||||
"trigger",
|
||||
"state",
|
||||
"unique_resource_count",
|
||||
"progress",
|
||||
# "scanner_args",
|
||||
"duration",
|
||||
"inserted_at",
|
||||
"started_at",
|
||||
"completed_at",
|
||||
"scheduled_at",
|
||||
"provider",
|
||||
]
|
||||
|
||||
included_serializers = {
|
||||
"provider": "api.v1.serializers.ProviderIncludeSerializer",
|
||||
}
|
||||
|
||||
|
||||
class ScanCreateSerializer(RLSSerializer, BaseWriteSerializer):
|
||||
class Meta:
|
||||
model = Scan
|
||||
@@ -1012,51 +892,6 @@ class ResourceSerializer(RLSSerializer):
|
||||
return fields
|
||||
|
||||
|
||||
class ResourceIncludeSerializer(RLSSerializer):
|
||||
"""
|
||||
Serializer for the Resource model.
|
||||
"""
|
||||
|
||||
tags = serializers.SerializerMethodField()
|
||||
type_ = serializers.CharField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Resource
|
||||
fields = [
|
||||
"id",
|
||||
"inserted_at",
|
||||
"updated_at",
|
||||
"uid",
|
||||
"name",
|
||||
"region",
|
||||
"service",
|
||||
"type_",
|
||||
"tags",
|
||||
]
|
||||
extra_kwargs = {
|
||||
"id": {"read_only": True},
|
||||
"inserted_at": {"read_only": True},
|
||||
"updated_at": {"read_only": True},
|
||||
}
|
||||
|
||||
@extend_schema_field(
|
||||
{
|
||||
"type": "object",
|
||||
"description": "Tags associated with the resource",
|
||||
"example": {"env": "prod", "owner": "johndoe"},
|
||||
}
|
||||
)
|
||||
def get_tags(self, obj):
|
||||
return obj.get_tags(self.context.get("tenant_id"))
|
||||
|
||||
def get_fields(self):
|
||||
"""`type` is a Python reserved keyword."""
|
||||
fields = super().get_fields()
|
||||
type_ = fields.pop("type_")
|
||||
fields["type"] = type_
|
||||
return fields
|
||||
|
||||
|
||||
class FindingSerializer(RLSSerializer):
|
||||
"""
|
||||
Serializer for the Finding model.
|
||||
@@ -1086,8 +921,8 @@ class FindingSerializer(RLSSerializer):
|
||||
]
|
||||
|
||||
included_serializers = {
|
||||
"scan": ScanIncludeSerializer,
|
||||
"resources": ResourceIncludeSerializer,
|
||||
"scan": ScanSerializer,
|
||||
"resources": ResourceSerializer,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -6,10 +6,7 @@ from api.v1.views import (
|
||||
ComplianceOverviewViewSet,
|
||||
CustomTokenObtainView,
|
||||
CustomTokenRefreshView,
|
||||
CustomTokenSwitchTenantView,
|
||||
FindingViewSet,
|
||||
GithubSocialLoginView,
|
||||
GoogleSocialLoginView,
|
||||
InvitationAcceptViewSet,
|
||||
InvitationViewSet,
|
||||
MembershipViewSet,
|
||||
@@ -59,7 +56,6 @@ users_router.register(r"memberships", MembershipViewSet, basename="user-membersh
|
||||
urlpatterns = [
|
||||
path("tokens", CustomTokenObtainView.as_view(), name="token-obtain"),
|
||||
path("tokens/refresh", CustomTokenRefreshView.as_view(), name="token-refresh"),
|
||||
path("tokens/switch", CustomTokenSwitchTenantView.as_view(), name="token-switch"),
|
||||
path(
|
||||
"providers/secrets",
|
||||
ProviderSecretViewSet.as_view({"get": "list", "post": "create"}),
|
||||
@@ -110,8 +106,6 @@ urlpatterns = [
|
||||
),
|
||||
name="provider_group-providers-relationship",
|
||||
),
|
||||
path("tokens/google", GoogleSocialLoginView.as_view(), name="token-google"),
|
||||
path("tokens/github", GithubSocialLoginView.as_view(), name="token-github"),
|
||||
path("", include(router.urls)),
|
||||
path("", include(tenants_router.urls)),
|
||||
path("", include(users_router.urls)),
|
||||
|
||||
+101
-232
@@ -1,28 +1,20 @@
|
||||
import glob
|
||||
import os
|
||||
|
||||
import sentry_sdk
|
||||
from allauth.socialaccount.providers.github.views import GitHubOAuth2Adapter
|
||||
from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter
|
||||
import boto3
|
||||
from botocore.exceptions import ClientError, NoCredentialsError, ParamValidationError
|
||||
from celery.result import AsyncResult
|
||||
from config.env import env
|
||||
from config.settings.social_login import (
|
||||
GITHUB_OAUTH_CALLBACK_URL,
|
||||
GOOGLE_OAUTH_CALLBACK_URL,
|
||||
)
|
||||
from dj_rest_auth.registration.views import SocialLoginView
|
||||
from django.conf import settings as django_settings
|
||||
from django.contrib.postgres.aggregates import ArrayAgg
|
||||
from django.contrib.postgres.search import SearchQuery
|
||||
from django.db import transaction
|
||||
from django.db.models import Count, Exists, F, OuterRef, Prefetch, Q, Subquery, Sum
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.db.models import Count, F, OuterRef, Prefetch, Q, Subquery, Sum
|
||||
from django.http import HttpResponse
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.urls import reverse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.cache import cache_control
|
||||
from django_celery_beat.models import PeriodicTask
|
||||
from drf_spectacular.settings import spectacular_settings
|
||||
from drf_spectacular.utils import (
|
||||
OpenApiParameter,
|
||||
@@ -46,7 +38,6 @@ from rest_framework.permissions import SAFE_METHODS
|
||||
from rest_framework_json_api.views import RelationshipView, Response
|
||||
from rest_framework_simplejwt.exceptions import InvalidToken, TokenError
|
||||
from tasks.beat import schedule_provider_scan
|
||||
from tasks.jobs.export import get_s3_client
|
||||
from tasks.tasks import (
|
||||
check_provider_connection_task,
|
||||
delete_provider_task,
|
||||
@@ -83,7 +74,6 @@ from api.models import (
|
||||
ProviderGroupMembership,
|
||||
ProviderSecret,
|
||||
Resource,
|
||||
ResourceFindingMapping,
|
||||
Role,
|
||||
RoleProviderGroupRelationship,
|
||||
Scan,
|
||||
@@ -97,7 +87,8 @@ from api.models import (
|
||||
from api.pagination import ComplianceOverviewPagination
|
||||
from api.rbac.permissions import Permissions, get_providers, get_role
|
||||
from api.rls import Tenant
|
||||
from api.utils import CustomOAuth2Client, validate_invitation
|
||||
from api.utils import validate_invitation
|
||||
from api.uuid_utils import datetime_to_uuid7
|
||||
from api.v1.serializers import (
|
||||
ComplianceOverviewFullSerializer,
|
||||
ComplianceOverviewSerializer,
|
||||
@@ -137,8 +128,6 @@ from api.v1.serializers import (
|
||||
TenantSerializer,
|
||||
TokenRefreshSerializer,
|
||||
TokenSerializer,
|
||||
TokenSocialLoginSerializer,
|
||||
TokenSwitchTenantSerializer,
|
||||
UserCreateSerializer,
|
||||
UserRoleRelationshipSerializer,
|
||||
UserSerializer,
|
||||
@@ -205,43 +194,13 @@ class CustomTokenRefreshView(GenericAPIView):
|
||||
)
|
||||
|
||||
|
||||
@extend_schema(
|
||||
tags=["Token"],
|
||||
summary="Switch tenant using a valid tenant ID",
|
||||
description="Switch tenant by providing a valid tenant ID. The authenticated user must belong to the tenant.",
|
||||
)
|
||||
class CustomTokenSwitchTenantView(GenericAPIView):
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
resource_name = "tokens-switch-tenant"
|
||||
serializer_class = TokenSwitchTenantSerializer
|
||||
http_method_names = ["post"]
|
||||
|
||||
def post(self, request):
|
||||
serializer = TokenSwitchTenantSerializer(
|
||||
data=request.data, context={"request": request}
|
||||
)
|
||||
|
||||
try:
|
||||
serializer.is_valid(raise_exception=True)
|
||||
except TokenError as e:
|
||||
raise InvalidToken(e.args[0])
|
||||
|
||||
return Response(
|
||||
data={
|
||||
"type": "tokens-switch-tenant",
|
||||
"attributes": serializer.validated_data,
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
|
||||
@extend_schema(exclude=True)
|
||||
class SchemaView(SpectacularAPIView):
|
||||
serializer_class = None
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
spectacular_settings.TITLE = "Prowler API"
|
||||
spectacular_settings.VERSION = "1.5.4"
|
||||
spectacular_settings.VERSION = "1.4.0"
|
||||
spectacular_settings.DESCRIPTION = (
|
||||
"Prowler API specification.\n\nThis file is auto-generated."
|
||||
)
|
||||
@@ -301,58 +260,6 @@ class SchemaView(SpectacularAPIView):
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
|
||||
@extend_schema(exclude=True)
|
||||
class GoogleSocialLoginView(SocialLoginView):
|
||||
adapter_class = GoogleOAuth2Adapter
|
||||
client_class = CustomOAuth2Client
|
||||
callback_url = GOOGLE_OAUTH_CALLBACK_URL
|
||||
|
||||
def get_response(self):
|
||||
original_response = super().get_response()
|
||||
|
||||
if self.user and self.user.is_authenticated:
|
||||
serializer = TokenSocialLoginSerializer(data={"email": self.user.email})
|
||||
try:
|
||||
serializer.is_valid(raise_exception=True)
|
||||
except TokenError as e:
|
||||
raise InvalidToken(e.args[0])
|
||||
return Response(
|
||||
data={
|
||||
"type": "google-social-tokens",
|
||||
"attributes": serializer.validated_data,
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
return original_response
|
||||
|
||||
|
||||
@extend_schema(exclude=True)
|
||||
class GithubSocialLoginView(SocialLoginView):
|
||||
adapter_class = GitHubOAuth2Adapter
|
||||
client_class = CustomOAuth2Client
|
||||
callback_url = GITHUB_OAUTH_CALLBACK_URL
|
||||
|
||||
def get_response(self):
|
||||
original_response = super().get_response()
|
||||
|
||||
if self.user and self.user.is_authenticated:
|
||||
serializer = TokenSocialLoginSerializer(data={"email": self.user.email})
|
||||
|
||||
try:
|
||||
serializer.is_valid(raise_exception=True)
|
||||
except TokenError as e:
|
||||
raise InvalidToken(e.args[0])
|
||||
|
||||
return Response(
|
||||
data={
|
||||
"type": "github-social-tokens",
|
||||
"attributes": serializer.validated_data,
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
return original_response
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
list=extend_schema(
|
||||
tags=["User"],
|
||||
@@ -1078,8 +985,6 @@ class ProviderViewSet(BaseRLSViewSet):
|
||||
provider = get_object_or_404(Provider, pk=pk)
|
||||
provider.is_deleted = True
|
||||
provider.save()
|
||||
task_name = f"scan-perform-scheduled-{pk}"
|
||||
PeriodicTask.objects.filter(name=task_name).update(enabled=False)
|
||||
|
||||
with transaction.atomic():
|
||||
task = delete_provider_task.delay(
|
||||
@@ -1127,18 +1032,6 @@ class ProviderViewSet(BaseRLSViewSet):
|
||||
request=ScanCreateSerializer,
|
||||
responses={202: OpenApiResponse(response=TaskSerializer)},
|
||||
),
|
||||
report=extend_schema(
|
||||
tags=["Scan"],
|
||||
summary="Download ZIP report",
|
||||
description="Returns a ZIP file containing the requested report",
|
||||
request=ScanReportSerializer,
|
||||
responses={
|
||||
200: OpenApiResponse(description="Report obtained successfully"),
|
||||
202: OpenApiResponse(description="The task is in progress"),
|
||||
403: OpenApiResponse(description="There is a problem with credentials"),
|
||||
404: OpenApiResponse(description="The scan has no reports"),
|
||||
},
|
||||
),
|
||||
)
|
||||
@method_decorator(CACHE_DECORATOR, name="list")
|
||||
@method_decorator(CACHE_DECORATOR, name="retrieve")
|
||||
@@ -1188,8 +1081,6 @@ class ScanViewSet(BaseRLSViewSet):
|
||||
elif self.action == "partial_update":
|
||||
return ScanUpdateSerializer
|
||||
elif self.action == "report":
|
||||
if hasattr(self, "response_serializer_class"):
|
||||
return self.response_serializer_class
|
||||
return ScanReportSerializer
|
||||
return super().get_serializer_class()
|
||||
|
||||
@@ -1208,93 +1099,81 @@ class ScanViewSet(BaseRLSViewSet):
|
||||
)
|
||||
return Response(data=read_serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@extend_schema(
|
||||
tags=["Scan"],
|
||||
summary="Download ZIP report",
|
||||
description="Returns a ZIP file containing the requested report",
|
||||
request=ScanReportSerializer,
|
||||
responses={
|
||||
200: OpenApiResponse(description="Report obtained successfully"),
|
||||
423: OpenApiResponse(
|
||||
description="There is a problem with the AWS credentials"
|
||||
),
|
||||
},
|
||||
)
|
||||
@action(detail=True, methods=["get"], url_name="report")
|
||||
def report(self, request, pk=None):
|
||||
scan_instance = self.get_object()
|
||||
scan_instance = Scan.objects.get(pk=pk)
|
||||
output_path = scan_instance.output_path
|
||||
|
||||
if scan_instance.state == StateChoices.EXECUTING:
|
||||
# If the scan is still running, return the task
|
||||
prowler_task = Task.objects.get(id=scan_instance.task.id)
|
||||
self.response_serializer_class = TaskSerializer
|
||||
output_serializer = self.get_serializer(prowler_task)
|
||||
if not output_path:
|
||||
return Response(
|
||||
data=output_serializer.data,
|
||||
status=status.HTTP_202_ACCEPTED,
|
||||
headers={
|
||||
"Content-Location": reverse(
|
||||
"task-detail", kwargs={"pk": output_serializer.data["id"]}
|
||||
)
|
||||
},
|
||||
{"detail": "No files found"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
|
||||
try:
|
||||
output_celery_task = Task.objects.get(
|
||||
task_runner_task__task_name="scan-report",
|
||||
task_runner_task__task_args__contains=pk,
|
||||
)
|
||||
self.response_serializer_class = TaskSerializer
|
||||
output_serializer = self.get_serializer(output_celery_task)
|
||||
if output_serializer.data["state"] == StateChoices.EXECUTING:
|
||||
# If the task is still running, return the task
|
||||
return Response(
|
||||
data=output_serializer.data,
|
||||
status=status.HTTP_202_ACCEPTED,
|
||||
headers={
|
||||
"Content-Location": reverse(
|
||||
"task-detail", kwargs={"pk": output_serializer.data["id"]}
|
||||
)
|
||||
},
|
||||
)
|
||||
except Task.DoesNotExist:
|
||||
# If the task does not exist, it means that the task is removed from the database
|
||||
pass
|
||||
|
||||
output_location = scan_instance.output_location
|
||||
if not output_location:
|
||||
return Response(
|
||||
{"detail": "The scan has no reports."},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
if scan_instance.output_location.startswith("s3://"):
|
||||
if scan_instance.upload_to_s3:
|
||||
s3_client = None
|
||||
try:
|
||||
s3_client = get_s3_client()
|
||||
s3_client = boto3.client(
|
||||
"s3",
|
||||
aws_access_key_id=env.str("DJANGO_ARTIFACTS_AWS_ACCESS_KEY_ID"),
|
||||
aws_secret_access_key=env.str(
|
||||
"DJANGO_ARTIFACTS_AWS_SECRET_ACCESS_KEY"
|
||||
),
|
||||
aws_session_token=env.str("DJANGO_ARTIFACTS_AWS_SESSION_TOKEN"),
|
||||
region_name=env.str("DJANGO_ARTIFACTS_AWS_DEFAULT_REGION"),
|
||||
)
|
||||
s3_client.list_buckets()
|
||||
except (ClientError, NoCredentialsError, ParamValidationError):
|
||||
try:
|
||||
s3_client = boto3.client("s3")
|
||||
s3_client.list_buckets()
|
||||
except (ClientError, NoCredentialsError, ParamValidationError):
|
||||
return Response(
|
||||
{"detail": "There is a problem with the AWS credentials."},
|
||||
status=status.HTTP_423_LOCKED,
|
||||
)
|
||||
|
||||
bucket_name = env.str("DJANGO_ARTIFACTS_AWS_S3_OUTPUT_BUCKET")
|
||||
|
||||
try:
|
||||
key = output_path[len(f"s3://{bucket_name}/") :]
|
||||
s3_object = s3_client.get_object(Bucket=bucket_name, Key=key)
|
||||
file_content = s3_object["Body"].read()
|
||||
filename = os.path.basename(output_path.split("/")[-1])
|
||||
except ClientError:
|
||||
return Response(
|
||||
{"detail": "There is a problem with credentials."},
|
||||
status=status.HTTP_403_FORBIDDEN,
|
||||
{"detail": "Error accessing cloud storage"},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
|
||||
bucket_name = env.str("DJANGO_OUTPUT_S3_AWS_OUTPUT_BUCKET")
|
||||
key = output_location[len(f"s3://{bucket_name}/") :]
|
||||
try:
|
||||
s3_object = s3_client.get_object(Bucket=bucket_name, Key=key)
|
||||
except ClientError as e:
|
||||
error_code = e.response.get("Error", {}).get("Code")
|
||||
if error_code == "NoSuchKey":
|
||||
return Response(
|
||||
{"detail": "The scan has no reports."},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
return Response(
|
||||
{"detail": "There is a problem with credentials."},
|
||||
status=status.HTTP_403_FORBIDDEN,
|
||||
)
|
||||
file_content = s3_object["Body"].read()
|
||||
filename = os.path.basename(output_location.split("/")[-1])
|
||||
else:
|
||||
zip_files = glob.glob(output_location)
|
||||
zip_files = glob.glob(output_path)
|
||||
if not zip_files:
|
||||
return Response(
|
||||
{"detail": "No local files found"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
|
||||
try:
|
||||
file_path = zip_files[0]
|
||||
except IndexError as e:
|
||||
sentry_sdk.capture_exception(e)
|
||||
with open(file_path, "rb") as f:
|
||||
file_content = f.read()
|
||||
filename = os.path.basename(file_path)
|
||||
except IOError:
|
||||
return Response(
|
||||
{"detail": "The scan has no reports."},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
{"detail": "Error reading local file"},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
with open(file_path, "rb") as f:
|
||||
file_content = f.read()
|
||||
filename = os.path.basename(file_path)
|
||||
|
||||
response = HttpResponse(
|
||||
file_content, content_type="application/x-zip-compressed"
|
||||
@@ -1518,10 +1397,17 @@ class ResourceViewSet(BaseRLSViewSet):
|
||||
@method_decorator(CACHE_DECORATOR, name="list")
|
||||
@method_decorator(CACHE_DECORATOR, name="retrieve")
|
||||
class FindingViewSet(BaseRLSViewSet):
|
||||
queryset = Finding.all_objects.all()
|
||||
queryset = Finding.objects.all()
|
||||
serializer_class = FindingSerializer
|
||||
filterset_class = FindingFilter
|
||||
prefetch_for_includes = {
|
||||
"__all__": [],
|
||||
"resources": [
|
||||
Prefetch("resources", queryset=Resource.objects.select_related("findings"))
|
||||
],
|
||||
"scan": [Prefetch("scan", queryset=Scan.objects.select_related("findings"))],
|
||||
}
|
||||
http_method_names = ["get"]
|
||||
filterset_class = FindingFilter
|
||||
ordering = ["-inserted_at"]
|
||||
ordering_fields = [
|
||||
"status",
|
||||
@@ -1530,18 +1416,6 @@ class FindingViewSet(BaseRLSViewSet):
|
||||
"inserted_at",
|
||||
"updated_at",
|
||||
]
|
||||
prefetch_for_includes = {
|
||||
"__all__": [],
|
||||
"resources": [
|
||||
Prefetch(
|
||||
"resources",
|
||||
queryset=Resource.all_objects.prefetch_related("tags", "findings"),
|
||||
)
|
||||
],
|
||||
"scan": [
|
||||
Prefetch("scan", queryset=Scan.all_objects.select_related("findings"))
|
||||
],
|
||||
}
|
||||
# RBAC required permissions (implicit -> MANAGE_PROVIDERS enable unlimited visibility or check the visibility of
|
||||
# the provider through the provider group)
|
||||
required_permissions = []
|
||||
@@ -1555,34 +1429,41 @@ class FindingViewSet(BaseRLSViewSet):
|
||||
return super().get_serializer_class()
|
||||
|
||||
def get_queryset(self):
|
||||
tenant_id = self.request.tenant_id
|
||||
user_roles = get_role(self.request.user)
|
||||
if user_roles.unlimited_visibility:
|
||||
# User has unlimited visibility, return all findings
|
||||
queryset = Finding.all_objects.filter(tenant_id=tenant_id)
|
||||
# User has unlimited visibility, return all scans
|
||||
queryset = Finding.objects.filter(tenant_id=self.request.tenant_id)
|
||||
else:
|
||||
# User lacks permission, filter findings based on provider groups associated with the role
|
||||
queryset = Finding.all_objects.filter(
|
||||
# User lacks permission, filter providers based on provider groups associated with the role
|
||||
queryset = Finding.objects.filter(
|
||||
scan__provider__in=get_providers(user_roles)
|
||||
)
|
||||
|
||||
search_value = self.request.query_params.get("filter[search]", None)
|
||||
if search_value:
|
||||
# Django's ORM will build a LEFT JOIN and OUTER JOIN on any "through" tables, resulting in duplicates
|
||||
# The duplicates then require a `distinct` query
|
||||
search_query = SearchQuery(
|
||||
search_value, config="simple", search_type="plain"
|
||||
)
|
||||
|
||||
resource_match = Resource.all_objects.filter(
|
||||
text_search=search_query,
|
||||
id__in=ResourceFindingMapping.objects.filter(
|
||||
resource_id=OuterRef("pk"),
|
||||
tenant_id=tenant_id,
|
||||
).values("resource_id"),
|
||||
)
|
||||
|
||||
queryset = queryset.filter(
|
||||
Q(text_search=search_query) | Q(Exists(resource_match))
|
||||
)
|
||||
Q(impact_extended__contains=search_value)
|
||||
| Q(status_extended__contains=search_value)
|
||||
| Q(check_id=search_value)
|
||||
| Q(check_id__icontains=search_value)
|
||||
| Q(text_search=search_query)
|
||||
| Q(resources__uid=search_value)
|
||||
| Q(resources__name=search_value)
|
||||
| Q(resources__region=search_value)
|
||||
| Q(resources__service=search_value)
|
||||
| Q(resources__type=search_value)
|
||||
| Q(resources__uid__contains=search_value)
|
||||
| Q(resources__name__contains=search_value)
|
||||
| Q(resources__region__contains=search_value)
|
||||
| Q(resources__service__contains=search_value)
|
||||
| Q(resources__tags__text_search=search_query)
|
||||
| Q(resources__text_search=search_query)
|
||||
).distinct()
|
||||
|
||||
return queryset
|
||||
|
||||
@@ -1592,22 +1473,10 @@ class FindingViewSet(BaseRLSViewSet):
|
||||
return queryset
|
||||
return super().filter_queryset(queryset)
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
base_qs = self.filter_queryset(self.get_queryset())
|
||||
paginated_ids = self.paginate_queryset(base_qs.values_list("id", flat=True))
|
||||
if paginated_ids is not None:
|
||||
ids = list(paginated_ids)
|
||||
findings = (
|
||||
Finding.all_objects.filter(tenant_id=self.request.tenant_id, id__in=ids)
|
||||
.select_related("scan")
|
||||
.prefetch_related("resources")
|
||||
)
|
||||
# Re-sort in Python to preserve ordering:
|
||||
findings = sorted(findings, key=lambda x: ids.index(x.id))
|
||||
serializer = self.get_serializer(findings, many=True)
|
||||
return self.get_paginated_response(serializer.data)
|
||||
serializer = self.get_serializer(base_qs, many=True)
|
||||
return Response(serializer.data)
|
||||
def inserted_at_to_uuidv7(self, inserted_at):
|
||||
if inserted_at is None:
|
||||
return None
|
||||
return datetime_to_uuid7(inserted_at)
|
||||
|
||||
@action(detail=False, methods=["get"], url_name="findings_services_regions")
|
||||
def findings_services_regions(self, request):
|
||||
|
||||
@@ -50,9 +50,9 @@ class RLSTask(Task):
|
||||
|
||||
tenant_id = kwargs.get("tenant_id")
|
||||
with rls_transaction(tenant_id):
|
||||
APITask.objects.update_or_create(
|
||||
APITask.objects.create(
|
||||
id=task_result_instance.task_id,
|
||||
tenant_id=tenant_id,
|
||||
defaults={"task_runner_task": task_result_instance},
|
||||
task_runner_task=task_result_instance,
|
||||
)
|
||||
return result
|
||||
|
||||
@@ -4,8 +4,6 @@ from config.custom_logging import LOGGING # noqa
|
||||
from config.env import BASE_DIR, env # noqa
|
||||
from config.settings.celery import * # noqa
|
||||
from config.settings.partitions import * # noqa
|
||||
from config.settings.sentry import * # noqa
|
||||
from config.settings.social_login import * # noqa
|
||||
|
||||
SECRET_KEY = env("SECRET_KEY", default="secret")
|
||||
DEBUG = env.bool("DJANGO_DEBUG", default=False)
|
||||
@@ -31,13 +29,6 @@ INSTALLED_APPS = [
|
||||
"django_celery_results",
|
||||
"django_celery_beat",
|
||||
"rest_framework_simplejwt.token_blacklist",
|
||||
"allauth",
|
||||
"allauth.account",
|
||||
"allauth.socialaccount",
|
||||
"allauth.socialaccount.providers.google",
|
||||
"allauth.socialaccount.providers.github",
|
||||
"dj_rest_auth.registration",
|
||||
"rest_framework.authtoken",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
@@ -51,11 +42,8 @@ MIDDLEWARE = [
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
"api.middleware.APILoggingMiddleware",
|
||||
"allauth.account.middleware.AccountMiddleware",
|
||||
]
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
CORS_ALLOWED_ORIGINS = ["http://localhost", "http://127.0.0.1"]
|
||||
|
||||
ROOT_URLCONF = "config.urls"
|
||||
@@ -219,22 +207,4 @@ CACHE_STALE_WHILE_REVALIDATE = env.int("DJANGO_STALE_WHILE_REVALIDATE", 60)
|
||||
|
||||
|
||||
TESTING = False
|
||||
|
||||
FINDINGS_MAX_DAYS_IN_RANGE = env.int("DJANGO_FINDINGS_MAX_DAYS_IN_RANGE", 7)
|
||||
|
||||
|
||||
# API export settings
|
||||
DJANGO_TMP_OUTPUT_DIRECTORY = env.str(
|
||||
"DJANGO_TMP_OUTPUT_DIRECTORY", "/tmp/prowler_api_output"
|
||||
)
|
||||
DJANGO_FINDINGS_BATCH_SIZE = env.str("DJANGO_FINDINGS_BATCH_SIZE", 1000)
|
||||
|
||||
DJANGO_OUTPUT_S3_AWS_OUTPUT_BUCKET = env.str("DJANGO_OUTPUT_S3_AWS_OUTPUT_BUCKET", "")
|
||||
DJANGO_OUTPUT_S3_AWS_ACCESS_KEY_ID = env.str("DJANGO_OUTPUT_S3_AWS_ACCESS_KEY_ID", "")
|
||||
DJANGO_OUTPUT_S3_AWS_SECRET_ACCESS_KEY = env.str(
|
||||
"DJANGO_OUTPUT_S3_AWS_SECRET_ACCESS_KEY", ""
|
||||
)
|
||||
DJANGO_OUTPUT_S3_AWS_SESSION_TOKEN = env.str("DJANGO_OUTPUT_S3_AWS_SESSION_TOKEN", "")
|
||||
DJANGO_OUTPUT_S3_AWS_DEFAULT_REGION = env.str("DJANGO_OUTPUT_S3_AWS_DEFAULT_REGION", "")
|
||||
|
||||
DJANGO_DELETION_BATCH_SIZE = env.int("DJANGO_DELETION_BATCH_SIZE", 5000)
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
import sentry_sdk
|
||||
from config.env import env
|
||||
|
||||
IGNORED_EXCEPTIONS = [
|
||||
# Provider is not connected due to credentials errors
|
||||
"is not connected",
|
||||
# Authentication Errors from AWS
|
||||
"InvalidToken",
|
||||
"AccessDeniedException",
|
||||
"AuthorizationErrorException",
|
||||
"UnrecognizedClientException",
|
||||
"UnauthorizedOperation",
|
||||
"AuthFailure",
|
||||
"InvalidClientTokenId",
|
||||
"AccessDenied",
|
||||
"No Shodan API Key", # Shodan Check
|
||||
"RequestLimitExceeded", # For now we don't want to log the RequestLimitExceeded errors
|
||||
"ThrottlingException",
|
||||
"Rate exceeded",
|
||||
"SubscriptionRequiredException",
|
||||
"UnknownOperationException",
|
||||
"OptInRequired",
|
||||
"ReadTimeout",
|
||||
"LimitExceeded",
|
||||
"ConnectTimeoutError",
|
||||
"ExpiredToken",
|
||||
"IncompleteSignature",
|
||||
"RegionDisabledException",
|
||||
"TooManyRequestsException",
|
||||
"SignatureDoesNotMatch",
|
||||
"InvalidParameterValueException",
|
||||
"InvalidInputException",
|
||||
"ValidationException",
|
||||
"AWSSecretAccessKeyInvalidError",
|
||||
"InvalidAction",
|
||||
"InvalidRequestException",
|
||||
"RequestExpired",
|
||||
"ConnectionClosedError",
|
||||
"HTTPSConnectionPool",
|
||||
"Pool is closed", # The following comes from urllib3: eu-west-1 -- HTTPClientError[126]: An HTTP Client raised an unhandled exception: AWSHTTPSConnectionPool(host='hostname.s3.eu-west-1.amazonaws.com', port=443): Pool is closed.
|
||||
# Authentication Errors from GCP
|
||||
"ClientAuthenticationError",
|
||||
"AuthorizationFailed",
|
||||
"Reauthentication is needed",
|
||||
"Permission denied to get service",
|
||||
"API has not been used in project",
|
||||
"HttpError 404 when requesting",
|
||||
"HttpError 403 when requesting",
|
||||
"HttpError 400 when requesting",
|
||||
"GCPNoAccesibleProjectsError",
|
||||
# Authentication Errors from Azure
|
||||
"ClientAuthenticationError",
|
||||
"AuthorizationFailed",
|
||||
"Subscription Not Registered",
|
||||
"AzureNotValidClientIdError",
|
||||
"AzureNotValidClientSecretError",
|
||||
"AzureNotValidTenantIdError",
|
||||
"AzureTenantIdAndClientSecretNotBelongingToClientIdError",
|
||||
"AzureTenantIdAndClientIdNotBelongingToClientSecretError",
|
||||
"AzureClientIdAndClientSecretNotBelongingToTenantIdError",
|
||||
"AzureHTTPResponseError",
|
||||
"Error with credentials provided",
|
||||
# AWS Service is not available in a region
|
||||
"EndpointConnectionError",
|
||||
]
|
||||
|
||||
|
||||
def before_send(event, hint):
|
||||
"""
|
||||
before_send handles the Sentry events in order to sent them or not
|
||||
"""
|
||||
# Ignore logs with the ignored_exceptions
|
||||
# https://docs.python.org/3/library/logging.html#logrecord-objects
|
||||
if "log_record" in hint:
|
||||
log_msg = hint["log_record"].msg
|
||||
log_lvl = hint["log_record"].levelno
|
||||
|
||||
# Handle Error events and discard the rest
|
||||
if log_lvl == 40 and any(ignored in log_msg for ignored in IGNORED_EXCEPTIONS):
|
||||
return
|
||||
return event
|
||||
|
||||
|
||||
sentry_sdk.init(
|
||||
dsn=env.str("DJANGO_SENTRY_DSN", ""),
|
||||
# Add data like request headers and IP for users,
|
||||
# see https://docs.sentry.io/platforms/python/data-management/data-collected/ for more info
|
||||
before_send=before_send,
|
||||
send_default_pii=True,
|
||||
_experiments={
|
||||
# Set continuous_profiling_auto_start to True
|
||||
# to automatically start the profiler on when
|
||||
# possible.
|
||||
"continuous_profiling_auto_start": True,
|
||||
},
|
||||
)
|
||||
@@ -1,53 +0,0 @@
|
||||
from config.env import env
|
||||
|
||||
# Google Oauth settings
|
||||
GOOGLE_OAUTH_CLIENT_ID = env("DJANGO_GOOGLE_OAUTH_CLIENT_ID", default="")
|
||||
GOOGLE_OAUTH_CLIENT_SECRET = env("DJANGO_GOOGLE_OAUTH_CLIENT_SECRET", default="")
|
||||
GOOGLE_OAUTH_CALLBACK_URL = env("DJANGO_GOOGLE_OAUTH_CALLBACK_URL", default="")
|
||||
|
||||
GITHUB_OAUTH_CLIENT_ID = env("DJANGO_GITHUB_OAUTH_CLIENT_ID", default="")
|
||||
GITHUB_OAUTH_CLIENT_SECRET = env("DJANGO_GITHUB_OAUTH_CLIENT_SECRET", default="")
|
||||
GITHUB_OAUTH_CALLBACK_URL = env("DJANGO_GITHUB_OAUTH_CALLBACK_URL", default="")
|
||||
|
||||
# Allauth settings
|
||||
ACCOUNT_LOGIN_METHODS = {"email"} # Use Email / Password authentication
|
||||
ACCOUNT_USERNAME_REQUIRED = False
|
||||
ACCOUNT_EMAIL_REQUIRED = True
|
||||
ACCOUNT_EMAIL_VERIFICATION = "none" # Do not require email confirmation
|
||||
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
|
||||
REST_AUTH = {
|
||||
"TOKEN_MODEL": None,
|
||||
"REST_USE_JWT": True,
|
||||
}
|
||||
# django-allauth (social)
|
||||
# Authenticate if local account with this email address already exists
|
||||
SOCIALACCOUNT_EMAIL_AUTHENTICATION = True
|
||||
# Connect local account and social account if local account with that email address already exists
|
||||
SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT = True
|
||||
SOCIALACCOUNT_ADAPTER = "api.adapters.ProwlerSocialAccountAdapter"
|
||||
SOCIALACCOUNT_PROVIDERS = {
|
||||
"google": {
|
||||
"APP": {
|
||||
"client_id": GOOGLE_OAUTH_CLIENT_ID,
|
||||
"secret": GOOGLE_OAUTH_CLIENT_SECRET,
|
||||
"key": "",
|
||||
},
|
||||
"SCOPE": [
|
||||
"email",
|
||||
"profile",
|
||||
],
|
||||
"AUTH_PARAMS": {
|
||||
"access_type": "online",
|
||||
},
|
||||
},
|
||||
"github": {
|
||||
"APP": {
|
||||
"client_id": GITHUB_OAUTH_CLIENT_ID,
|
||||
"secret": GITHUB_OAUTH_CLIENT_SECRET,
|
||||
},
|
||||
"SCOPE": [
|
||||
"user",
|
||||
"read:org",
|
||||
],
|
||||
},
|
||||
}
|
||||
@@ -486,7 +486,7 @@ def scans_fixture(tenants_fixture, providers_fixture):
|
||||
name="Scan 1",
|
||||
provider=provider,
|
||||
trigger=Scan.TriggerChoices.MANUAL,
|
||||
state=StateChoices.COMPLETED,
|
||||
state=StateChoices.AVAILABLE,
|
||||
tenant_id=tenant.id,
|
||||
started_at="2024-01-02T00:00:00Z",
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from celery.utils.log import get_task_logger
|
||||
from django.db import DatabaseError
|
||||
from django.db import transaction
|
||||
|
||||
from api.db_router import MainRouter
|
||||
from api.db_utils import batch_delete, rls_transaction
|
||||
@@ -8,12 +8,11 @@ from api.models import Finding, Provider, Resource, Scan, ScanSummary, Tenant
|
||||
logger = get_task_logger(__name__)
|
||||
|
||||
|
||||
def delete_provider(tenant_id: str, pk: str):
|
||||
def delete_provider(pk: str):
|
||||
"""
|
||||
Gracefully deletes an instance of a provider along with its related data.
|
||||
|
||||
Args:
|
||||
tenant_id (str): Tenant ID the resources belong to.
|
||||
pk (str): The primary key of the Provider instance to delete.
|
||||
|
||||
Returns:
|
||||
@@ -23,31 +22,33 @@ def delete_provider(tenant_id: str, pk: str):
|
||||
Raises:
|
||||
Provider.DoesNotExist: If no instance with the provided primary key exists.
|
||||
"""
|
||||
with rls_transaction(tenant_id):
|
||||
instance = Provider.all_objects.get(pk=pk)
|
||||
deletion_summary = {}
|
||||
deletion_steps = [
|
||||
("Scan Summaries", ScanSummary.all_objects.filter(scan__provider=instance)),
|
||||
("Findings", Finding.all_objects.filter(scan__provider=instance)),
|
||||
("Resources", Resource.all_objects.filter(provider=instance)),
|
||||
("Scans", Scan.all_objects.filter(provider=instance)),
|
||||
]
|
||||
instance = Provider.all_objects.get(pk=pk)
|
||||
deletion_summary = {}
|
||||
|
||||
for step_name, queryset in deletion_steps:
|
||||
try:
|
||||
_, step_summary = batch_delete(tenant_id, queryset)
|
||||
deletion_summary.update(step_summary)
|
||||
except DatabaseError as db_error:
|
||||
logger.error(f"Error deleting {step_name}: {db_error}")
|
||||
raise
|
||||
with transaction.atomic():
|
||||
# Delete Scan Summaries
|
||||
scan_summaries_qs = ScanSummary.all_objects.filter(scan__provider=instance)
|
||||
_, scans_summ_summary = batch_delete(scan_summaries_qs)
|
||||
deletion_summary.update(scans_summ_summary)
|
||||
|
||||
try:
|
||||
with rls_transaction(tenant_id):
|
||||
_, provider_summary = instance.delete()
|
||||
# Delete Findings
|
||||
findings_qs = Finding.all_objects.filter(scan__provider=instance)
|
||||
_, findings_summary = batch_delete(findings_qs)
|
||||
deletion_summary.update(findings_summary)
|
||||
|
||||
# Delete Resources
|
||||
resources_qs = Resource.all_objects.filter(provider=instance)
|
||||
_, resources_summary = batch_delete(resources_qs)
|
||||
deletion_summary.update(resources_summary)
|
||||
|
||||
# Delete Scans
|
||||
scans_qs = Scan.all_objects.filter(provider=instance)
|
||||
_, scans_summary = batch_delete(scans_qs)
|
||||
deletion_summary.update(scans_summary)
|
||||
|
||||
provider_deleted_count, provider_summary = instance.delete()
|
||||
deletion_summary.update(provider_summary)
|
||||
except DatabaseError as db_error:
|
||||
logger.error(f"Error deleting Provider: {db_error}")
|
||||
raise
|
||||
|
||||
return deletion_summary
|
||||
|
||||
|
||||
@@ -65,8 +66,9 @@ def delete_tenant(pk: str):
|
||||
deletion_summary = {}
|
||||
|
||||
for provider in Provider.objects.using(MainRouter.admin_db).filter(tenant_id=pk):
|
||||
summary = delete_provider(pk, provider.id)
|
||||
deletion_summary.update(summary)
|
||||
with rls_transaction(pk):
|
||||
summary = delete_provider(provider.id)
|
||||
deletion_summary.update(summary)
|
||||
|
||||
Tenant.objects.using(MainRouter.admin_db).filter(id=pk).delete()
|
||||
|
||||
|
||||
@@ -2,11 +2,9 @@ import os
|
||||
import zipfile
|
||||
|
||||
import boto3
|
||||
import config.django.base as base
|
||||
from botocore.exceptions import ClientError, NoCredentialsError, ParamValidationError
|
||||
from botocore.exceptions import ClientError
|
||||
from celery.utils.log import get_task_logger
|
||||
from django.conf import settings
|
||||
|
||||
from config.env import env
|
||||
from prowler.config.config import (
|
||||
csv_file_suffix,
|
||||
html_file_suffix,
|
||||
@@ -18,6 +16,7 @@ from prowler.lib.outputs.html.html import HTML
|
||||
from prowler.lib.outputs.ocsf.ocsf import OCSF
|
||||
|
||||
logger = get_task_logger(__name__)
|
||||
tmp_output_directory = "/tmp/prowler_api_output"
|
||||
|
||||
|
||||
# Predefined mapping for output formats and their configurations
|
||||
@@ -48,45 +47,12 @@ def _compress_output_files(output_directory: str) -> str:
|
||||
for suffix in [config["suffix"] for config in OUTPUT_FORMATS_MAPPING.values()]:
|
||||
zipf.write(
|
||||
f"{output_directory}{suffix}",
|
||||
f"output/{output_directory.split('/')[-1]}{suffix}",
|
||||
f"artifacts/{output_directory.split('/')[-1]}{suffix}",
|
||||
)
|
||||
|
||||
return zip_path
|
||||
|
||||
|
||||
def get_s3_client():
|
||||
"""
|
||||
Create and return a boto3 S3 client using AWS credentials from environment variables.
|
||||
|
||||
This function attempts to initialize an S3 client by reading the AWS access key, secret key,
|
||||
session token, and region from environment variables. It then validates the client by listing
|
||||
available S3 buckets. If an error occurs during this process (for example, due to missing or
|
||||
invalid credentials), it falls back to creating an S3 client without explicitly provided credentials,
|
||||
which may rely on other configuration sources (e.g., IAM roles).
|
||||
|
||||
Returns:
|
||||
boto3.client: A configured S3 client instance.
|
||||
|
||||
Raises:
|
||||
ClientError, NoCredentialsError, or ParamValidationError if both attempts to create a client fail.
|
||||
"""
|
||||
s3_client = None
|
||||
try:
|
||||
s3_client = boto3.client(
|
||||
"s3",
|
||||
aws_access_key_id=settings.DJANGO_OUTPUT_S3_AWS_ACCESS_KEY_ID,
|
||||
aws_secret_access_key=settings.DJANGO_OUTPUT_S3_AWS_SECRET_ACCESS_KEY,
|
||||
aws_session_token=settings.DJANGO_OUTPUT_S3_AWS_SESSION_TOKEN,
|
||||
region_name=settings.DJANGO_OUTPUT_S3_AWS_DEFAULT_REGION,
|
||||
)
|
||||
s3_client.list_buckets()
|
||||
except (ClientError, NoCredentialsError, ParamValidationError, ValueError):
|
||||
s3_client = boto3.client("s3")
|
||||
s3_client.list_buckets()
|
||||
|
||||
return s3_client
|
||||
|
||||
|
||||
def _upload_to_s3(tenant_id: str, zip_path: str, scan_id: str) -> str:
|
||||
"""
|
||||
Upload the specified ZIP file to an S3 bucket.
|
||||
@@ -102,24 +68,35 @@ def _upload_to_s3(tenant_id: str, zip_path: str, scan_id: str) -> str:
|
||||
Raises:
|
||||
botocore.exceptions.ClientError: If the upload attempt to S3 fails for any reason.
|
||||
"""
|
||||
if not base.DJANGO_OUTPUT_S3_AWS_OUTPUT_BUCKET:
|
||||
if not env.str("DJANGO_ARTIFACTS_AWS_S3_OUTPUT_BUCKET", ""):
|
||||
return
|
||||
|
||||
if env.str("DJANGO_ARTIFACTS_AWS_ACCESS_KEY_ID", ""):
|
||||
s3 = boto3.client(
|
||||
"s3",
|
||||
aws_access_key_id=env.str("DJANGO_ARTIFACTS_AWS_ACCESS_KEY_ID"),
|
||||
aws_secret_access_key=env.str("DJANGO_ARTIFACTS_AWS_SECRET_ACCESS_KEY"),
|
||||
aws_session_token=env.str("DJANGO_ARTIFACTS_AWS_SESSION_TOKEN"),
|
||||
region_name=env.str("DJANGO_ARTIFACTS_AWS_DEFAULT_REGION"),
|
||||
)
|
||||
else:
|
||||
s3 = boto3.client("s3")
|
||||
|
||||
s3_key = f"{tenant_id}/{scan_id}/{os.path.basename(zip_path)}"
|
||||
try:
|
||||
s3 = get_s3_client()
|
||||
s3_key = f"{tenant_id}/{scan_id}/{os.path.basename(zip_path)}"
|
||||
s3.upload_file(
|
||||
Filename=zip_path,
|
||||
Bucket=base.DJANGO_OUTPUT_S3_AWS_OUTPUT_BUCKET,
|
||||
Bucket=env.str("DJANGO_ARTIFACTS_AWS_S3_OUTPUT_BUCKET"),
|
||||
Key=s3_key,
|
||||
)
|
||||
return f"s3://{base.DJANGO_OUTPUT_S3_AWS_OUTPUT_BUCKET}/{s3_key}"
|
||||
except (ClientError, NoCredentialsError, ParamValidationError, ValueError) as e:
|
||||
return f"s3://{env.str('DJANGO_ARTIFACTS_AWS_S3_OUTPUT_BUCKET')}/{s3_key}"
|
||||
except ClientError as e:
|
||||
logger.error(f"S3 upload failed: {str(e)}")
|
||||
raise e
|
||||
|
||||
|
||||
def _generate_output_directory(
|
||||
output_directory, prowler_provider: object, tenant_id: str, scan_id: str
|
||||
prowler_provider: object, tenant_id: str, scan_id: str
|
||||
) -> str:
|
||||
"""
|
||||
Generate a file system path for the output directory of a prowler scan.
|
||||
@@ -130,11 +107,11 @@ def _generate_output_directory(
|
||||
store the output files of a prowler scan.
|
||||
|
||||
Note:
|
||||
This function depends on one external variable:
|
||||
This function depends on two external variables:
|
||||
- `tmp_output_directory`: The base directory where temporary outputs are stored.
|
||||
- `output_file_timestamp`: A timestamp (as a string) used to uniquely identify the output.
|
||||
|
||||
Args:
|
||||
output_directory (str): The base output directory.
|
||||
prowler_provider (object): An identifier or descriptor for the prowler provider.
|
||||
Typically, this is a string indicating the provider (e.g., "aws").
|
||||
tenant_id (str): The unique identifier for the tenant.
|
||||
@@ -144,13 +121,10 @@ def _generate_output_directory(
|
||||
str: The constructed file system path for the prowler scan output directory.
|
||||
|
||||
Example:
|
||||
>>> _generate_output_directory("/tmp", "aws", "tenant-1234", "scan-5678")
|
||||
'/tmp/tenant-1234/aws/scan-5678/prowler-output-2023-02-15T12:34:56'
|
||||
>>> _generate_output_directory("aws", "tenant-1234", "scan-5678")
|
||||
'/tmp/tenant-1234/scan-5678/prowler-output-aws-2023-02-15T12:34:56'
|
||||
"""
|
||||
path = (
|
||||
f"{output_directory}/{tenant_id}/{scan_id}/prowler-output-"
|
||||
return (
|
||||
f"{tmp_output_directory}/{tenant_id}/{scan_id}/prowler-output-"
|
||||
f"{prowler_provider}-{output_file_timestamp}"
|
||||
)
|
||||
os.makedirs("/".join(path.split("/")[:-1]), exist_ok=True)
|
||||
|
||||
return path
|
||||
|
||||
@@ -344,18 +344,9 @@ def perform_prowler_scan(
|
||||
total_requirements=compliance["total_requirements"],
|
||||
)
|
||||
)
|
||||
try:
|
||||
with rls_transaction(tenant_id):
|
||||
ComplianceOverview.objects.bulk_create(
|
||||
compliance_overview_objects, batch_size=100
|
||||
)
|
||||
except Exception as overview_exception:
|
||||
import sentry_sdk
|
||||
with rls_transaction(tenant_id):
|
||||
ComplianceOverview.objects.bulk_create(compliance_overview_objects)
|
||||
|
||||
sentry_sdk.capture_exception(overview_exception)
|
||||
logger.error(
|
||||
f"Error storing compliance overview for scan {scan_id}: {overview_exception}"
|
||||
)
|
||||
if exception is not None:
|
||||
raise exception
|
||||
|
||||
|
||||
+56
-102
@@ -1,11 +1,8 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from shutil import rmtree
|
||||
import os
|
||||
|
||||
from celery import chain, shared_task
|
||||
from celery.utils.log import get_task_logger
|
||||
from config.celery import RLSTask
|
||||
from config.django.base import DJANGO_FINDINGS_BATCH_SIZE, DJANGO_TMP_OUTPUT_DIRECTORY
|
||||
from api.utils import initialize_prowler_provider
|
||||
from django_celery_beat.models import PeriodicTask
|
||||
from tasks.jobs.connection import check_provider_connection
|
||||
from tasks.jobs.deletion import delete_provider, delete_tenant
|
||||
@@ -16,17 +13,13 @@ from tasks.jobs.export import (
|
||||
_upload_to_s3,
|
||||
)
|
||||
from tasks.jobs.scan import aggregate_findings, perform_prowler_scan
|
||||
from tasks.utils import batched, get_next_execution_datetime
|
||||
from tasks.utils import get_next_execution_datetime
|
||||
|
||||
from api.db_utils import rls_transaction
|
||||
from api.decorators import set_tenant
|
||||
from api.models import Finding, Provider, Scan, ScanSummary, StateChoices
|
||||
from api.utils import initialize_prowler_provider
|
||||
from api.v1.serializers import ScanTaskSerializer
|
||||
from prowler.lib.outputs.finding import Finding as FindingOutput
|
||||
|
||||
logger = get_task_logger(__name__)
|
||||
|
||||
|
||||
@shared_task(base=RLSTask, name="provider-connection-check")
|
||||
@set_tenant
|
||||
@@ -45,10 +38,9 @@ def check_provider_connection_task(provider_id: str):
|
||||
return check_provider_connection(provider_id=provider_id)
|
||||
|
||||
|
||||
@shared_task(
|
||||
base=RLSTask, name="provider-deletion", queue="deletion", autoretry_for=(Exception,)
|
||||
)
|
||||
def delete_provider_task(provider_id: str, tenant_id: str):
|
||||
@shared_task(base=RLSTask, name="provider-deletion")
|
||||
@set_tenant
|
||||
def delete_provider_task(provider_id: str):
|
||||
"""
|
||||
Task to delete a specific Provider instance.
|
||||
|
||||
@@ -56,7 +48,6 @@ def delete_provider_task(provider_id: str, tenant_id: str):
|
||||
|
||||
Args:
|
||||
provider_id (str): The primary key of the `Provider` instance to be deleted.
|
||||
tenant_id (str): Tenant ID the provider belongs to.
|
||||
|
||||
Returns:
|
||||
tuple: A tuple containing:
|
||||
@@ -64,7 +55,7 @@ def delete_provider_task(provider_id: str, tenant_id: str):
|
||||
- A dictionary with the count of deleted instances per model,
|
||||
including related models if cascading deletes were triggered.
|
||||
"""
|
||||
return delete_provider(tenant_id=tenant_id, pk=provider_id)
|
||||
return delete_provider(pk=provider_id)
|
||||
|
||||
|
||||
@shared_task(base=RLSTask, name="scan-perform", queue="scans")
|
||||
@@ -95,10 +86,8 @@ def perform_scan_task(
|
||||
)
|
||||
|
||||
chain(
|
||||
perform_scan_summary_task.si(tenant_id, scan_id),
|
||||
generate_outputs.si(
|
||||
scan_id=scan_id, provider_id=provider_id, tenant_id=tenant_id
|
||||
),
|
||||
perform_scan_summary_task.s(tenant_id, scan_id),
|
||||
generate_outputs.si(scan_id, provider_id, tenant_id=tenant_id),
|
||||
).apply_async()
|
||||
|
||||
return result
|
||||
@@ -130,43 +119,6 @@ def perform_scheduled_scan_task(self, tenant_id: str, provider_id: str):
|
||||
periodic_task_instance = PeriodicTask.objects.get(
|
||||
name=f"scan-perform-scheduled-{provider_id}"
|
||||
)
|
||||
|
||||
executed_scan = Scan.objects.filter(
|
||||
tenant_id=tenant_id,
|
||||
provider_id=provider_id,
|
||||
task__task_runner_task__task_id=task_id,
|
||||
).order_by("completed_at")
|
||||
|
||||
if (
|
||||
Scan.objects.filter(
|
||||
tenant_id=tenant_id,
|
||||
provider_id=provider_id,
|
||||
trigger=Scan.TriggerChoices.SCHEDULED,
|
||||
state=StateChoices.EXECUTING,
|
||||
scheduler_task_id=periodic_task_instance.id,
|
||||
scheduled_at__date=datetime.now(timezone.utc).date(),
|
||||
).exists()
|
||||
or executed_scan.exists()
|
||||
):
|
||||
# Duplicated task execution due to visibility timeout or scan is already running
|
||||
logger.warning(f"Duplicated scheduled scan for provider {provider_id}.")
|
||||
try:
|
||||
affected_scan = executed_scan.first()
|
||||
if not affected_scan:
|
||||
raise ValueError(
|
||||
"Error retrieving affected scan details after detecting duplicated scheduled "
|
||||
"scan."
|
||||
)
|
||||
# Return the affected scan details to avoid losing data
|
||||
serializer = ScanTaskSerializer(instance=affected_scan)
|
||||
except Exception as duplicated_scan_exception:
|
||||
logger.error(
|
||||
f"Duplicated scheduled scan for provider {provider_id}. Error retrieving affected scan details: "
|
||||
f"{str(duplicated_scan_exception)}"
|
||||
)
|
||||
raise duplicated_scan_exception
|
||||
return serializer.data
|
||||
|
||||
next_scan_datetime = get_next_execution_datetime(task_id, provider_id)
|
||||
scan_instance, _ = Scan.objects.get_or_create(
|
||||
tenant_id=tenant_id,
|
||||
@@ -174,11 +126,7 @@ def perform_scheduled_scan_task(self, tenant_id: str, provider_id: str):
|
||||
trigger=Scan.TriggerChoices.SCHEDULED,
|
||||
state__in=(StateChoices.SCHEDULED, StateChoices.AVAILABLE),
|
||||
scheduler_task_id=periodic_task_instance.id,
|
||||
defaults={
|
||||
"state": StateChoices.SCHEDULED,
|
||||
"name": "Daily scheduled scan",
|
||||
"scheduled_at": next_scan_datetime - timedelta(days=1),
|
||||
},
|
||||
defaults={"state": StateChoices.SCHEDULED},
|
||||
)
|
||||
|
||||
scan_instance.task_id = task_id
|
||||
@@ -205,10 +153,8 @@ def perform_scheduled_scan_task(self, tenant_id: str, provider_id: str):
|
||||
)
|
||||
|
||||
chain(
|
||||
perform_scan_summary_task.si(tenant_id, scan_instance.id),
|
||||
generate_outputs.si(
|
||||
scan_id=str(scan_instance.id), provider_id=provider_id, tenant_id=tenant_id
|
||||
),
|
||||
perform_scan_summary_task.s(tenant_id, scan_instance.id),
|
||||
generate_outputs.si(str(scan_instance.id), provider_id, tenant_id=tenant_id),
|
||||
).apply_async()
|
||||
|
||||
return result
|
||||
@@ -219,16 +165,36 @@ def perform_scan_summary_task(tenant_id: str, scan_id: str):
|
||||
return aggregate_findings(tenant_id=tenant_id, scan_id=scan_id)
|
||||
|
||||
|
||||
@shared_task(name="tenant-deletion", queue="deletion", autoretry_for=(Exception,))
|
||||
@shared_task(name="tenant-deletion")
|
||||
def delete_tenant_task(tenant_id: str):
|
||||
return delete_tenant(pk=tenant_id)
|
||||
|
||||
|
||||
@shared_task(
|
||||
base=RLSTask,
|
||||
name="scan-report",
|
||||
queue="scan-reports",
|
||||
)
|
||||
def batched(iterable, batch_size):
|
||||
"""
|
||||
Yield successive batches from an iterable.
|
||||
|
||||
Args:
|
||||
iterable: An iterable source of items.
|
||||
batch_size (int): The number of items per batch.
|
||||
|
||||
Yields:
|
||||
tuple: A pair (batch, is_last_batch) where:
|
||||
- batch (list): A list of items (with length equal to batch_size,
|
||||
except possibly for the last batch).
|
||||
- is_last_batch (bool): True if this is the final batch, False otherwise.
|
||||
"""
|
||||
batch = []
|
||||
for item in iterable:
|
||||
batch.append(item)
|
||||
if len(batch) == batch_size:
|
||||
yield batch, False
|
||||
batch = []
|
||||
|
||||
yield batch, True
|
||||
|
||||
|
||||
@shared_task(base=RLSTask, name="scan-output", queue="scans")
|
||||
@set_tenant(keep_tenant=True)
|
||||
def generate_outputs(scan_id: str, provider_id: str, tenant_id: str):
|
||||
"""
|
||||
@@ -246,16 +212,9 @@ def generate_outputs(scan_id: str, provider_id: str, tenant_id: str):
|
||||
scan_id (str): The scan identifier.
|
||||
provider_id (str): The provider_id id to be used in generating outputs.
|
||||
"""
|
||||
# Initialize the prowler provider
|
||||
prowler_provider = initialize_prowler_provider(Provider.objects.get(id=provider_id))
|
||||
|
||||
# Get the provider UID
|
||||
provider_uid = Provider.objects.get(id=provider_id).uid
|
||||
|
||||
# Generate and ensure the output directory exists
|
||||
output_directory = _generate_output_directory(
|
||||
DJANGO_TMP_OUTPUT_DIRECTORY, provider_uid, tenant_id, scan_id
|
||||
)
|
||||
output_directory = _generate_output_directory(provider_id, tenant_id, scan_id)
|
||||
os.makedirs("/".join(output_directory.split("/")[:-1]), exist_ok=True)
|
||||
|
||||
# Define auxiliary variables
|
||||
output_writers = {}
|
||||
@@ -264,22 +223,21 @@ def generate_outputs(scan_id: str, provider_id: str, tenant_id: str):
|
||||
)
|
||||
|
||||
# Retrieve findings queryset
|
||||
findings_qs = Finding.all_objects.filter(scan_id=scan_id).order_by("uid")
|
||||
findings_qs = Finding.objects.filter(scan_id=scan_id).order_by("uid")
|
||||
|
||||
# Process findings in batches
|
||||
for batch, is_last_batch in batched(
|
||||
findings_qs.iterator(), DJANGO_FINDINGS_BATCH_SIZE
|
||||
):
|
||||
for batch, is_last_batch in batched(findings_qs.iterator(), 50):
|
||||
finding_outputs = [
|
||||
FindingOutput.transform_api_finding(finding, prowler_provider)
|
||||
for finding in batch
|
||||
FindingOutput.transform_api_finding(finding) for finding in batch
|
||||
]
|
||||
|
||||
# Generate output files
|
||||
for mode, config in OUTPUT_FORMATS_MAPPING.items():
|
||||
kwargs = dict(config.get("kwargs", {}))
|
||||
if mode == "html":
|
||||
kwargs["provider"] = prowler_provider
|
||||
kwargs["provider"] = initialize_prowler_provider(
|
||||
Provider.objects.get(id=provider_id)
|
||||
)
|
||||
kwargs["stats"] = scan_summary
|
||||
|
||||
writer_class = config["class"]
|
||||
@@ -292,15 +250,12 @@ def generate_outputs(scan_id: str, provider_id: str, tenant_id: str):
|
||||
findings=finding_outputs,
|
||||
file_path=output_directory,
|
||||
file_extension=config["suffix"],
|
||||
from_cli=False,
|
||||
)
|
||||
writer.close_file = is_last_batch
|
||||
output_writers[writer_class] = writer
|
||||
|
||||
# Write the current batch using the writer
|
||||
writer.batch_write_data_to_file(**kwargs)
|
||||
|
||||
# TODO: Refactor the output classes to avoid this manual reset
|
||||
writer._data = []
|
||||
|
||||
# Compress output files
|
||||
@@ -310,20 +265,19 @@ def generate_outputs(scan_id: str, provider_id: str, tenant_id: str):
|
||||
uploaded = _upload_to_s3(tenant_id, output_directory, scan_id)
|
||||
|
||||
if uploaded:
|
||||
# Remove the local files after upload
|
||||
try:
|
||||
rmtree(Path(output_directory).parent, ignore_errors=True)
|
||||
except FileNotFoundError as e:
|
||||
logger.error(f"Error deleting output files: {e}")
|
||||
|
||||
output_directory = uploaded
|
||||
uploaded = True
|
||||
else:
|
||||
uploaded = False
|
||||
|
||||
# Update the scan instance with the output path
|
||||
Scan.all_objects.filter(id=scan_id).update(output_location=output_directory)
|
||||
# Update the scan instance with the output path and upload status
|
||||
Scan.objects.filter(id=scan_id).update(
|
||||
output_path=output_directory, upload_to_s3=uploaded
|
||||
)
|
||||
|
||||
logger.info(f"Scan output files generated, output location: {output_directory}")
|
||||
|
||||
return {"upload": uploaded}
|
||||
return {
|
||||
"output_path": output_directory,
|
||||
"upload_to_s3": uploaded,
|
||||
"scan_id": scan_id,
|
||||
"provider_id": provider_id,
|
||||
}
|
||||
|
||||
@@ -9,19 +9,17 @@ from api.models import Provider, Tenant
|
||||
class TestDeleteProvider:
|
||||
def test_delete_provider_success(self, providers_fixture):
|
||||
instance = providers_fixture[0]
|
||||
tenant_id = str(instance.tenant_id)
|
||||
result = delete_provider(tenant_id, instance.id)
|
||||
result = delete_provider(instance.id)
|
||||
|
||||
assert result
|
||||
with pytest.raises(ObjectDoesNotExist):
|
||||
Provider.objects.get(pk=instance.id)
|
||||
|
||||
def test_delete_provider_does_not_exist(self, tenants_fixture):
|
||||
tenant_id = str(tenants_fixture[0].id)
|
||||
def test_delete_provider_does_not_exist(self):
|
||||
non_existent_pk = "babf6796-cfcc-4fd3-9dcf-88d012247645"
|
||||
|
||||
with pytest.raises(ObjectDoesNotExist):
|
||||
delete_provider(tenant_id, non_existent_pk)
|
||||
delete_provider(non_existent_pk)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
||||
@@ -4,7 +4,7 @@ from unittest.mock import patch
|
||||
import pytest
|
||||
from django_celery_beat.models import IntervalSchedule, PeriodicTask
|
||||
from django_celery_results.models import TaskResult
|
||||
from tasks.utils import batched, get_next_execution_datetime
|
||||
from tasks.utils import get_next_execution_datetime
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -74,29 +74,3 @@ class TestGetNextExecutionDatetime:
|
||||
get_next_execution_datetime(
|
||||
task_id=task_result.task_id, provider_id="nonexistent"
|
||||
)
|
||||
|
||||
|
||||
class TestBatchedFunction:
|
||||
def test_empty_iterable(self):
|
||||
result = list(batched([], 3))
|
||||
assert result == [([], True)]
|
||||
|
||||
def test_exact_batches(self):
|
||||
result = list(batched([1, 2, 3, 4], 2))
|
||||
expected = [([1, 2], False), ([3, 4], False), ([], True)]
|
||||
assert result == expected
|
||||
|
||||
def test_inexact_batches(self):
|
||||
result = list(batched([1, 2, 3, 4, 5], 2))
|
||||
expected = [([1, 2], False), ([3, 4], False), ([5], True)]
|
||||
assert result == expected
|
||||
|
||||
def test_batch_size_one(self):
|
||||
result = list(batched([1, 2, 3], 1))
|
||||
expected = [([1], False), ([2], False), ([3], False), ([], True)]
|
||||
assert result == expected
|
||||
|
||||
def test_batch_size_greater_than_length(self):
|
||||
result = list(batched([1, 2, 3], 5))
|
||||
expected = [([1, 2, 3], True)]
|
||||
assert result == expected
|
||||
|
||||
@@ -24,27 +24,3 @@ def get_next_execution_datetime(task_id: int, provider_id: str) -> datetime:
|
||||
)
|
||||
|
||||
return current_scheduled_time + timedelta(**{interval.period: interval.every})
|
||||
|
||||
|
||||
def batched(iterable, batch_size):
|
||||
"""
|
||||
Yield successive batches from an iterable.
|
||||
|
||||
Args:
|
||||
iterable: An iterable source of items.
|
||||
batch_size (int): The number of items per batch.
|
||||
|
||||
Yields:
|
||||
tuple: A pair (batch, is_last_batch) where:
|
||||
- batch (list): A list of items (with length equal to batch_size,
|
||||
except possibly for the last batch).
|
||||
- is_last_batch (bool): True if this is the final batch, False otherwise.
|
||||
"""
|
||||
batch = []
|
||||
for item in iterable:
|
||||
batch.append(item)
|
||||
if len(batch) == batch_size:
|
||||
yield batch, False
|
||||
batch = []
|
||||
|
||||
yield batch, True
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import warnings
|
||||
|
||||
from dashboard.common_methods import get_section_containers_cis
|
||||
|
||||
warnings.filterwarnings("ignore")
|
||||
|
||||
|
||||
def get_table(data):
|
||||
aux = data[
|
||||
[
|
||||
"REQUIREMENTS_ID",
|
||||
"REQUIREMENTS_DESCRIPTION",
|
||||
"REQUIREMENTS_ATTRIBUTES_SECTION",
|
||||
"CHECKID",
|
||||
"STATUS",
|
||||
"REGION",
|
||||
"ACCOUNTID",
|
||||
"RESOURCEID",
|
||||
]
|
||||
].copy()
|
||||
|
||||
return get_section_containers_cis(
|
||||
aux, "REQUIREMENTS_ID", "REQUIREMENTS_ATTRIBUTES_SECTION"
|
||||
)
|
||||
@@ -1,23 +0,0 @@
|
||||
import warnings
|
||||
|
||||
from dashboard.common_methods import get_section_container_iso
|
||||
|
||||
warnings.filterwarnings("ignore")
|
||||
|
||||
|
||||
def get_table(data):
|
||||
aux = data[
|
||||
[
|
||||
"REQUIREMENTS_ATTRIBUTES_CATEGORY",
|
||||
"REQUIREMENTS_ATTRIBUTES_OBJETIVE_ID",
|
||||
"REQUIREMENTS_ATTRIBUTES_OBJETIVE_NAME",
|
||||
"CHECKID",
|
||||
"STATUS",
|
||||
"REGION",
|
||||
"ACCOUNTID",
|
||||
"RESOURCEID",
|
||||
]
|
||||
]
|
||||
return get_section_container_iso(
|
||||
aux, "REQUIREMENTS_ATTRIBUTES_CATEGORY", "REQUIREMENTS_ATTRIBUTES_OBJETIVE_ID"
|
||||
)
|
||||
@@ -1,24 +0,0 @@
|
||||
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",
|
||||
]
|
||||
]
|
||||
|
||||
return get_section_containers_format3(
|
||||
aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ID"
|
||||
)
|
||||
@@ -1,23 +0,0 @@
|
||||
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",
|
||||
]
|
||||
]
|
||||
return get_section_containers_format3(
|
||||
aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ID"
|
||||
)
|
||||
@@ -1,23 +0,0 @@
|
||||
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",
|
||||
]
|
||||
]
|
||||
return get_section_containers_format3(
|
||||
aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ID"
|
||||
)
|
||||
@@ -1,24 +0,0 @@
|
||||
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",
|
||||
]
|
||||
]
|
||||
|
||||
return get_section_containers_format3(
|
||||
aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ID"
|
||||
)
|
||||
@@ -76,6 +76,7 @@ def load_csv_files(csv_files):
|
||||
result = result.replace("_AZURE", " - AZURE")
|
||||
if "KUBERNETES" in result:
|
||||
result = result.replace("_KUBERNETES", " - KUBERNETES")
|
||||
result = result[result.find("CIS_") :]
|
||||
results.append(result)
|
||||
|
||||
unique_results = set(results)
|
||||
|
||||
@@ -165,21 +165,9 @@ else:
|
||||
)
|
||||
|
||||
# For the timestamp, remove the two columns and keep only the date
|
||||
|
||||
data["TIMESTAMP"] = pd.to_datetime(data["TIMESTAMP"])
|
||||
# Handle findings from v3 outputs
|
||||
if "FINDING_UNIQUE_ID" in data.columns:
|
||||
data.rename(columns={"FINDING_UNIQUE_ID": "FINDING_UID"}, inplace=True)
|
||||
if "ACCOUNT_ID" in data.columns:
|
||||
data.rename(columns={"ACCOUNT_ID": "ACCOUNT_UID"}, inplace=True)
|
||||
if "ASSESSMENT_START_TIME" in data.columns:
|
||||
data.rename(columns={"ASSESSMENT_START_TIME": "TIMESTAMP"}, inplace=True)
|
||||
if "RESOURCE_ID" in data.columns:
|
||||
data.rename(columns={"RESOURCE_ID": "RESOURCE_UID"}, inplace=True)
|
||||
|
||||
# Remove dupplicates on the finding_uid colummn but keep the last one taking into account the timestamp
|
||||
data = data.sort_values("TIMESTAMP").drop_duplicates("FINDING_UID", keep="last")
|
||||
|
||||
data["ASSESSMENT_TIME"] = data["TIMESTAMP"].dt.strftime("%Y-%m-%d")
|
||||
data["ASSESSMENT_TIME"] = data["TIMESTAMP"].dt.strftime("%Y-%m-%d %H:%M:%S")
|
||||
data_valid = pd.DataFrame()
|
||||
for account in data["ACCOUNT_UID"].unique():
|
||||
all_times = data[data["ACCOUNT_UID"] == account]["ASSESSMENT_TIME"].unique()
|
||||
|
||||
@@ -7,8 +7,6 @@ services:
|
||||
required: false
|
||||
ports:
|
||||
- "${DJANGO_PORT:-8080}:${DJANGO_PORT:-8080}"
|
||||
volumes:
|
||||
- "/tmp/prowler_api_output:/tmp/prowler_api_output"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
@@ -67,8 +65,6 @@ services:
|
||||
env_file:
|
||||
- path: .env
|
||||
required: false
|
||||
volumes:
|
||||
- "/tmp/prowler_api_output:/tmp/prowler_api_output"
|
||||
depends_on:
|
||||
valkey:
|
||||
condition: service_healthy
|
||||
|
||||
@@ -19,13 +19,8 @@ For isolation and to avoid conflicts with other environments, we recommend using
|
||||
Then install all dependencies including the ones for developers:
|
||||
```
|
||||
poetry install --with dev
|
||||
eval $(poetry env activate) \
|
||||
poetry shell
|
||||
```
|
||||
> [!IMPORTANT]
|
||||
> Starting from Poetry v2.0.0, `poetry shell` has been deprecated in favor of `poetry env activate`.
|
||||
>
|
||||
> If your poetry version is below 2.0.0 you must keep using `poetry shell` to activate your environment.
|
||||
> In case you have any doubts, consult the Poetry environment activation guide: https://python-poetry.org/docs/managing-environments/#activating-the-environment
|
||||
|
||||
## Contributing with your code or fixes to Prowler
|
||||
|
||||
|
||||
@@ -175,7 +175,6 @@ Due to the complexity and differences of each provider use the rest of the provi
|
||||
- [GCP](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/gcp/gcp_provider.py)
|
||||
- [Azure](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/azure/azure_provider.py)
|
||||
- [Kubernetes](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/kubernetes/kubernetes_provider.py)
|
||||
- [Microsoft365](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/microsoft365/microsoft365_provider.py)
|
||||
|
||||
To facilitate understanding here is a pseudocode of how the most basic provider could be with examples.
|
||||
|
||||
|
||||
@@ -237,4 +237,3 @@ It is really important to check if the current Prowler's permissions for each pr
|
||||
- AWS: https://docs.prowler.cloud/en/latest/getting-started/requirements/#aws-authentication
|
||||
- Azure: https://docs.prowler.cloud/en/latest/getting-started/requirements/#permissions
|
||||
- GCP: https://docs.prowler.cloud/en/latest/getting-started/requirements/#gcp-authentication
|
||||
- Microsoft365: https://docs.prowler.cloud/en/latest/getting-started/requirements/#microsoft365-authentication
|
||||
|
||||
@@ -102,32 +102,3 @@ Those credentials must be associated to a user or service account with proper pe
|
||||
|
||||
???+ note
|
||||
By default, `prowler` will scan all accessible GCP Projects, use flag `--project-ids` to specify the projects to be scanned.
|
||||
|
||||
## Microsoft365
|
||||
|
||||
Prowler for Microsoft365 currently supports the following authentication types:
|
||||
|
||||
- [Service principal application](https://learn.microsoft.com/en-us/entra/identity-platform/app-objects-and-service-principals?tabs=browser#service-principal-object) (recommended).
|
||||
- Current az cli credentials stored.
|
||||
- Interactive browser authentication.
|
||||
|
||||
|
||||
???+ warning
|
||||
For Prowler App only the Service Principal with an application authentication method is supported.
|
||||
|
||||
### Service Principal authentication
|
||||
|
||||
To allow Prowler assume the service principal identity to start the scan it is needed to configure the following environment variables:
|
||||
|
||||
```console
|
||||
export AZURE_CLIENT_ID="XXXXXXXXX"
|
||||
export AZURE_CLIENT_SECRET="XXXXXXXXX"
|
||||
export AZURE_TENANT_ID="XXXXXXXXX"
|
||||
```
|
||||
|
||||
If you try to execute Prowler with the `--sp-env-auth` flag and those variables are empty or not exported, the execution is going to fail.
|
||||
Follow the instructions in the [Create Prowler Service Principal](../tutorials/azure/create-prowler-service-principal.md) section to create a service principal.
|
||||
|
||||
### Interactive Browser authentication
|
||||
|
||||
To use `--browser-auth` the user needs to authenticate against Azure using the default browser to start the scan, also `--tenant-id` flag is required.
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 330 KiB |
+5
-29
@@ -76,7 +76,7 @@ Prowler App can be installed in different ways, depending on your environment:
|
||||
git clone https://github.com/prowler-cloud/prowler \
|
||||
cd prowler/api \
|
||||
poetry install \
|
||||
eval $(poetry env activate) \
|
||||
poetry shell \
|
||||
set -a \
|
||||
source .env \
|
||||
docker compose up postgres valkey -d \
|
||||
@@ -85,12 +85,6 @@ Prowler App can be installed in different ways, depending on your environment:
|
||||
gunicorn -c config/guniconf.py config.wsgi:application
|
||||
```
|
||||
|
||||
???+ important
|
||||
Starting from Poetry v2.0.0, `poetry shell` has been deprecated in favor of `poetry env activate`.
|
||||
|
||||
If your poetry version is below 2.0.0 you must keep using `poetry shell` to activate your environment.
|
||||
In case you have any doubts, consult the Poetry environment activation guide: https://python-poetry.org/docs/managing-environments/#activating-the-environment
|
||||
|
||||
> Now, you can access the API documentation at http://localhost:8080/api/v1/docs.
|
||||
|
||||
_Commands to run the API Worker_:
|
||||
@@ -99,7 +93,7 @@ Prowler App can be installed in different ways, depending on your environment:
|
||||
git clone https://github.com/prowler-cloud/prowler \
|
||||
cd prowler/api \
|
||||
poetry install \
|
||||
eval $(poetry env activate) \
|
||||
poetry shell \
|
||||
set -a \
|
||||
source .env \
|
||||
cd src/backend \
|
||||
@@ -112,7 +106,7 @@ Prowler App can be installed in different ways, depending on your environment:
|
||||
git clone https://github.com/prowler-cloud/prowler \
|
||||
cd prowler/api \
|
||||
poetry install \
|
||||
eval $(poetry env activate) \
|
||||
poetry shell \
|
||||
set -a \
|
||||
source .env \
|
||||
cd src/backend \
|
||||
@@ -170,7 +164,7 @@ Prowler is available as a project in [PyPI](https://pypi.org/project/prowler/),
|
||||
|
||||
* `Python >= 3.9`
|
||||
* `Python pip >= 21.0.0`
|
||||
* AWS, GCP, Azure, Microsoft365 and/or Kubernetes credentials
|
||||
* AWS, GCP, Azure and/or Kubernetes credentials
|
||||
|
||||
_Commands_:
|
||||
|
||||
@@ -423,7 +417,7 @@ While the scan is running, start exploring the findings in these sections:
|
||||
|
||||
### Prowler CLI
|
||||
|
||||
To run Prowler, you will need to specify the provider (e.g `aws`, `gcp`, `azure`, `microsoft365` or `kubernetes`):
|
||||
To run Prowler, you will need to specify the provider (e.g `aws`, `gcp`, `azure` or `kubernetes`):
|
||||
|
||||
???+ note
|
||||
If no provider specified, AWS will be used for backward compatibility with most of v2 options.
|
||||
@@ -565,23 +559,5 @@ kubectl logs prowler-XXXXX --namespace prowler-ns
|
||||
???+ note
|
||||
By default, `prowler` will scan all namespaces in your active Kubernetes context. Use the flag `--context` to specify the context to be scanned and `--namespaces` to specify the namespaces to be scanned.
|
||||
|
||||
#### Microsoft365
|
||||
|
||||
With Microsoft365 you need to specify which auth method is going to be used:
|
||||
|
||||
```console
|
||||
# To use service principal authentication
|
||||
prowler microsoft365 --sp-env-auth
|
||||
|
||||
# To use az cli authentication
|
||||
prowler microsoft365 --az-cli-auth
|
||||
|
||||
# To use browser authentication
|
||||
prowler microsoft365 --browser-auth --tenant-id "XXXXXXXX"
|
||||
|
||||
```
|
||||
|
||||
See more details about Microsoft365 Authentication in [Requirements](getting-started/requirements.md#microsoft365)
|
||||
|
||||
## Prowler v2 Documentation
|
||||
For **Prowler v2 Documentation**, please check it out [here](https://github.com/prowler-cloud/prowler/blob/8818f47333a0c1c1a457453c87af0ea5b89a385f/README.md).
|
||||
|
||||
@@ -27,12 +27,7 @@ cd prowler
|
||||
pip install poetry
|
||||
mkdir /tmp/poetry
|
||||
poetry config cache-dir /tmp/poetry
|
||||
eval $(poetry env activate)
|
||||
poetry shell
|
||||
poetry install
|
||||
python prowler.py -v
|
||||
```
|
||||
> [!IMPORTANT]
|
||||
> Starting from Poetry v2.0.0, `poetry shell` has been deprecated in favor of `poetry env activate`.
|
||||
>
|
||||
> If your poetry version is below 2.0.0 you must keep using `poetry shell` to activate your environment.
|
||||
> In case you have any doubts, consult the Poetry environment activation guide: https://python-poetry.org/docs/managing-environments/#activating-the-environment
|
||||
|
||||
@@ -4,7 +4,7 @@ To allow Prowler assume an identity to start the scan with the required privileg
|
||||
|
||||
To create a Service Principal Application you can use the Azure Portal or the Azure CLI.
|
||||
|
||||
## From Azure Portal / Entra Admin Center
|
||||
## From Azure Portal
|
||||
|
||||
1. Access to Microsoft Entra ID
|
||||
2. In the left menu bar, go to "App registrations"
|
||||
|
||||
@@ -47,7 +47,6 @@ The following list includes all the AWS checks with configurable variables that
|
||||
| `ecr_repositories_scan_vulnerabilities_in_latest_image` | `ecr_repository_vulnerability_minimum_severity` | String |
|
||||
| `eks_cluster_uses_a_supported_version` | `eks_cluster_oldest_version_supported` | String |
|
||||
| `eks_control_plane_logging_all_types_enabled` | `eks_required_log_types` | List of Strings |
|
||||
| `elasticache_redis_cluster_backup_enabled` | `minimum_snapshot_retention_period` | Integer |
|
||||
| `elb_is_in_multiple_az` | `elb_min_azs` | Integer |
|
||||
| `elbv2_is_in_multiple_az` | `elbv2_min_azs` | Integer |
|
||||
| `guardduty_is_enabled` | `mute_non_default_regions` | Boolean |
|
||||
|
||||
@@ -36,7 +36,7 @@ In this page you can do multiple functions:
|
||||
* Severity
|
||||
* Service
|
||||
* Status
|
||||
* See which files has been scanned to generate the dashboard placing your mouse on the `?` icon:
|
||||
* See wich files has been scanned to generate the dashboard placing your mouse on the `?` icon:
|
||||
<img src="../img/dashboard/dashboard-files-scanned.png">
|
||||
* Download the `Top Findings by Severity` table using the button `DOWNLOAD THIS TABLE AS CSV` or `DOWNLOAD THIS TABLE AS XLSX`
|
||||
* Click on the provider cards to filter by provider.
|
||||
|
||||
@@ -25,9 +25,6 @@ Prowler will follow the same credentials search as [Google authentication librar
|
||||
|
||||
Those credentials must be associated to a user or service account with proper permissions to do all checks. To make sure, add the `Viewer` role to the member associated with the credentials.
|
||||
|
||||
???+ note
|
||||
Prowler will use the enabled Google Cloud APIs to get the information needed to perform the checks.
|
||||
|
||||
## Impersonate Service Account
|
||||
|
||||
If you want to impersonate a GCP service account, you can use the `--impersonate-service-account` argument:
|
||||
@@ -37,3 +34,23 @@ prowler gcp --impersonate-service-account <service-account-email>
|
||||
```
|
||||
|
||||
This argument will use the default credentials to impersonate the service account provided.
|
||||
|
||||
## Service APIs
|
||||
|
||||
Prowler will use the Google Cloud APIs to get the information needed to perform the checks. Make sure that the following APIs are enabled in the project:
|
||||
|
||||
- apikeys.googleapis.com
|
||||
- artifactregistry.googleapis.com
|
||||
- bigquery.googleapis.com
|
||||
- sqladmin.googleapis.com
|
||||
- storage.googleapis.com
|
||||
- compute.googleapis.com
|
||||
- dataproc.googleapis.com
|
||||
- dns.googleapis.com
|
||||
- containerregistry.googleapis.com
|
||||
- container.googleapis.com
|
||||
- iam.googleapis.com
|
||||
- cloudkms.googleapis.com
|
||||
- logging.googleapis.com
|
||||
|
||||
You can enable them automatically using our script [enable_apis_in_projects.sh](https://github.com/prowler-cloud/prowler/blob/master/contrib/gcp/enable_apis_in_projects.sh)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
For in-cluster execution, you can use the supplied yaml files inside `/kubernetes`:
|
||||
|
||||
* [prowler-sa.yaml](https://github.com/prowler-cloud/prowler/blob/master/kubernetes/prowler-sa.yaml)
|
||||
* [job.yaml](https://github.com/prowler-cloud/prowler/blob/master/kubernetes/job.yaml)
|
||||
* [prowler-role.yaml](https://github.com/prowler-cloud/prowler/blob/master/kubernetes/prowler-role.yaml)
|
||||
* [prowler-rolebinding.yaml](https://github.com/prowler-cloud/prowler/blob/master/kubernetes/prowler-rolebinding.yaml)
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
# Microsoft365 authentication
|
||||
|
||||
By default Prowler uses MsGraph Python SDK identity package authentication methods using the class `ClientSecretCredential`.
|
||||
This allows Prowler to authenticate against microsoft365 using the following methods:
|
||||
|
||||
- Service principal authentication by environment variables (Enterprise Application)
|
||||
- Current CLI credentials stored
|
||||
- Interactive browser authentication
|
||||
|
||||
To launch the tool first you need to specify which method is used through the following flags:
|
||||
|
||||
```console
|
||||
# To use service principal authentication
|
||||
prowler microsoft365 --sp-env-auth
|
||||
|
||||
# To use cli authentication
|
||||
prowler microsoft365 --az-cli-auth
|
||||
|
||||
# To use browser authentication
|
||||
prowler microsoft365 --browser-auth --tenant-id "XXXXXXXX"
|
||||
```
|
||||
|
||||
To use Prowler you need to set up also the permissions required to access your resources in your Microsoft365 account, to more details refer to [Requirements](../../getting-started/requirements.md)
|
||||
@@ -71,15 +71,6 @@ For AWS, enter your `AWS Account ID` and choose one of the following methods to
|
||||
|
||||
<img src="../../img/aws-role.png" alt="AWS Role" width="700"/>
|
||||
|
||||
???+ note
|
||||
check if your AWS Security Token Service (STS) has the EU (Ireland) endpoint active. If not we will not be able to connect to your AWS account.
|
||||
|
||||
If that is the case your STS configuration may look like this:
|
||||
|
||||
<img src="../../img/sts-configuration.png" alt="AWS Role" width="800"/>
|
||||
|
||||
To solve this issue, please activate the EU (Ireland) STS endpoint.
|
||||
|
||||
---
|
||||
|
||||
### **Step 4.2: Azure Credentials**
|
||||
@@ -109,11 +100,9 @@ By default, the `kubeconfig` file is located at `~/.kube/config`.
|
||||
<img src="../../img/kubernetes-credentials.png" alt="Kubernetes Credentials" width="700"/>
|
||||
|
||||
???+ note
|
||||
If you are adding an **EKS**, **GKE**, **AKS** or external cluster, follow these additional steps to ensure proper authentication:
|
||||
If you are adding an **Amazon EKS** cluster, follow these additional steps to ensure proper authentication:
|
||||
|
||||
** Make sure your cluster allow traffic from the Prowler Cloud IP address `52.48.254.174/32` **
|
||||
|
||||
1. Apply the necessary Kubernetes resources to your EKS, GKE, AKS or external cluster (you can find the files in the [`kubernetes` directory of the Prowler repository](https://github.com/prowler-cloud/prowler/tree/master/kubernetes)):
|
||||
1. Apply the necessary Kubernetes resources to your EKS cluster (you can find the files in the [`kubernetes` directory of the Prowler repository](https://github.com/prowler-cloud/prowler/tree/master/kubernetes)):
|
||||
```console
|
||||
kubectl apply -f kubernetes/prowler-sa.yaml
|
||||
kubectl apply -f kubernetes/prowler-role.yaml
|
||||
@@ -130,11 +119,11 @@ By default, the `kubeconfig` file is located at `~/.kube/config`.
|
||||
3. Update your `kubeconfig` to use the ServiceAccount token:
|
||||
```console
|
||||
kubectl config set-credentials prowler-sa --token=<SA_TOKEN>
|
||||
kubectl config set-context <CONTEXT_NAME> --user=prowler-sa
|
||||
kubectl config set-context <CLUSTER_ARN> --user=prowler-sa
|
||||
```
|
||||
Replace <SA_TOKEN> with the generated token and <CONTEXT_NAME> with your KubeConfig Context Name of your EKS, GKE or AKS cluster.
|
||||
Replace <SA_TOKEN> with the generated token and <CLUSTER_ARN> with your EKS cluster ARN.
|
||||
|
||||
4. Now you can add the modified `kubeconfig` in Prowler Cloud. Then simply test the connection.
|
||||
4. Now you can add the modified `kubeconfig` as the credentials of the AWS EKS Cluster in Prowler Cloud. Then simply test the connection.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
AUTH_METHOD;TIMESTAMP;ACCOUNT_UID;ACCOUNT_NAME;ACCOUNT_EMAIL;ACCOUNT_ORGANIZATION_UID;ACCOUNT_ORGANIZATION_NAME;ACCOUNT_TAGS;FINDING_UID;PROVIDER;CHECK_ID;CHECK_TITLE;CHECK_TYPE;STATUS;STATUS_EXTENDED;MUTED;SERVICE_NAME;SUBSERVICE_NAME;SEVERITY;RESOURCE_TYPE;RESOURCE_UID;RESOURCE_NAME;RESOURCE_DETAILS;RESOURCE_TAGS;PARTITION;REGION;DESCRIPTION;RISK;RELATED_URL;REMEDIATION_RECOMMENDATION_TEXT;REMEDIATION_RECOMMENDATION_URL;REMEDIATION_CODE_NATIVEIAC;REMEDIATION_CODE_TERRAFORM;REMEDIATION_CODE_CLI;REMEDIATION_CODE_OTHER;COMPLIANCE;CATEGORIES;DEPENDS_ON;RELATED_TO;NOTES;PROWLER_VERSION
|
||||
<auth_method>;2025-02-14 14:27:03.913874;<account_uid>;;;;;;<finding_uid>;aws;accessanalyzer_enabled;Check if IAM Access Analyzer is enabled;IAM;FAIL;IAM Access Analyzer in account <account_uid> is not enabled.;False;accessanalyzer;;low;Other;<resource_uid>;<resource_name>;;;aws;<region>;Check if IAM Access Analyzer is enabled;AWS IAM Access Analyzer helps you identify the resources in your organization and accounts, such as Amazon S3 buckets or IAM roles, that are shared with an external entity. This lets you identify unintended access to your resources and data, which is a security risk. IAM Access Analyzer uses a form of mathematical analysis called automated reasoning, which applies logic and mathematical inference to determine all possible access paths allowed by a resource policy.;https://docs.aws.amazon.com/IAM/latest/UserGuide/what-is-access-analyzer.html;Enable IAM Access Analyzer for all accounts, create analyzer and take action over it is recommendations (IAM Access Analyzer is available at no additional cost).;https://docs.aws.amazon.com/IAM/latest/UserGuide/what-is-access-analyzer.html;;;aws accessanalyzer create-analyzer --analyzer-name <NAME> --type <ACCOUNT|ORGANIZATION>;;CIS-1.4: 1.20 | CIS-1.5: 1.20 | KISA-ISMS-P-2023: 2.5.6, 2.6.4, 2.8.1, 2.8.2 | CIS-2.0: 1.20 | KISA-ISMS-P-2023-korean: 2.5.6, 2.6.4, 2.8.1, 2.8.2 | AWS-Account-Security-Onboarding: Enabled security services, Create analyzers in each active regions, Verify that events are present in SecurityHub aggregated view | CIS-3.0: 1.20;;;;;<prowler_version>
|
||||
<auth_method>;2025-02-14 14:27:03.913874;<account_uid>;;;;;;<finding_uid>;aws;account_maintain_current_contact_details;Maintain current contact details.;IAM;MANUAL;Login to the AWS Console. Choose your account name on the top right of the window -> My Account -> Contact Information.;False;account;;medium;Other;<resource_uid>;<account_uid>;;;aws;<region>;Maintain current contact details.;Ensure contact email and telephone details for AWS accounts are current and map to more than one individual in your organization. An AWS account supports a number of contact details, and AWS will use these to contact the account owner if activity judged to be in breach of Acceptable Use Policy. If an AWS account is observed to be behaving in a prohibited or suspicious manner, AWS will attempt to contact the account owner by email and phone using the contact details listed. If this is unsuccessful and the account behavior needs urgent mitigation, proactive measures may be taken, including throttling of traffic between the account exhibiting suspicious behavior and the AWS API endpoints and the Internet. This will result in impaired service to and from the account in question.;;Using the Billing and Cost Management console complete contact details.;https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-update-contact.html;;;No command available.;https://docs.prowler.com/checks/aws/iam-policies/iam_18-maintain-contact-details#aws-console;CIS-1.4: 1.1 | CIS-1.5: 1.1 | KISA-ISMS-P-2023: 2.1.3 | CIS-2.0: 1.1 | KISA-ISMS-P-2023-korean: 2.1.3 | AWS-Well-Architected-Framework-Security-Pillar: SEC03-BP03, SEC10-BP01 | AWS-Account-Security-Onboarding: Billing, emergency, security contacts | CIS-3.0: 1.1 | ENS-RD2022: op.ext.7.aws.am.1;;;;;<prowler_version>
|
||||
<auth_method>;2025-02-14 14:27:03.913874;<account_uid>;;;;;;<finding_uid>;aws;account_maintain_different_contact_details_to_security_billing_and_operations;Maintain different contact details to security, billing and operations.;IAM;FAIL;SECURITY, BILLING and OPERATIONS contacts not found or they are not different between each other and between ROOT contact.;False;account;;medium;Other;<resource_uid>;<account_uid>;;;aws;<region>;Maintain different contact details to security, billing and operations.;Ensure contact email and telephone details for AWS accounts are current and map to more than one individual in your organization. An AWS account supports a number of contact details, and AWS will use these to contact the account owner if activity judged to be in breach of Acceptable Use Policy. If an AWS account is observed to be behaving in a prohibited or suspicious manner, AWS will attempt to contact the account owner by email and phone using the contact details listed. If this is unsuccessful and the account behavior needs urgent mitigation, proactive measures may be taken, including throttling of traffic between the account exhibiting suspicious behavior and the AWS API endpoints and the Internet. This will result in impaired service to and from the account in question.;https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-update-contact.html;Using the Billing and Cost Management console complete contact details.;https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-update-contact.html;;;;https://docs.prowler.com/checks/aws/iam-policies/iam_18-maintain-contact-details#aws-console;KISA-ISMS-P-2023: 2.1.3 | KISA-ISMS-P-2023-korean: 2.1.3;;;;;<prowler_version>
|
||||
<auth_method>;2025-02-14 14:27:03.913874;<account_uid>;;;;;;<finding_uid>;aws;account_security_contact_information_is_registered;Ensure security contact information is registered.;IAM;MANUAL;Login to the AWS Console. Choose your account name on the top right of the window -> My Account -> Alternate Contacts -> Security Section.;False;account;;medium;Other;<resource_uid>:root;<account_uid>;;;aws;<region>;Ensure security contact information is registered.;AWS provides customers with the option of specifying the contact information for accounts security team. It is recommended that this information be provided. Specifying security-specific contact information will help ensure that security advisories sent by AWS reach the team in your organization that is best equipped to respond to them.;;Go to the My Account section and complete alternate contacts.;https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-update-contact.html;;;No command available.;https://docs.prowler.com/checks/aws/iam-policies/iam_19#aws-console;CIS-1.4: 1.2 | CIS-1.5: 1.2 | AWS-Foundational-Security-Best-Practices: account, acm | KISA-ISMS-P-2023: 2.1.3, 2.2.1 | CIS-2.0: 1.2 | KISA-ISMS-P-2023-korean: 2.1.3, 2.2.1 | AWS-Well-Architected-Framework-Security-Pillar: SEC03-BP03, SEC10-BP01 | AWS-Account-Security-Onboarding: Billing, emergency, security contacts | CIS-3.0: 1.2 | ENS-RD2022: op.ext.7.aws.am.1;;;;;<prowler_version>
|
||||
|
@@ -1,625 +0,0 @@
|
||||
[
|
||||
{
|
||||
"message": "IAM Access Analyzer in account <account_uid> is not enabled.",
|
||||
"metadata": {
|
||||
"event_code": "accessanalyzer_enabled",
|
||||
"product": {
|
||||
"name": "Prowler",
|
||||
"uid": "prowler",
|
||||
"vendor_name": "Prowler",
|
||||
"version": "<prowler_version>"
|
||||
},
|
||||
"profiles": [
|
||||
"cloud",
|
||||
"datetime"
|
||||
],
|
||||
"tenant_uid": "",
|
||||
"version": "1.4.0"
|
||||
},
|
||||
"severity_id": 2,
|
||||
"severity": "Low",
|
||||
"status": "New",
|
||||
"status_code": "FAIL",
|
||||
"status_detail": "IAM Access Analyzer in account <account_uid> is not enabled.",
|
||||
"status_id": 1,
|
||||
"unmapped": {
|
||||
"related_url": "https://docs.aws.amazon.com/IAM/latest/UserGuide/what-is-access-analyzer.html",
|
||||
"categories": [],
|
||||
"depends_on": [],
|
||||
"related_to": [],
|
||||
"notes": "",
|
||||
"compliance": {
|
||||
"CIS-1.4": [
|
||||
"1.20"
|
||||
],
|
||||
"CIS-1.5": [
|
||||
"1.20"
|
||||
],
|
||||
"KISA-ISMS-P-2023": [
|
||||
"2.5.6",
|
||||
"2.6.4",
|
||||
"2.8.1",
|
||||
"2.8.2"
|
||||
],
|
||||
"CIS-2.0": [
|
||||
"1.20"
|
||||
],
|
||||
"KISA-ISMS-P-2023-korean": [
|
||||
"2.5.6",
|
||||
"2.6.4",
|
||||
"2.8.1",
|
||||
"2.8.2"
|
||||
],
|
||||
"AWS-Account-Security-Onboarding": [
|
||||
"Enabled security services",
|
||||
"Create analyzers in each active regions",
|
||||
"Verify that events are present in SecurityHub aggregated view"
|
||||
],
|
||||
"CIS-3.0": [
|
||||
"1.20"
|
||||
]
|
||||
}
|
||||
},
|
||||
"activity_name": "Create",
|
||||
"activity_id": 1,
|
||||
"finding_info": {
|
||||
"created_time": 1739539623,
|
||||
"created_time_dt": "2025-02-14T14:27:03.913874",
|
||||
"desc": "Check if IAM Access Analyzer is enabled",
|
||||
"product_uid": "prowler",
|
||||
"title": "Check if IAM Access Analyzer is enabled",
|
||||
"types": [
|
||||
"IAM"
|
||||
],
|
||||
"uid": "<finding_uid>"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"cloud_partition": "aws",
|
||||
"region": "<region>",
|
||||
"data": {
|
||||
"details": "",
|
||||
"metadata": {
|
||||
"arn": "<resource_arn>",
|
||||
"name": "<resource_name>",
|
||||
"status": "NOT_AVAILABLE",
|
||||
"findings": [],
|
||||
"tags": [],
|
||||
"type": "",
|
||||
"region": "<region>"
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"name": "accessanalyzer"
|
||||
},
|
||||
"labels": [],
|
||||
"name": "<resource_name>",
|
||||
"type": "Other",
|
||||
"uid": "<resource_uid>"
|
||||
}
|
||||
],
|
||||
"category_name": "Findings",
|
||||
"category_uid": 2,
|
||||
"class_name": "Detection Finding",
|
||||
"class_uid": 2004,
|
||||
"cloud": {
|
||||
"account": {
|
||||
"name": "",
|
||||
"type": "AWS Account",
|
||||
"type_id": 10,
|
||||
"uid": "<account_uid>",
|
||||
"labels": []
|
||||
},
|
||||
"org": {
|
||||
"name": "",
|
||||
"uid": ""
|
||||
},
|
||||
"provider": "aws",
|
||||
"region": "<region>"
|
||||
},
|
||||
"remediation": {
|
||||
"desc": "Enable IAM Access Analyzer for all accounts, create analyzer and take action over it is recommendations (IAM Access Analyzer is available at no additional cost).",
|
||||
"references": [
|
||||
"aws accessanalyzer create-analyzer --analyzer-name <NAME> --type <ACCOUNT|ORGANIZATION>",
|
||||
"https://docs.aws.amazon.com/IAM/latest/UserGuide/what-is-access-analyzer.html"
|
||||
]
|
||||
},
|
||||
"risk_details": "AWS IAM Access Analyzer helps you identify the resources in your organization and accounts, such as Amazon S3 buckets or IAM roles, that are shared with an external entity. This lets you identify unintended access to your resources and data, which is a security risk. IAM Access Analyzer uses a form of mathematical analysis called automated reasoning, which applies logic and mathematical inference to determine all possible access paths allowed by a resource policy.",
|
||||
"time": 1739539623,
|
||||
"time_dt": "2025-02-14T14:27:03.913874",
|
||||
"type_uid": 200401,
|
||||
"type_name": "Detection Finding: Create"
|
||||
},
|
||||
{
|
||||
"message": "Login to the AWS Console. Choose your account name on the top right of the window -> My Account -> Contact Information.",
|
||||
"metadata": {
|
||||
"event_code": "account_maintain_current_contact_details",
|
||||
"product": {
|
||||
"name": "Prowler",
|
||||
"uid": "prowler",
|
||||
"vendor_name": "Prowler",
|
||||
"version": "<prowler_version>"
|
||||
},
|
||||
"profiles": [
|
||||
"cloud",
|
||||
"datetime"
|
||||
],
|
||||
"tenant_uid": "",
|
||||
"version": "1.4.0"
|
||||
},
|
||||
"severity_id": 3,
|
||||
"severity": "Medium",
|
||||
"status": "New",
|
||||
"status_code": "MANUAL",
|
||||
"status_detail": "Login to the AWS Console. Choose your account name on the top right of the window -> My Account -> Contact Information.",
|
||||
"status_id": 1,
|
||||
"unmapped": {
|
||||
"related_url": "",
|
||||
"categories": [],
|
||||
"depends_on": [],
|
||||
"related_to": [],
|
||||
"notes": "",
|
||||
"compliance": {
|
||||
"CIS-1.4": [
|
||||
"1.1"
|
||||
],
|
||||
"CIS-1.5": [
|
||||
"1.1"
|
||||
],
|
||||
"KISA-ISMS-P-2023": [
|
||||
"2.1.3"
|
||||
],
|
||||
"CIS-2.0": [
|
||||
"1.1"
|
||||
],
|
||||
"KISA-ISMS-P-2023-korean": [
|
||||
"2.1.3"
|
||||
],
|
||||
"AWS-Well-Architected-Framework-Security-Pillar": [
|
||||
"SEC03-BP03",
|
||||
"SEC10-BP01"
|
||||
],
|
||||
"AWS-Account-Security-Onboarding": [
|
||||
"Billing, emergency, security contacts"
|
||||
],
|
||||
"CIS-3.0": [
|
||||
"1.1"
|
||||
],
|
||||
"ENS-RD2022": [
|
||||
"op.ext.7.aws.am.1"
|
||||
]
|
||||
}
|
||||
},
|
||||
"activity_name": "Create",
|
||||
"activity_id": 1,
|
||||
"finding_info": {
|
||||
"created_time": 1739539623,
|
||||
"created_time_dt": "2025-02-14T14:27:03.913874",
|
||||
"desc": "Maintain current contact details.",
|
||||
"product_uid": "prowler",
|
||||
"title": "Maintain current contact details.",
|
||||
"types": [
|
||||
"IAM"
|
||||
],
|
||||
"uid": "<finding_uid>"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"cloud_partition": "aws",
|
||||
"region": "<region>",
|
||||
"data": {
|
||||
"details": "",
|
||||
"metadata": {
|
||||
"type": "PRIMARY",
|
||||
"email": null,
|
||||
"name": "<account_name>",
|
||||
"phone_number": "<value>"
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"name": "account"
|
||||
},
|
||||
"labels": [],
|
||||
"name": "<account_uid>",
|
||||
"type": "Other",
|
||||
"uid": "arn:aws:iam::<account_uid>:root"
|
||||
}
|
||||
],
|
||||
"category_name": "Findings",
|
||||
"category_uid": 2,
|
||||
"class_name": "Detection Finding",
|
||||
"class_uid": 2004,
|
||||
"cloud": {
|
||||
"account": {
|
||||
"name": "",
|
||||
"type": "AWS Account",
|
||||
"type_id": 10,
|
||||
"uid": "<account_uid>",
|
||||
"labels": []
|
||||
},
|
||||
"org": {
|
||||
"name": "",
|
||||
"uid": ""
|
||||
},
|
||||
"provider": "aws",
|
||||
"region": "<region>"
|
||||
},
|
||||
"remediation": {
|
||||
"desc": "Using the Billing and Cost Management console complete contact details.",
|
||||
"references": [
|
||||
"No command available.",
|
||||
"https://docs.prowler.com/checks/aws/iam-policies/iam_18-maintain-contact-details#aws-console",
|
||||
"https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-update-contact.html"
|
||||
]
|
||||
},
|
||||
"risk_details": "Ensure contact email and telephone details for AWS accounts are current and map to more than one individual in your organization. An AWS account supports a number of contact details, and AWS will use these to contact the account owner if activity judged to be in breach of Acceptable Use Policy. If an AWS account is observed to be behaving in a prohibited or suspicious manner, AWS will attempt to contact the account owner by email and phone using the contact details listed. If this is unsuccessful and the account behavior needs urgent mitigation, proactive measures may be taken, including throttling of traffic between the account exhibiting suspicious behavior and the AWS API endpoints and the Internet. This will result in impaired service to and from the account in question.",
|
||||
"time": 1739539623,
|
||||
"time_dt": "2025-02-14T14:27:03.913874",
|
||||
"type_uid": 200401,
|
||||
"type_name": "Detection Finding: Create"
|
||||
},
|
||||
{
|
||||
"message": "SECURITY, BILLING and OPERATIONS contacts not found or they are not different between each other and between ROOT contact.",
|
||||
"metadata": {
|
||||
"event_code": "account_maintain_different_contact_details_to_security_billing_and_operations",
|
||||
"product": {
|
||||
"name": "Prowler",
|
||||
"uid": "prowler",
|
||||
"vendor_name": "Prowler",
|
||||
"version": "<prowler_version>"
|
||||
},
|
||||
"profiles": [
|
||||
"cloud",
|
||||
"datetime"
|
||||
],
|
||||
"tenant_uid": "",
|
||||
"version": "1.4.0"
|
||||
},
|
||||
"severity_id": 3,
|
||||
"severity": "Medium",
|
||||
"status": "New",
|
||||
"status_code": "FAIL",
|
||||
"status_detail": "SECURITY, BILLING and OPERATIONS contacts not found or they are not different between each other and between ROOT contact.",
|
||||
"status_id": 1,
|
||||
"unmapped": {
|
||||
"related_url": "https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-update-contact.html",
|
||||
"categories": [],
|
||||
"depends_on": [],
|
||||
"related_to": [],
|
||||
"notes": "",
|
||||
"compliance": {
|
||||
"KISA-ISMS-P-2023": [
|
||||
"2.1.3"
|
||||
],
|
||||
"KISA-ISMS-P-2023-korean": [
|
||||
"2.1.3"
|
||||
]
|
||||
}
|
||||
},
|
||||
"activity_name": "Create",
|
||||
"activity_id": 1,
|
||||
"finding_info": {
|
||||
"created_time": 1739539623,
|
||||
"created_time_dt": "2025-02-14T14:27:03.913874",
|
||||
"desc": "Maintain different contact details to security, billing and operations.",
|
||||
"product_uid": "prowler",
|
||||
"title": "Maintain different contact details to security, billing and operations.",
|
||||
"types": [
|
||||
"IAM"
|
||||
],
|
||||
"uid": "<finding_uid>"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"cloud_partition": "aws",
|
||||
"region": "<region>",
|
||||
"data": {
|
||||
"details": "",
|
||||
"metadata": {
|
||||
"type": "PRIMARY",
|
||||
"email": null,
|
||||
"name": "<account_name>",
|
||||
"phone_number": "<value>"
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"name": "account"
|
||||
},
|
||||
"labels": [],
|
||||
"name": "<account_uid>",
|
||||
"type": "Other",
|
||||
"uid": "arn:aws:iam::<account_uid>:root"
|
||||
}
|
||||
],
|
||||
"category_name": "Findings",
|
||||
"category_uid": 2,
|
||||
"class_name": "Detection Finding",
|
||||
"class_uid": 2004,
|
||||
"cloud": {
|
||||
"account": {
|
||||
"name": "",
|
||||
"type": "AWS Account",
|
||||
"type_id": 10,
|
||||
"uid": "<account_uid>",
|
||||
"labels": []
|
||||
},
|
||||
"org": {
|
||||
"name": "",
|
||||
"uid": ""
|
||||
},
|
||||
"provider": "aws",
|
||||
"region": "<region>"
|
||||
},
|
||||
"remediation": {
|
||||
"desc": "Using the Billing and Cost Management console complete contact details.",
|
||||
"references": [
|
||||
"https://docs.prowler.com/checks/aws/iam-policies/iam_18-maintain-contact-details#aws-console",
|
||||
"https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-update-contact.html"
|
||||
]
|
||||
},
|
||||
"risk_details": "Ensure contact email and telephone details for AWS accounts are current and map to more than one individual in your organization. An AWS account supports a number of contact details, and AWS will use these to contact the account owner if activity judged to be in breach of Acceptable Use Policy. If an AWS account is observed to be behaving in a prohibited or suspicious manner, AWS will attempt to contact the account owner by email and phone using the contact details listed. If this is unsuccessful and the account behavior needs urgent mitigation, proactive measures may be taken, including throttling of traffic between the account exhibiting suspicious behavior and the AWS API endpoints and the Internet. This will result in impaired service to and from the account in question.",
|
||||
"time": 1739539623,
|
||||
"time_dt": "2025-02-14T14:27:03.913874",
|
||||
"type_uid": 200401,
|
||||
"type_name": "Detection Finding: Create"
|
||||
},
|
||||
{
|
||||
"message": "Login to the AWS Console. Choose your account name on the top right of the window -> My Account -> Alternate Contacts -> Security Section.",
|
||||
"metadata": {
|
||||
"event_code": "account_security_contact_information_is_registered",
|
||||
"product": {
|
||||
"name": "Prowler",
|
||||
"uid": "prowler",
|
||||
"vendor_name": "Prowler",
|
||||
"version": "<prowler_version>"
|
||||
},
|
||||
"profiles": [
|
||||
"cloud",
|
||||
"datetime"
|
||||
],
|
||||
"tenant_uid": "",
|
||||
"version": "1.4.0"
|
||||
},
|
||||
"severity_id": 3,
|
||||
"severity": "Medium",
|
||||
"status": "New",
|
||||
"status_code": "MANUAL",
|
||||
"status_detail": "Login to the AWS Console. Choose your account name on the top right of the window -> My Account -> Alternate Contacts -> Security Section.",
|
||||
"status_id": 1,
|
||||
"unmapped": {
|
||||
"related_url": "",
|
||||
"categories": [],
|
||||
"depends_on": [],
|
||||
"related_to": [],
|
||||
"notes": "",
|
||||
"compliance": {
|
||||
"CIS-1.4": [
|
||||
"1.2"
|
||||
],
|
||||
"CIS-1.5": [
|
||||
"1.2"
|
||||
],
|
||||
"AWS-Foundational-Security-Best-Practices": [
|
||||
"account",
|
||||
"acm"
|
||||
],
|
||||
"KISA-ISMS-P-2023": [
|
||||
"2.1.3",
|
||||
"2.2.1"
|
||||
],
|
||||
"CIS-2.0": [
|
||||
"1.2"
|
||||
],
|
||||
"KISA-ISMS-P-2023-korean": [
|
||||
"2.1.3",
|
||||
"2.2.1"
|
||||
],
|
||||
"AWS-Well-Architected-Framework-Security-Pillar": [
|
||||
"SEC03-BP03",
|
||||
"SEC10-BP01"
|
||||
],
|
||||
"AWS-Account-Security-Onboarding": [
|
||||
"Billing, emergency, security contacts"
|
||||
],
|
||||
"CIS-3.0": [
|
||||
"1.2"
|
||||
],
|
||||
"ENS-RD2022": [
|
||||
"op.ext.7.aws.am.1"
|
||||
]
|
||||
}
|
||||
},
|
||||
"activity_name": "Create",
|
||||
"activity_id": 1,
|
||||
"finding_info": {
|
||||
"created_time": 1739539623,
|
||||
"created_time_dt": "2025-02-14T14:27:03.913874",
|
||||
"desc": "Ensure security contact information is registered.",
|
||||
"product_uid": "prowler",
|
||||
"title": "Ensure security contact information is registered.",
|
||||
"types": [
|
||||
"IAM"
|
||||
],
|
||||
"uid": "<finding_uid>"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"cloud_partition": "aws",
|
||||
"region": "<region>",
|
||||
"data": {
|
||||
"details": "",
|
||||
"metadata": {
|
||||
"type": "PRIMARY",
|
||||
"email": null,
|
||||
"name": "<account_name>",
|
||||
"phone_number": "<value>"
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"name": "account"
|
||||
},
|
||||
"labels": [],
|
||||
"name": "<account_uid>",
|
||||
"type": "Other",
|
||||
"uid": "arn:aws:iam::<account_uid>:root"
|
||||
}
|
||||
],
|
||||
"category_name": "Findings",
|
||||
"category_uid": 2,
|
||||
"class_name": "Detection Finding",
|
||||
"class_uid": 2004,
|
||||
"cloud": {
|
||||
"account": {
|
||||
"name": "",
|
||||
"type": "AWS Account",
|
||||
"type_id": 10,
|
||||
"uid": "<account_uid>",
|
||||
"labels": []
|
||||
},
|
||||
"org": {
|
||||
"name": "",
|
||||
"uid": ""
|
||||
},
|
||||
"provider": "aws",
|
||||
"region": "<region>"
|
||||
},
|
||||
"remediation": {
|
||||
"desc": "Go to the My Account section and complete alternate contacts.",
|
||||
"references": [
|
||||
"No command available.",
|
||||
"https://docs.prowler.com/checks/aws/iam-policies/iam_19#aws-console",
|
||||
"https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-update-contact.html"
|
||||
]
|
||||
},
|
||||
"risk_details": "AWS provides customers with the option of specifying the contact information for accounts security team. It is recommended that this information be provided. Specifying security-specific contact information will help ensure that security advisories sent by AWS reach the team in your organization that is best equipped to respond to them.",
|
||||
"time": 1739539623,
|
||||
"time_dt": "2025-02-14T14:27:03.913874",
|
||||
"type_uid": 200401,
|
||||
"type_name": "Detection Finding: Create"
|
||||
},
|
||||
{
|
||||
"message": "Login to the AWS Console as root. Choose your account name on the top right of the window -> My Account -> Configure Security Challenge Questions.",
|
||||
"metadata": {
|
||||
"event_code": "account_security_questions_are_registered_in_the_aws_account",
|
||||
"product": {
|
||||
"name": "Prowler",
|
||||
"uid": "prowler",
|
||||
"vendor_name": "Prowler",
|
||||
"version": "<prowler_version>"
|
||||
},
|
||||
"profiles": [
|
||||
"cloud",
|
||||
"datetime"
|
||||
],
|
||||
"tenant_uid": "",
|
||||
"version": "1.4.0"
|
||||
},
|
||||
"severity_id": 3,
|
||||
"severity": "Medium",
|
||||
"status": "New",
|
||||
"status_code": "MANUAL",
|
||||
"status_detail": "Login to the AWS Console as root. Choose your account name on the top right of the window -> My Account -> Configure Security Challenge Questions.",
|
||||
"status_id": 1,
|
||||
"unmapped": {
|
||||
"related_url": "",
|
||||
"categories": [],
|
||||
"depends_on": [],
|
||||
"related_to": [],
|
||||
"notes": "",
|
||||
"compliance": {
|
||||
"CIS-1.4": [
|
||||
"1.3"
|
||||
],
|
||||
"CIS-1.5": [
|
||||
"1.3"
|
||||
],
|
||||
"KISA-ISMS-P-2023": [
|
||||
"2.1.3"
|
||||
],
|
||||
"CIS-2.0": [
|
||||
"1.3"
|
||||
],
|
||||
"KISA-ISMS-P-2023-korean": [
|
||||
"2.1.3"
|
||||
],
|
||||
"AWS-Well-Architected-Framework-Security-Pillar": [
|
||||
"SEC03-BP03",
|
||||
"SEC10-BP01"
|
||||
],
|
||||
"CIS-3.0": [
|
||||
"1.3"
|
||||
],
|
||||
"ENS-RD2022": [
|
||||
"op.ext.7.aws.am.1"
|
||||
]
|
||||
}
|
||||
},
|
||||
"activity_name": "Create",
|
||||
"activity_id": 1,
|
||||
"finding_info": {
|
||||
"created_time": 1739539623,
|
||||
"created_time_dt": "2025-02-14T14:27:03.913874",
|
||||
"desc": "Ensure security questions are registered in the AWS account.",
|
||||
"product_uid": "prowler",
|
||||
"title": "Ensure security questions are registered in the AWS account.",
|
||||
"types": [
|
||||
"IAM"
|
||||
],
|
||||
"uid": "<finding_uid>"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"cloud_partition": "aws",
|
||||
"region": "<region>",
|
||||
"data": {
|
||||
"details": "",
|
||||
"metadata": {
|
||||
"type": "SECURITY",
|
||||
"email": null,
|
||||
"name": null,
|
||||
"phone_number": null
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"name": "account"
|
||||
},
|
||||
"labels": [],
|
||||
"name": "<account_uid>",
|
||||
"type": "Other",
|
||||
"uid": "arn:aws:iam::<account_uid>:root"
|
||||
}
|
||||
],
|
||||
"category_name": "Findings",
|
||||
"category_uid": 2,
|
||||
"class_name": "Detection Finding",
|
||||
"class_uid": 2004,
|
||||
"cloud": {
|
||||
"account": {
|
||||
"name": "",
|
||||
"type": "AWS Account",
|
||||
"type_id": 10,
|
||||
"uid": "<account_uid>",
|
||||
"labels": []
|
||||
},
|
||||
"org": {
|
||||
"name": "",
|
||||
"uid": ""
|
||||
},
|
||||
"provider": "aws",
|
||||
"region": "<region>"
|
||||
},
|
||||
"remediation": {
|
||||
"desc": "Login as root account and from My Account configure Security questions.",
|
||||
"references": [
|
||||
"No command available.",
|
||||
"https://docs.prowler.com/checks/aws/iam-policies/iam_15",
|
||||
"https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-security-challenge.html"
|
||||
]
|
||||
},
|
||||
"risk_details": "The AWS support portal allows account owners to establish security questions that can be used to authenticate individuals calling AWS customer service for support. It is recommended that security questions be established. When creating a new AWS account a default super user is automatically created. This account is referred to as the root account. It is recommended that the use of this account be limited and highly controlled. During events in which the root password is no longer accessible or the MFA token associated with root is lost/destroyed it is possible through authentication using secret questions and associated answers to recover root login access.",
|
||||
"time": 1739539623,
|
||||
"time_dt": "2025-02-14T14:27:03.913874",
|
||||
"type_uid": 200401,
|
||||
"type_name": "Detection Finding: Create"
|
||||
}
|
||||
]
|
||||
@@ -1,5 +0,0 @@
|
||||
AUTH_METHOD;TIMESTAMP;ACCOUNT_UID;ACCOUNT_NAME;ACCOUNT_EMAIL;ACCOUNT_ORGANIZATION_UID;ACCOUNT_ORGANIZATION_NAME;ACCOUNT_TAGS;FINDING_UID;PROVIDER;CHECK_ID;CHECK_TITLE;CHECK_TYPE;STATUS;STATUS_EXTENDED;MUTED;SERVICE_NAME;SUBSERVICE_NAME;SEVERITY;RESOURCE_TYPE;RESOURCE_UID;RESOURCE_NAME;RESOURCE_DETAILS;RESOURCE_TAGS;PARTITION;REGION;DESCRIPTION;RISK;RELATED_URL;REMEDIATION_RECOMMENDATION_TEXT;REMEDIATION_RECOMMENDATION_URL;REMEDIATION_CODE_NATIVEIAC;REMEDIATION_CODE_TERRAFORM;REMEDIATION_CODE_CLI;REMEDIATION_CODE_OTHER;COMPLIANCE;CATEGORIES;DEPENDS_ON;RELATED_TO;NOTES;PROWLER_VERSION
|
||||
<auth_method>;2025-02-14 14:27:30.710664;<account_uid>;<account_name>;;<account_organization_uid>;ProwlerPro.onmicrosoft.com;;<finding_uid>;azure;aks_cluster_rbac_enabled;Ensure AKS RBAC is enabled;;PASS;RBAC is enabled for cluster '<resource_name>' in subscription '<account_name>'.;False;aks;;medium;Microsoft.ContainerService/ManagedClusters;/subscriptions/<account_uid>/resourcegroups/<resource_name>_group/providers/Microsoft.ContainerService/managedClusters/<resource_name>;<resource_name>;;;<partition>;<region>;Azure Kubernetes Service (AKS) can be configured to use Azure Active Directory (AD) for user authentication. In this configuration, you sign in to an AKS cluster using an Azure AD authentication token. You can also configure Kubernetes role-based access control (Kubernetes RBAC) to limit access to cluster resources based a user's identity or group membership.;Kubernetes RBAC and AKS help you secure your cluster access and provide only the minimum required permissions to developers and operators.;https://learn.microsoft.com/en-us/azure/aks/azure-ad-rbac?tabs=portal;;https://learn.microsoft.com/en-us/security/benchmark/azure/security-controls-v2-privileged-access#pa-7-follow-just-enough-administration-least-privilege-principle;;https://docs.prowler.com/checks/azure/azure-kubernetes-policies/bc_azr_kubernetes_2#terraform;;https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/AKS/enable-role-based-access-control-for-kubernetes-service.html#;ENS-RD2022: op.acc.2.az.r1.eid.1;;;;;<prowler_version>
|
||||
<auth_method>;2025-02-14 14:27:30.710664;<account_uid>;<account_name>;;<account_organization_uid>;ProwlerPro.onmicrosoft.com;;<finding_uid>;azure;aks_clusters_created_with_private_nodes;Ensure clusters are created with Private Nodes;;PASS;Cluster '<resource_name>' was created with private nodes in subscription '<account_name>';False;aks;;high;Microsoft.ContainerService/ManagedClusters;/subscriptions/<account_uid>/resourcegroups/<resource_name>_group/providers/Microsoft.ContainerService/managedClusters/<resource_name>;<resource_name>;;;<partition>;<region>;Disable public IP addresses for cluster nodes, so that they only have private IP addresses. Private Nodes are nodes with no public IP addresses.;Disabling public IP addresses on cluster nodes restricts access to only internal networks, forcing attackers to obtain local network access before attempting to compromise the underlying Kubernetes hosts.;https://learn.microsoft.com/en-us/azure/aks/private-clusters;;https://learn.microsoft.com/en-us/azure/aks/access-private-cluster;;;;;ENS-RD2022: mp.com.4.r2.az.aks.1 | MITRE-ATTACK: T1190, T1530;;;;;<prowler_version>
|
||||
<auth_method>;2025-02-14 14:27:30.710664;<account_uid>;<account_name>;;<account_organization_uid>;ProwlerPro.onmicrosoft.com;;<finding_uid>;azure;aks_clusters_public_access_disabled;Ensure clusters are created with Private Endpoint Enabled and Public Access Disabled;;FAIL;Public access to nodes is enabled for cluster '<resource_name>' in subscription '<account_name>';False;aks;;high;Microsoft.ContainerService/ManagedClusters;/subscriptions/<account_uid>/resourcegroups/<resource_name>_group/providers/Microsoft.ContainerService/managedClusters/<resource_name>;<resource_name>;;;<partition>;<region>;Disable access to the Kubernetes API from outside the node network if it is not required.;In a private cluster, the master node has two endpoints, a private and public endpoint. The private endpoint is the internal IP address of the master, behind an internal load balancer in the master's wirtual network. Nodes communicate with the master using the private endpoint. The public endpoint enables the Kubernetes API to be accessed from outside the master's virtual network. Although Kubernetes API requires an authorized token to perform sensitive actions, a vulnerability could potentially expose the Kubernetes publically with unrestricted access. Additionally, an attacker may be able to identify the current cluster and Kubernetes API version and determine whether it is vulnerable to an attack. Unless required, disabling public endpoint will help prevent such threats, and require the attacker to be on the master's virtual network to perform any attack on the Kubernetes API.;https://learn.microsoft.com/en-us/azure/aks/private-clusters?tabs=azure-portal;To use a private endpoint, create a new private endpoint in your virtual network then create a link between your virtual network and a new private DNS zone;https://learn.microsoft.com/en-us/azure/aks/access-private-cluster?tabs=azure-cli;;;az aks update -n <cluster_name> -g <resource_group> --disable-public-fqdn;;ENS-RD2022: mp.com.4.az.aks.2 | MITRE-ATTACK: T1190, T1530;;;;;<prowler_version>
|
||||
<auth_method>;2025-02-14 14:27:30.710664;<account_uid>;<account_name>;;<account_organization_uid>;ProwlerPro.onmicrosoft.com;;<finding_uid>;azure;aks_network_policy_enabled;Ensure Network Policy is Enabled and set as appropriate;;PASS;Network policy is enabled for cluster '<resource_name>' in subscription '<account_name>'.;False;aks;;medium;Microsoft.ContainerService/managedClusters;/subscriptions/<account_uid>/resourcegroups/<resource_name>_group/providers/Microsoft.ContainerService/managedClusters/<resource_name>;<resource_name>;;;<partition>;<region>;When you run modern, microservices-based applications in Kubernetes, you often want to control which components can communicate with each other. The principle of least privilege should be applied to how traffic can flow between pods in an Azure Kubernetes Service (AKS) cluster. Let's say you likely want to block traffic directly to back-end applications. The Network Policy feature in Kubernetes lets you define rules for ingress and egress traffic between pods in a cluster.;All pods in an AKS cluster can send and receive traffic without limitations, by default. To improve security, you can define rules that control the flow of traffic. Back-end applications are often only exposed to required front-end services, for example. Or, database components are only accessible to the application tiers that connect to them. Network Policy is a Kubernetes specification that defines access policies for communication between Pods. Using Network Policies, you define an ordered set of rules to send and receive traffic and apply them to a collection of pods that match one or more label selectors. These network policy rules are defined as YAML manifests. Network policies can be included as part of a wider manifest that also creates a deployment or service.;https://learn.microsoft.com/en-us/security/benchmark/azure/security-controls-v2-network-security#ns-2-connect-private-networks-together;;https://learn.microsoft.com/en-us/azure/aks/use-network-policies;;https://docs.prowler.com/checks/azure/azure-kubernetes-policies/bc_azr_kubernetes_4#terraform;;;ENS-RD2022: mp.com.4.r2.az.aks.1;;;;Network Policy requires the Network Policy add-on. This add-on is included automatically when a cluster with Network Policy is created, but for an existing cluster, needs to be added prior to enabling Network Policy. Enabling/Disabling Network Policy causes a rolling update of all cluster nodes, similar to performing a cluster upgrade. This operation is long-running and will block other operations on the cluster (including delete) until it has run to completion. If Network Policy is used, a cluster must have at least 2 nodes of type n1-standard-1 or higher. The recommended minimum size cluster to run Network Policy enforcement is 3 n1-standard-1 instances. Enabling Network Policy enforcement consumes additional resources in nodes. Specifically, it increases the memory footprint of the kube-system process by approximately 128MB, and requires approximately 300 millicores of CPU.;<prowler_version>
|
||||
|
@@ -1,552 +0,0 @@
|
||||
[
|
||||
{
|
||||
"message": "There are no AppInsight configured in subscription <subscription_name>.",
|
||||
"metadata": {
|
||||
"event_code": "appinsights_ensure_is_configured",
|
||||
"product": {
|
||||
"name": "Prowler",
|
||||
"uid": "prowler",
|
||||
"vendor_name": "Prowler",
|
||||
"version": "5.4.0"
|
||||
},
|
||||
"profiles": [
|
||||
"cloud",
|
||||
"datetime"
|
||||
],
|
||||
"tenant_uid": "<tenant_uid>",
|
||||
"version": "1.4.0"
|
||||
},
|
||||
"severity_id": 2,
|
||||
"severity": "Low",
|
||||
"status": "New",
|
||||
"status_code": "FAIL",
|
||||
"status_detail": "There are no AppInsight configured in subscription <subscription_name>.",
|
||||
"status_id": 1,
|
||||
"unmapped": {
|
||||
"related_url": "https://learn.microsoft.com/en-us/azure/azure-monitor/app/app-insights-overview",
|
||||
"categories": [],
|
||||
"depends_on": [],
|
||||
"related_to": [],
|
||||
"notes": "Because Application Insights relies on a Log Analytics Workspace, an organization will incur additional expenses when using this service.",
|
||||
"compliance": {
|
||||
"CIS-2.1": [
|
||||
"5.3.1"
|
||||
],
|
||||
"ENS-RD2022": [
|
||||
"mp.s.4.r1.az.nt.2"
|
||||
],
|
||||
"CIS-3.0": [
|
||||
"6.3.1"
|
||||
],
|
||||
"CIS-2.0": [
|
||||
"5.3.1"
|
||||
]
|
||||
}
|
||||
},
|
||||
"activity_name": "Create",
|
||||
"activity_id": 1,
|
||||
"finding_info": {
|
||||
"created_time": 1739539650,
|
||||
"created_time_dt": "2025-02-14T14:27:30.710664",
|
||||
"desc": "Application Insights within Azure act as an Application Performance Monitoring solution providing valuable data into how well an application performs and additional information when performing incident response. The types of log data collected include application metrics, telemetry data, and application trace logging data providing organizations with detailed information about application activity and application transactions. Both data sets help organizations adopt a proactive and retroactive means to handle security and performance related metrics within their modern applications.",
|
||||
"product_uid": "prowler",
|
||||
"title": "Ensure Application Insights are Configured.",
|
||||
"types": [],
|
||||
"uid": "<finding_uid>"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"cloud_partition": "AzureCloud",
|
||||
"region": "global",
|
||||
"data": {
|
||||
"details": "",
|
||||
"metadata": {}
|
||||
},
|
||||
"group": {
|
||||
"name": "appinsights"
|
||||
},
|
||||
"labels": [],
|
||||
"name": "AppInsights",
|
||||
"type": "Microsoft.Insights/components",
|
||||
"uid": "AppInsights"
|
||||
}
|
||||
],
|
||||
"category_name": "Findings",
|
||||
"category_uid": 2,
|
||||
"class_name": "Detection Finding",
|
||||
"class_uid": 2004,
|
||||
"cloud": {
|
||||
"account": {
|
||||
"name": "<subscription_name>",
|
||||
"type": "Azure AD Account",
|
||||
"type_id": 6,
|
||||
"uid": "<subscription_uid>",
|
||||
"labels": []
|
||||
},
|
||||
"org": {
|
||||
"name": "<organization_name>",
|
||||
"uid": "<tenant_uid>"
|
||||
},
|
||||
"provider": "azure",
|
||||
"region": "global"
|
||||
},
|
||||
"remediation": {
|
||||
"desc": "1. Navigate to Application Insights 2. Under the Basics tab within the PROJECT DETAILS section, select the Subscription 3. Select the Resource group 4. Within the INSTANCE DETAILS, enter a Name 5. Select a Region 6. Next to Resource Mode, select Workspace-based 7. Within the WORKSPACE DETAILS, select the Subscription for the log analytics workspace 8. Select the appropriate Log Analytics Workspace 9. Click Next:Tags > 10. Enter the appropriate Tags as Name, Value pairs. 11. Click Next:Review+Create 12. Click Create.",
|
||||
"references": [
|
||||
"az monitor app-insights component create --app <app name> --resource-group <resource group name> --location <location> --kind 'web' --retention-time <INT days to retain logs> --workspace <log analytics workspace ID> -- subscription <subscription ID>",
|
||||
"https://www.tenable.com/audits/items/CIS_Microsoft_Azure_Foundations_v2.0.0_L2.audit:8a7a608d180042689ad9d3f16aa359f1"
|
||||
]
|
||||
},
|
||||
"risk_details": "Configuring Application Insights provides additional data not found elsewhere within Azure as part of a much larger logging and monitoring program within an organization's Information Security practice. The types and contents of these logs will act as both a potential cost saving measure (application performance) and a means to potentially confirm the source of a potential incident (trace logging). Metrics and Telemetry data provide organizations with a proactive approach to cost savings by monitoring an application's performance, while the trace logging data provides necessary details in a reactive incident response scenario by helping organizations identify the potential source of an incident within their application.",
|
||||
"time": 1739539650,
|
||||
"time_dt": "2025-02-14T14:27:30.710664",
|
||||
"type_uid": 200401,
|
||||
"type_name": "Detection Finding: Create"
|
||||
},
|
||||
{
|
||||
"message": "There is not another correct email configured for subscription <subscription_name>.",
|
||||
"metadata": {
|
||||
"event_code": "defender_additional_email_configured_with_a_security_contact",
|
||||
"product": {
|
||||
"name": "Prowler",
|
||||
"uid": "prowler",
|
||||
"vendor_name": "Prowler",
|
||||
"version": "5.4.0"
|
||||
},
|
||||
"profiles": [
|
||||
"cloud",
|
||||
"datetime"
|
||||
],
|
||||
"tenant_uid": "<tenant_uid>",
|
||||
"version": "1.4.0"
|
||||
},
|
||||
"severity_id": 3,
|
||||
"severity": "Medium",
|
||||
"status": "New",
|
||||
"status_code": "FAIL",
|
||||
"status_detail": "There is not another correct email configured for subscription <subscription_name>.",
|
||||
"status_id": 1,
|
||||
"unmapped": {
|
||||
"related_url": "https://docs.microsoft.com/en-us/azure/security-center/security-center-provide-security-contact-details",
|
||||
"categories": [],
|
||||
"depends_on": [],
|
||||
"related_to": [],
|
||||
"notes": "",
|
||||
"compliance": {
|
||||
"CIS-2.1": [
|
||||
"2.1.18"
|
||||
],
|
||||
"ENS-RD2022": [
|
||||
"op.mon.3.r3.az.de.1"
|
||||
],
|
||||
"CIS-3.0": [
|
||||
"3.1.13"
|
||||
],
|
||||
"CIS-2.0": [
|
||||
"2.1.19"
|
||||
]
|
||||
}
|
||||
},
|
||||
"activity_name": "Create",
|
||||
"activity_id": 1,
|
||||
"finding_info": {
|
||||
"created_time": 1739539650,
|
||||
"created_time_dt": "2025-02-14T14:27:30.710664",
|
||||
"desc": "Microsoft Defender for Cloud emails the subscription owners whenever a high-severity alert is triggered for their subscription. You should provide a security contact email address as an additional email address.",
|
||||
"product_uid": "prowler",
|
||||
"title": "Ensure 'Additional email addresses' is Configured with a Security Contact Email",
|
||||
"types": [],
|
||||
"uid": "<finding_uid>"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"cloud_partition": "AzureCloud",
|
||||
"region": "global",
|
||||
"data": {
|
||||
"details": "",
|
||||
"metadata": {
|
||||
"resource_id": "<resource_uid>",
|
||||
"name": "<resource_name>",
|
||||
"emails": "",
|
||||
"phone": "",
|
||||
"alert_notifications_minimal_severity": "High",
|
||||
"alert_notifications_state": "On",
|
||||
"notified_roles": [
|
||||
"Owner"
|
||||
],
|
||||
"notified_roles_state": "On"
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"name": "defender"
|
||||
},
|
||||
"labels": [],
|
||||
"name": "<resource_name>",
|
||||
"type": "AzureEmailNotifications",
|
||||
"uid": "<resource_uid>"
|
||||
}
|
||||
],
|
||||
"category_name": "Findings",
|
||||
"category_uid": 2,
|
||||
"class_name": "Detection Finding",
|
||||
"class_uid": 2004,
|
||||
"cloud": {
|
||||
"account": {
|
||||
"name": "<subscription_name>",
|
||||
"type": "Azure AD Account",
|
||||
"type_id": 6,
|
||||
"uid": "<subscription_uid>",
|
||||
"labels": []
|
||||
},
|
||||
"org": {
|
||||
"name": "<organization_name>",
|
||||
"uid": "<tenant_uid>"
|
||||
},
|
||||
"provider": "azure",
|
||||
"region": "global"
|
||||
},
|
||||
"remediation": {
|
||||
"desc": "1. From Azure Home select the Portal Menu 2. Select Microsoft Defender for Cloud 3. Click on Environment Settings 4. Click on the appropriate Management Group, Subscription, or Workspace 5. Click on Email notifications 6. Enter a valid security contact email address (or multiple addresses separated by commas) in the Additional email addresses field 7. Click Save",
|
||||
"references": [
|
||||
"https://docs.prowler.com/checks/azure/azure-general-policies/ensure-that-security-contact-emails-is-set#terraform",
|
||||
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/SecurityCenter/security-contact-email.html",
|
||||
"https://learn.microsoft.com/en-us/rest/api/defenderforcloud/security-contacts/list?view=rest-defenderforcloud-2020-01-01-preview&tabs=HTTP"
|
||||
]
|
||||
},
|
||||
"risk_details": "Microsoft Defender for Cloud emails the Subscription Owner to notify them about security alerts. Adding your Security Contact's email address to the 'Additional email addresses' field ensures that your organization's Security Team is included in these alerts. This ensures that the proper people are aware of any potential compromise in order to mitigate the risk in a timely fashion.",
|
||||
"time": 1739539650,
|
||||
"time_dt": "2025-02-14T14:27:30.710664",
|
||||
"type_uid": 200401,
|
||||
"type_name": "Detection Finding: Create"
|
||||
},
|
||||
{
|
||||
"message": "Defender Auto Provisioning Log Analytics Agents from subscription <subscription_name> is set to OFF.",
|
||||
"metadata": {
|
||||
"event_code": "defender_auto_provisioning_log_analytics_agent_vms_on",
|
||||
"product": {
|
||||
"name": "Prowler",
|
||||
"uid": "prowler",
|
||||
"vendor_name": "Prowler",
|
||||
"version": "5.4.0"
|
||||
},
|
||||
"profiles": [
|
||||
"cloud",
|
||||
"datetime"
|
||||
],
|
||||
"tenant_uid": "<tenant_uid>",
|
||||
"version": "1.4.0"
|
||||
},
|
||||
"severity_id": 3,
|
||||
"severity": "Medium",
|
||||
"status": "New",
|
||||
"status_code": "FAIL",
|
||||
"status_detail": "Defender Auto Provisioning Log Analytics Agents from subscription <subscription_name> is set to OFF.",
|
||||
"status_id": 1,
|
||||
"unmapped": {
|
||||
"related_url": "https://docs.microsoft.com/en-us/azure/security-center/security-center-data-security",
|
||||
"categories": [],
|
||||
"depends_on": [],
|
||||
"related_to": [],
|
||||
"notes": "",
|
||||
"compliance": {
|
||||
"CIS-2.1": [
|
||||
"2.1.14"
|
||||
],
|
||||
"ENS-RD2022": [
|
||||
"op.mon.3.r2.az.de.1",
|
||||
"mp.s.4.r1.az.nt.5"
|
||||
],
|
||||
"MITRE-ATTACK": [
|
||||
"T1190"
|
||||
],
|
||||
"CIS-3.0": [
|
||||
"3.1.1.1"
|
||||
],
|
||||
"CIS-2.0": [
|
||||
"2.1.15"
|
||||
]
|
||||
}
|
||||
},
|
||||
"activity_name": "Create",
|
||||
"activity_id": 1,
|
||||
"finding_info": {
|
||||
"created_time": 1739539650,
|
||||
"created_time_dt": "2025-02-14T14:27:30.710664",
|
||||
"desc": "Ensure that Auto provisioning of 'Log Analytics agent for Azure VMs' is Set to 'On'. The Microsoft Monitoring Agent scans for various security-related configurations and events such as system updates, OS vulnerabilities, endpoint protection, and provides alerts.",
|
||||
"product_uid": "prowler",
|
||||
"title": "Ensure that Auto provisioning of 'Log Analytics agent for Azure VMs' is Set to 'On'",
|
||||
"types": [],
|
||||
"uid": "<finding_uid>"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"cloud_partition": "AzureCloud",
|
||||
"region": "global",
|
||||
"data": {
|
||||
"details": "",
|
||||
"metadata": {
|
||||
"resource_id": "<resource_uid>",
|
||||
"resource_name": "<resource_name>",
|
||||
"resource_type": "Microsoft.Security/autoProvisioningSettings",
|
||||
"auto_provision": "Off"
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"name": "defender"
|
||||
},
|
||||
"labels": [],
|
||||
"name": "<resource_name>",
|
||||
"type": "AzureDefenderPlan",
|
||||
"uid": "<resource_uid>"
|
||||
}
|
||||
],
|
||||
"category_name": "Findings",
|
||||
"category_uid": 2,
|
||||
"class_name": "Detection Finding",
|
||||
"class_uid": 2004,
|
||||
"cloud": {
|
||||
"account": {
|
||||
"name": "<subscription_name>",
|
||||
"type": "Azure AD Account",
|
||||
"type_id": 6,
|
||||
"uid": "<subscription_uid>",
|
||||
"labels": []
|
||||
},
|
||||
"org": {
|
||||
"name": "<organization_name>",
|
||||
"uid": "<tenant_uid>"
|
||||
},
|
||||
"provider": "azure",
|
||||
"region": "global"
|
||||
},
|
||||
"remediation": {
|
||||
"desc": "Ensure comprehensive visibility into possible security vulnerabilities, including missing updates, misconfigured operating system security settings, and active threats, allowing for timely mitigation and improved overall security posture",
|
||||
"references": [
|
||||
"https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/azure/SecurityCenter/automatic-provisioning-of-monitoring-agent.html",
|
||||
"https://learn.microsoft.com/en-us/azure/defender-for-cloud/monitoring-components"
|
||||
]
|
||||
},
|
||||
"risk_details": "Missing critical security information about your Azure VMs, such as security alerts, security recommendations, and change tracking.",
|
||||
"time": 1739539650,
|
||||
"time_dt": "2025-02-14T14:27:30.710664",
|
||||
"type_uid": 200401,
|
||||
"type_name": "Detection Finding: Create"
|
||||
},
|
||||
{
|
||||
"message": "Container image scan is disabled in subscription <subscription_name>.",
|
||||
"metadata": {
|
||||
"event_code": "defender_container_images_scan_enabled",
|
||||
"product": {
|
||||
"name": "Prowler",
|
||||
"uid": "prowler",
|
||||
"vendor_name": "Prowler",
|
||||
"version": "5.4.0"
|
||||
},
|
||||
"profiles": [
|
||||
"cloud",
|
||||
"datetime"
|
||||
],
|
||||
"tenant_uid": "<tenant_uid>",
|
||||
"version": "1.4.0"
|
||||
},
|
||||
"severity_id": 3,
|
||||
"severity": "Medium",
|
||||
"status": "New",
|
||||
"status_code": "FAIL",
|
||||
"status_detail": "Container image scan is disabled in subscription <subscription_name>.",
|
||||
"status_id": 1,
|
||||
"unmapped": {
|
||||
"related_url": "https://learn.microsoft.com/en-us/azure/container-registry/container-registry-check-health",
|
||||
"categories": [],
|
||||
"depends_on": [],
|
||||
"related_to": [],
|
||||
"notes": "When using an Azure container registry, you might occasionally encounter problems. For example, you might not be able to pull a container image because of an issue with Docker in your local environment. Or, a network issue might prevent you from connecting to the registry.",
|
||||
"compliance": {
|
||||
"MITRE-ATTACK": [
|
||||
"T1190",
|
||||
"T1525"
|
||||
]
|
||||
}
|
||||
},
|
||||
"activity_name": "Create",
|
||||
"activity_id": 1,
|
||||
"finding_info": {
|
||||
"created_time": 1739539650,
|
||||
"created_time_dt": "2025-02-14T14:27:30.710664",
|
||||
"desc": "Scan images being deployed to Azure (AKS) for vulnerabilities. Vulnerability scanning for images stored in Azure Container Registry is generally available in Azure Security Center. This capability is powered by Qualys, a leading provider of information security. When you push an image to Container Registry, Security Center automatically scans it, then checks for known vulnerabilities in packages or dependencies defined in the file. When the scan completes (after about 10 minutes), Security Center provides details and a security classification for each vulnerability detected, along with guidance on how to remediate issues and protect vulnerable attack surfaces.",
|
||||
"product_uid": "prowler",
|
||||
"title": "Ensure Image Vulnerability Scanning using Azure Defender image scanning or a third party provider",
|
||||
"types": [],
|
||||
"uid": "prowler-azure-defender_container_images_scan_enabled-<subscription_uid>-global-Dender plan for Containers"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"cloud_partition": "AzureCloud",
|
||||
"region": "global",
|
||||
"data": {
|
||||
"details": "",
|
||||
"metadata": {
|
||||
"resource_id": "<resource_uid>",
|
||||
"pricing_tier": "Free",
|
||||
"free_trial_remaining_time": 2592000.0,
|
||||
"extensions": {}
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"name": "defender"
|
||||
},
|
||||
"labels": [],
|
||||
"name": "<resource_name>",
|
||||
"type": "Microsoft.Security",
|
||||
"uid": "<resource_uid>"
|
||||
}
|
||||
],
|
||||
"category_name": "Findings",
|
||||
"category_uid": 2,
|
||||
"class_name": "Detection Finding",
|
||||
"class_uid": 2004,
|
||||
"cloud": {
|
||||
"account": {
|
||||
"name": "<subscription_name>",
|
||||
"type": "Azure AD Account",
|
||||
"type_id": 6,
|
||||
"uid": "<subscription_uid>",
|
||||
"labels": []
|
||||
},
|
||||
"org": {
|
||||
"name": "<organization_name>",
|
||||
"uid": "<tenant_uid>"
|
||||
},
|
||||
"provider": "azure",
|
||||
"region": "global"
|
||||
},
|
||||
"remediation": {
|
||||
"desc": "",
|
||||
"references": [
|
||||
"https://learn.microsoft.com/en-us/azure/container-registry/scan-images-defender"
|
||||
]
|
||||
},
|
||||
"risk_details": "Vulnerabilities in software packages can be exploited by hackers or malicious users to obtain unauthorized access to local cloud resources. Azure Defender and other third party products allow images to be scanned for known vulnerabilities.",
|
||||
"time": 1739539650,
|
||||
"time_dt": "2025-02-14T14:27:30.710664",
|
||||
"type_uid": 200401,
|
||||
"type_name": "Detection Finding: Create"
|
||||
},
|
||||
{
|
||||
"message": "Defender plan Defender for App Services from subscription <subscription_name> is set to OFF (pricing tier not standard).",
|
||||
"metadata": {
|
||||
"event_code": "defender_ensure_defender_for_app_services_is_on",
|
||||
"product": {
|
||||
"name": "Prowler",
|
||||
"uid": "prowler",
|
||||
"vendor_name": "Prowler",
|
||||
"version": "5.4.0"
|
||||
},
|
||||
"profiles": [
|
||||
"cloud",
|
||||
"datetime"
|
||||
],
|
||||
"tenant_uid": "<tenant_uid>",
|
||||
"version": "1.4.0"
|
||||
},
|
||||
"severity_id": 4,
|
||||
"severity": "High",
|
||||
"status": "New",
|
||||
"status_code": "FAIL",
|
||||
"status_detail": "Defender plan Defender for App Services from subscription <subscription_name> is set to OFF (pricing tier not standard).",
|
||||
"status_id": 1,
|
||||
"unmapped": {
|
||||
"related_url": "",
|
||||
"categories": [],
|
||||
"depends_on": [],
|
||||
"related_to": [],
|
||||
"notes": "",
|
||||
"compliance": {
|
||||
"CIS-2.1": [
|
||||
"2.1.2"
|
||||
],
|
||||
"ENS-RD2022": [
|
||||
"mp.s.4.r1.az.nt.3"
|
||||
],
|
||||
"MITRE-ATTACK": [
|
||||
"T1190",
|
||||
"T1059",
|
||||
"T1204",
|
||||
"T1552",
|
||||
"T1486",
|
||||
"T1499",
|
||||
"T1496",
|
||||
"T1087"
|
||||
],
|
||||
"CIS-3.0": [
|
||||
"3.1.6.1"
|
||||
]
|
||||
}
|
||||
},
|
||||
"activity_name": "Create",
|
||||
"activity_id": 1,
|
||||
"finding_info": {
|
||||
"created_time": 1739539650,
|
||||
"created_time_dt": "2025-02-14T14:27:30.710664",
|
||||
"desc": "Ensure That Microsoft Defender for App Services Is Set To 'On' ",
|
||||
"product_uid": "prowler",
|
||||
"title": "Ensure That Microsoft Defender for App Services Is Set To 'On' ",
|
||||
"types": [],
|
||||
"uid": "<finding_uid>"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"cloud_partition": "AzureCloud",
|
||||
"region": "global",
|
||||
"data": {
|
||||
"details": "",
|
||||
"metadata": {
|
||||
"resource_id": "<resource_uid>",
|
||||
"pricing_tier": "Free",
|
||||
"free_trial_remaining_time": 2592000.0,
|
||||
"extensions": {}
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"name": "defender"
|
||||
},
|
||||
"labels": [],
|
||||
"name": "<resource_name>",
|
||||
"type": "AzureDefenderPlan",
|
||||
"uid": "<resource_uid>"
|
||||
}
|
||||
],
|
||||
"category_name": "Findings",
|
||||
"category_uid": 2,
|
||||
"class_name": "Detection Finding",
|
||||
"class_uid": 2004,
|
||||
"cloud": {
|
||||
"account": {
|
||||
"name": "<subscription_name>",
|
||||
"type": "Azure AD Account",
|
||||
"type_id": 6,
|
||||
"uid": "<subscription_uid>",
|
||||
"labels": []
|
||||
},
|
||||
"org": {
|
||||
"name": "<organization_name>",
|
||||
"uid": "<tenant_uid>"
|
||||
},
|
||||
"provider": "azure",
|
||||
"region": "global"
|
||||
},
|
||||
"remediation": {
|
||||
"desc": "By <resource_name>, Microsoft Defender for Cloud is not enabled for your App Service instances. Enabling the Defender security service for App Service instances allows for advanced security defense using threat detection capabilities provided by Microsoft Security Response Center.",
|
||||
"references": [
|
||||
"https://docs.prowler.com/checks/azure/azure-general-policies/ensure-that-azure-defender-is-set-to-on-for-app-service#terraform",
|
||||
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/SecurityCenter/defender-app-service.html",
|
||||
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/SecurityCenter/defender-app-service.html"
|
||||
]
|
||||
},
|
||||
"risk_details": "Turning on Microsoft Defender for App Service enables threat detection for App Service, providing threat intelligence, anomaly detection, and behavior analytics in the Microsoft Defender for Cloud.",
|
||||
"time": 1739539650,
|
||||
"time_dt": "2025-02-14T14:27:30.710664",
|
||||
"type_uid": 200401,
|
||||
"type_name": "Detection Finding: Create"
|
||||
}
|
||||
]
|
||||
@@ -1,5 +0,0 @@
|
||||
AUTH_METHOD;TIMESTAMP;ACCOUNT_UID;ACCOUNT_NAME;ACCOUNT_EMAIL;ACCOUNT_ORGANIZATION_UID;ACCOUNT_ORGANIZATION_NAME;ACCOUNT_TAGS;FINDING_UID;PROVIDER;CHECK_ID;CHECK_TITLE;CHECK_TYPE;STATUS;STATUS_EXTENDED;MUTED;SERVICE_NAME;SUBSERVICE_NAME;SEVERITY;RESOURCE_TYPE;RESOURCE_UID;RESOURCE_NAME;RESOURCE_DETAILS;RESOURCE_TAGS;PARTITION;REGION;DESCRIPTION;RISK;RELATED_URL;REMEDIATION_RECOMMENDATION_TEXT;REMEDIATION_RECOMMENDATION_URL;REMEDIATION_CODE_NATIVEIAC;REMEDIATION_CODE_TERRAFORM;REMEDIATION_CODE_CLI;REMEDIATION_CODE_OTHER;COMPLIANCE;CATEGORIES;DEPENDS_ON;RELATED_TO;NOTES;PROWLER_VERSION
|
||||
<auth_method>;2025-02-14 14:27:20.697446;<account_uid>;<account_name>;;<account_organization_uid>;<account_organization_name>;<account_tags>;<finding_uid>;gcp;apikeys_key_exists;Ensure API Keys Only Exist for Active Services;;PASS;Project <account_uid> does not have active API Keys.;False;apikeys;;medium;API Key;<account_uid>;<account_name>;;;;<region>;API Keys should only be used for services in cases where other authentication methods are unavailable. Unused keys with their permissions in tact may still exist within a project. Keys are insecure because they can be viewed publicly, such as from within a browser, or they can be accessed on a device where the key resides. It is recommended to use standard authentication flow instead.;Security risks involved in using API-Keys appear below: API keys are simple encrypted strings, API keys do not identify the user or the application making the API request, API keys are typically accessible to clients, making it easy to discover and steal an API key.;;To avoid the security risk in using API keys, it is recommended to use standard authentication flow instead.;https://cloud.google.com/docs/authentication/api-keys;;;gcloud alpha services api-keys delete;;MITRE-ATTACK: T1098 | CIS-2.0: 1.12 | ENS-RD2022: op.acc.2.gcp.rbak.1 | CIS-3.0: 1.12;;;;;<prowler_version>
|
||||
<auth_method>;2025-02-14 14:27:20.697446;<account_uid>;<account_name>;;<account_organization_uid>;<account_organization_name>;<account_tags>;<finding_uid>;gcp;artifacts_container_analysis_enabled;Ensure Image Vulnerability Analysis using AR Container Analysis or a third-party provider;Security | Configuration;FAIL;AR Container Analysis is not enabled in project <account_uid>.;False;artifacts;Container Analysis;medium;Service;<resource_uid>;<resource_name>;;;;<region>;Scan images stored in Google Container Registry (GCR) for vulnerabilities using AR Container Analysis or a third-party provider. This helps identify and mitigate security risks associated with known vulnerabilities in container images.;Without image vulnerability scanning, container images stored in Artifact Registry may contain known vulnerabilities, increasing the risk of exploitation by malicious actors.;https://cloud.google.com/artifact-analysis/docs;Enable vulnerability scanning for images stored in Artifact Registry using AR Container Analysis or a third-party provider.;https://cloud.google.com/artifact-analysis/docs/container-scanning-overview;;;gcloud services enable containeranalysis.googleapis.com;;MITRE-ATTACK: T1525 | ENS-RD2022: op.exp.4.r4.gcp.log.1, op.mon.3.gcp.scc.1;;;;By default, AR Container Analysis is disabled.;<prowler_version>
|
||||
<auth_method>;2025-02-14 14:27:20.697446;<account_uid>;<account_name>;;<account_organization_uid>;<account_organization_name>;<account_tags>;<finding_uid>;gcp;compute_firewall_rdp_access_from_the_internet_allowed;Ensure That RDP Access Is Restricted From the Internet;;PASS;Firewall <resource_name> does not expose port 3389 (RDP) to the internet.;False;networking;;critical;FirewallRule;<resource_uid>;<resource_name>;;;;<region>;GCP `Firewall Rules` are specific to a `VPC Network`. Each rule either `allows` or `denies` traffic when its conditions are met. Its conditions allow users to specify the type of traffic, such as ports and protocols, and the source or destination of the traffic, including IP addresses, subnets, and instances. Firewall rules are defined at the VPC network level and are specific to the network in which they are defined. The rules themselves cannot be shared among networks. Firewall rules only support IPv4 traffic. When specifying a source for an ingress rule or a destination for an egress rule by address, an `IPv4` address or `IPv4 block in CIDR` notation can be used. Generic `(0.0.0.0/0)` incoming traffic from the Internet to a VPC or VM instance using `RDP` on `Port 3389` can be avoided.;Allowing unrestricted Remote Desktop Protocol (RDP) access can increase opportunities for malicious activities such as hacking, Man-In-The-Middle attacks (MITM) and Pass-The-Hash (PTH) attacks.;;Ensure that Google Cloud Virtual Private Cloud (VPC) firewall rules do not allow unrestricted access (i.e. 0.0.0.0/0) on TCP port 3389 in order to restrict Remote Desktop Protocol (RDP) traffic to trusted IP addresses or IP ranges only and reduce the attack surface. TCP port 3389 is used for secure remote GUI login to Windows VM instances by connecting a RDP client application with an RDP server.;https://cloud.google.com/vpc/docs/using-firewalls;;https://docs.<account_organization_name>/checks/gcp/google-cloud-networking-policies/bc_gcp_networking_2#terraform;https://docs.<account_organization_name>/checks/gcp/google-cloud-networking-policies/bc_gcp_networking_2#cli-command;https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudVPC/unrestricted-rdp-access.html;MITRE-ATTACK: T1190, T1199, T1048, T1498, T1046 | CIS-2.0: 3.7 | ENS-RD2022: mp.com.1.gcp.fw.1 | CIS-3.0: 3.7;internet-exposed;;;;<prowler_version>
|
||||
<auth_method>;2025-02-14 14:27:20.697446;<account_uid>;<account_name>;;<account_organization_uid>;<account_organization_name>;<account_tags>;<finding_uid>;gcp;compute_firewall_rdp_access_from_the_internet_allowed;Ensure That RDP Access Is Restricted From the Internet;;PASS;Firewall <resource_name> does not expose port 3389 (RDP) to the internet.;False;networking;;critical;FirewallRule;<resource_uid>;<resource_name>;;;;<region>;GCP `Firewall Rules` are specific to a `VPC Network`. Each rule either `allows` or `denies` traffic when its conditions are met. Its conditions allow users to specify the type of traffic, such as ports and protocols, and the source or destination of the traffic, including IP addresses, subnets, and instances. Firewall rules are defined at the VPC network level and are specific to the network in which they are defined. The rules themselves cannot be shared among networks. Firewall rules only support IPv4 traffic. When specifying a source for an ingress rule or a destination for an egress rule by address, an `IPv4` address or `IPv4 block in CIDR` notation can be used. Generic `(0.0.0.0/0)` incoming traffic from the Internet to a VPC or VM instance using `RDP` on `Port 3389` can be avoided.;Allowing unrestricted Remote Desktop Protocol (RDP) access can increase opportunities for malicious activities such as hacking, Man-In-The-Middle attacks (MITM) and Pass-The-Hash (PTH) attacks.;;Ensure that Google Cloud Virtual Private Cloud (VPC) firewall rules do not allow unrestricted access (i.e. 0.0.0.0/0) on TCP port 3389 in order to restrict Remote Desktop Protocol (RDP) traffic to trusted IP addresses or IP ranges only and reduce the attack surface. TCP port 3389 is used for secure remote GUI login to Windows VM instances by connecting a RDP client application with an RDP server.;https://cloud.google.com/vpc/docs/using-firewalls;;https://docs.<account_organization_name>/checks/gcp/google-cloud-networking-policies/bc_gcp_networking_2#terraform;https://docs.<account_organization_name>/checks/gcp/google-cloud-networking-policies/bc_gcp_networking_2#cli-command;https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudVPC/unrestricted-rdp-access.html;MITRE-ATTACK: T1190, T1199, T1048, T1498, T1046 | CIS-2.0: 3.7 | ENS-RD2022: mp.com.1.gcp.fw.1 | CIS-3.0: 3.7;internet-exposed;;;;<prowler_version>
|
||||
|
@@ -1,636 +0,0 @@
|
||||
[
|
||||
{
|
||||
"message": "Project <project_id> does not have active API Keys.",
|
||||
"metadata": {
|
||||
"event_code": "apikeys_key_exists",
|
||||
"product": {
|
||||
"name": "Prowler",
|
||||
"uid": "prowler",
|
||||
"vendor_name": "Prowler",
|
||||
"version": "5.4.0"
|
||||
},
|
||||
"profiles": [
|
||||
"cloud",
|
||||
"datetime"
|
||||
],
|
||||
"tenant_uid": "<tenant_uid>",
|
||||
"version": "1.4.0"
|
||||
},
|
||||
"severity_id": 3,
|
||||
"severity": "Medium",
|
||||
"status": "New",
|
||||
"status_code": "PASS",
|
||||
"status_detail": "Project <project_id> does not have active API Keys.",
|
||||
"status_id": 1,
|
||||
"unmapped": {
|
||||
"related_url": "",
|
||||
"categories": [],
|
||||
"depends_on": [],
|
||||
"related_to": [],
|
||||
"notes": "",
|
||||
"compliance": {
|
||||
"MITRE-ATTACK": [
|
||||
"T1098"
|
||||
],
|
||||
"CIS-2.0": [
|
||||
"1.12"
|
||||
],
|
||||
"ENS-RD2022": [
|
||||
"op.acc.2.gcp.rbak.1"
|
||||
],
|
||||
"CIS-3.0": [
|
||||
"1.12"
|
||||
]
|
||||
}
|
||||
},
|
||||
"activity_name": "Create",
|
||||
"activity_id": 1,
|
||||
"finding_info": {
|
||||
"created_time": 1739539640,
|
||||
"created_time_dt": "2025-02-14T14:27:20.697446",
|
||||
"desc": "API Keys should only be used for services in cases where other authentication methods are unavailable. Unused keys with their permissions in tact may still exist within a project. Keys are insecure because they can be viewed publicly, such as from within a browser, or they can be accessed on a device where the key resides. It is recommended to use standard authentication flow instead.",
|
||||
"product_uid": "prowler",
|
||||
"title": "Ensure API Keys Only Exist for Active Services",
|
||||
"types": [],
|
||||
"uid": "<finding_uid>"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"region": "global",
|
||||
"data": {
|
||||
"details": "",
|
||||
"metadata": {
|
||||
"number": "<uid>",
|
||||
"id": "<project_id>",
|
||||
"name": "<project_name>",
|
||||
"organization": {
|
||||
"id": "<tenant_uid>",
|
||||
"name": "organizations/<tenant_uid>",
|
||||
"display_name": "prowler.com"
|
||||
},
|
||||
"labels": {
|
||||
"tag": "test",
|
||||
"tag2": "test2",
|
||||
"generative-language": "enabled"
|
||||
},
|
||||
"lifecycle_state": "ACTIVE"
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"name": "apikeys"
|
||||
},
|
||||
"labels": [],
|
||||
"name": "<project_name>",
|
||||
"type": "API Key",
|
||||
"uid": "<project_id>"
|
||||
}
|
||||
],
|
||||
"category_name": "Findings",
|
||||
"category_uid": 2,
|
||||
"class_name": "Detection Finding",
|
||||
"class_uid": 2004,
|
||||
"cloud": {
|
||||
"account": {
|
||||
"name": "<project_name>",
|
||||
"type": "GCP Account",
|
||||
"type_id": 5,
|
||||
"uid": "<project_id>",
|
||||
"labels": [
|
||||
"tag:test"
|
||||
]
|
||||
},
|
||||
"org": {
|
||||
"name": "prowler.com",
|
||||
"uid": "<tenant_uid>"
|
||||
},
|
||||
"provider": "gcp",
|
||||
"region": "global"
|
||||
},
|
||||
"remediation": {
|
||||
"desc": "To avoid the security risk in using API keys, it is recommended to use standard authentication flow instead.",
|
||||
"references": [
|
||||
"gcloud alpha services api-keys delete",
|
||||
"https://cloud.google.com/docs/authentication/api-keys"
|
||||
]
|
||||
},
|
||||
"risk_details": "Security risks involved in using API-Keys appear below: API keys are simple encrypted strings, API keys do not identify the user or the application making the API request, API keys are typically accessible to clients, making it easy to discover and steal an API key.",
|
||||
"time": 1739539640,
|
||||
"time_dt": "2025-02-14T14:27:20.697446",
|
||||
"type_uid": 200401,
|
||||
"type_name": "Detection Finding: Create"
|
||||
},
|
||||
{
|
||||
"message": "AR Container Analysis is not enabled in project <project_id>.",
|
||||
"metadata": {
|
||||
"event_code": "artifacts_container_analysis_enabled",
|
||||
"product": {
|
||||
"name": "Prowler",
|
||||
"uid": "prowler",
|
||||
"vendor_name": "Prowler",
|
||||
"version": "5.4.0"
|
||||
},
|
||||
"profiles": [
|
||||
"cloud",
|
||||
"datetime"
|
||||
],
|
||||
"tenant_uid": "<tenant_uid>",
|
||||
"version": "1.4.0"
|
||||
},
|
||||
"severity_id": 3,
|
||||
"severity": "Medium",
|
||||
"status": "New",
|
||||
"status_code": "FAIL",
|
||||
"status_detail": "AR Container Analysis is not enabled in project <project_id>.",
|
||||
"status_id": 1,
|
||||
"unmapped": {
|
||||
"related_url": "https://cloud.google.com/artifact-analysis/docs",
|
||||
"categories": [],
|
||||
"depends_on": [],
|
||||
"related_to": [],
|
||||
"notes": "By default, AR Container Analysis is disabled.",
|
||||
"compliance": {
|
||||
"MITRE-ATTACK": [
|
||||
"T1525"
|
||||
],
|
||||
"ENS-RD2022": [
|
||||
"op.exp.4.r4.gcp.log.1",
|
||||
"op.mon.3.gcp.scc.1"
|
||||
]
|
||||
}
|
||||
},
|
||||
"activity_name": "Create",
|
||||
"activity_id": 1,
|
||||
"finding_info": {
|
||||
"created_time": 1739539640,
|
||||
"created_time_dt": "2025-02-14T14:27:20.697446",
|
||||
"desc": "Scan images stored in Google Container Registry (GCR) for vulnerabilities using AR Container Analysis or a third-party provider. This helps identify and mitigate security risks associated with known vulnerabilities in container images.",
|
||||
"product_uid": "prowler",
|
||||
"title": "Ensure Image Vulnerability Analysis using AR Container Analysis or a third-party provider",
|
||||
"types": [
|
||||
"Security",
|
||||
"Configuration"
|
||||
],
|
||||
"uid": "<finding_uid>"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"region": "global",
|
||||
"data": {
|
||||
"details": "",
|
||||
"metadata": {
|
||||
"number": "538174383574",
|
||||
"id": "<project_id>",
|
||||
"name": "<project_name>",
|
||||
"organization": {
|
||||
"id": "<tenant_uid>",
|
||||
"name": "organizations/<tenant_uid>",
|
||||
"display_name": "prowler.com"
|
||||
},
|
||||
"labels": {
|
||||
"tag": "test",
|
||||
"tag2": "test2",
|
||||
"generative-language": "enabled"
|
||||
},
|
||||
"lifecycle_state": "ACTIVE"
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"name": "artifacts"
|
||||
},
|
||||
"labels": [],
|
||||
"name": "AR Container Analysis",
|
||||
"type": "Service",
|
||||
"uid": "containeranalysis.googleapis.com"
|
||||
}
|
||||
],
|
||||
"category_name": "Findings",
|
||||
"category_uid": 2,
|
||||
"class_name": "Detection Finding",
|
||||
"class_uid": 2004,
|
||||
"cloud": {
|
||||
"account": {
|
||||
"name": "<project_name>",
|
||||
"type": "GCP Account",
|
||||
"type_id": 5,
|
||||
"uid": "<project_id>",
|
||||
"labels": [
|
||||
"tag:test"
|
||||
]
|
||||
},
|
||||
"org": {
|
||||
"name": "prowler.com",
|
||||
"uid": "<tenant_uid>"
|
||||
},
|
||||
"provider": "gcp",
|
||||
"region": "global"
|
||||
},
|
||||
"remediation": {
|
||||
"desc": "Enable vulnerability scanning for images stored in Artifact Registry using AR Container Analysis or a third-party provider.",
|
||||
"references": [
|
||||
"gcloud services enable containeranalysis.googleapis.com",
|
||||
"https://cloud.google.com/artifact-analysis/docs/container-scanning-overview"
|
||||
]
|
||||
},
|
||||
"risk_details": "Without image vulnerability scanning, container images stored in Artifact Registry may contain known vulnerabilities, increasing the risk of exploitation by malicious actors.",
|
||||
"time": 1739539640,
|
||||
"time_dt": "2025-02-14T14:27:20.697446",
|
||||
"type_uid": 200401,
|
||||
"type_name": "Detection Finding: Create"
|
||||
},
|
||||
{
|
||||
"message": "Firewall <resource_id> does not expose port 3389 (RDP) to the internet.",
|
||||
"metadata": {
|
||||
"event_code": "compute_firewall_rdp_access_from_the_internet_allowed",
|
||||
"product": {
|
||||
"name": "Prowler",
|
||||
"uid": "prowler",
|
||||
"vendor_name": "Prowler",
|
||||
"version": "5.4.0"
|
||||
},
|
||||
"profiles": [
|
||||
"cloud",
|
||||
"datetime"
|
||||
],
|
||||
"tenant_uid": "<tenant_uid>",
|
||||
"version": "1.4.0"
|
||||
},
|
||||
"severity_id": 5,
|
||||
"severity": "Critical",
|
||||
"status": "New",
|
||||
"status_code": "PASS",
|
||||
"status_detail": "Firewall <resource_id> does not expose port 3389 (RDP) to the internet.",
|
||||
"status_id": 1,
|
||||
"unmapped": {
|
||||
"related_url": "",
|
||||
"categories": [
|
||||
"internet-exposed"
|
||||
],
|
||||
"depends_on": [],
|
||||
"related_to": [],
|
||||
"notes": "",
|
||||
"compliance": {
|
||||
"MITRE-ATTACK": [
|
||||
"T1190",
|
||||
"T1199",
|
||||
"T1048",
|
||||
"T1498",
|
||||
"T1046"
|
||||
],
|
||||
"CIS-2.0": [
|
||||
"3.7"
|
||||
],
|
||||
"ENS-RD2022": [
|
||||
"mp.com.1.gcp.fw.1"
|
||||
],
|
||||
"CIS-3.0": [
|
||||
"3.7"
|
||||
]
|
||||
}
|
||||
},
|
||||
"activity_name": "Create",
|
||||
"activity_id": 1,
|
||||
"finding_info": {
|
||||
"created_time": 1739539640,
|
||||
"created_time_dt": "2025-02-14T14:27:20.697446",
|
||||
"desc": "GCP `Firewall Rules` are specific to a `VPC Network`. Each rule either `allows` or `denies` traffic when its conditions are met. Its conditions allow users to specify the type of traffic, such as ports and protocols, and the source or destination of the traffic, including IP addresses, subnets, and instances. Firewall rules are defined at the VPC network level and are specific to the network in which they are defined. The rules themselves cannot be shared among networks. Firewall rules only support IPv4 traffic. When specifying a source for an ingress rule or a destination for an egress rule by address, an `IPv4` address or `IPv4 block in CIDR` notation can be used. Generic `(0.0.0.0/0)` incoming traffic from the Internet to a VPC or VM instance using `RDP` on `Port 3389` can be avoided.",
|
||||
"product_uid": "prowler",
|
||||
"title": "Ensure That RDP Access Is Restricted From the Internet",
|
||||
"types": [],
|
||||
"uid": "<finding_uid>"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"region": "global",
|
||||
"data": {
|
||||
"details": "",
|
||||
"metadata": {
|
||||
"name": "<resource_id>",
|
||||
"id": "<uid>",
|
||||
"source_ranges": [
|
||||
"<value>"
|
||||
],
|
||||
"direction": "INGRESS",
|
||||
"allowed_rules": [
|
||||
{
|
||||
"IPProtocol": "icmp"
|
||||
}
|
||||
],
|
||||
"project_id": "<project_id>"
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"name": "networking"
|
||||
},
|
||||
"labels": [],
|
||||
"name": "<resource_id>",
|
||||
"type": "FirewallRule",
|
||||
"uid": "<uid>"
|
||||
}
|
||||
],
|
||||
"category_name": "Findings",
|
||||
"category_uid": 2,
|
||||
"class_name": "Detection Finding",
|
||||
"class_uid": 2004,
|
||||
"cloud": {
|
||||
"account": {
|
||||
"name": "<project_name>",
|
||||
"type": "GCP Account",
|
||||
"type_id": 5,
|
||||
"uid": "<project_id>",
|
||||
"labels": [
|
||||
"tag:test",
|
||||
"tag2:test2"
|
||||
]
|
||||
},
|
||||
"org": {
|
||||
"name": "prowler.com",
|
||||
"uid": "<tenant_uid>"
|
||||
},
|
||||
"provider": "gcp",
|
||||
"region": "global"
|
||||
},
|
||||
"remediation": {
|
||||
"desc": "Ensure that Google Cloud Virtual Private Cloud (VPC) firewall rules do not allow unrestricted access (i.e. 0.0.0.0/0) on TCP port 3389 in order to restrict Remote Desktop Protocol (RDP) traffic to trusted IP addresses or IP ranges only and reduce the attack surface. TCP port 3389 is used for secure remote GUI login to Windows VM instances by connecting a RDP client application with an RDP server.",
|
||||
"references": [
|
||||
"https://docs.prowler.com/checks/gcp/google-cloud-networking-policies/bc_gcp_networking_2#terraform",
|
||||
"https://docs.prowler.com/checks/gcp/google-cloud-networking-policies/bc_gcp_networking_2#cli-command",
|
||||
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudVPC/unrestricted-rdp-access.html",
|
||||
"https://cloud.google.com/vpc/docs/using-firewalls"
|
||||
]
|
||||
},
|
||||
"risk_details": "Allowing unrestricted Remote Desktop Protocol (RDP) access can increase opportunities for malicious activities such as hacking, Man-In-The-Middle attacks (MITM) and Pass-The-Hash (PTH) attacks.",
|
||||
"time": 1739539640,
|
||||
"time_dt": "2025-02-14T14:27:20.697446",
|
||||
"type_uid": 200401,
|
||||
"type_name": "Detection Finding: Create"
|
||||
},
|
||||
{
|
||||
"message": "Firewall <resource_id> does not expose port 3389 (RDP) to the internet.",
|
||||
"metadata": {
|
||||
"event_code": "compute_firewall_rdp_access_from_the_internet_allowed",
|
||||
"product": {
|
||||
"name": "Prowler",
|
||||
"uid": "prowler",
|
||||
"vendor_name": "Prowler",
|
||||
"version": "5.4.0"
|
||||
},
|
||||
"profiles": [
|
||||
"cloud",
|
||||
"datetime"
|
||||
],
|
||||
"tenant_uid": "<tenant_uid>",
|
||||
"version": "1.4.0"
|
||||
},
|
||||
"severity_id": 5,
|
||||
"severity": "Critical",
|
||||
"status": "New",
|
||||
"status_code": "PASS",
|
||||
"status_detail": "Firewall <resource_id> does not expose port 3389 (RDP) to the internet.",
|
||||
"status_id": 1,
|
||||
"unmapped": {
|
||||
"related_url": "",
|
||||
"categories": [
|
||||
"internet-exposed"
|
||||
],
|
||||
"depends_on": [],
|
||||
"related_to": [],
|
||||
"notes": "",
|
||||
"compliance": {
|
||||
"MITRE-ATTACK": [
|
||||
"T1190",
|
||||
"T1199",
|
||||
"T1048",
|
||||
"T1498",
|
||||
"T1046"
|
||||
],
|
||||
"CIS-2.0": [
|
||||
"3.7"
|
||||
],
|
||||
"ENS-RD2022": [
|
||||
"mp.com.1.gcp.fw.1"
|
||||
],
|
||||
"CIS-3.0": [
|
||||
"3.7"
|
||||
]
|
||||
}
|
||||
},
|
||||
"activity_name": "Create",
|
||||
"activity_id": 1,
|
||||
"finding_info": {
|
||||
"created_time": 1739539640,
|
||||
"created_time_dt": "2025-02-14T14:27:20.697446",
|
||||
"desc": "GCP `Firewall Rules` are specific to a `VPC Network`. Each rule either `allows` or `denies` traffic when its conditions are met. Its conditions allow users to specify the type of traffic, such as ports and protocols, and the source or destination of the traffic, including IP addresses, subnets, and instances. Firewall rules are defined at the VPC network level and are specific to the network in which they are defined. The rules themselves cannot be shared among networks. Firewall rules only support IPv4 traffic. When specifying a source for an ingress rule or a destination for an egress rule by address, an `IPv4` address or `IPv4 block in CIDR` notation can be used. Generic `(0.0.0.0/0)` incoming traffic from the Internet to a VPC or VM instance using `RDP` on `Port 3389` can be avoided.",
|
||||
"product_uid": "prowler",
|
||||
"title": "Ensure That RDP Access Is Restricted From the Internet",
|
||||
"types": [],
|
||||
"uid": "<finding_uid>"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"region": "global",
|
||||
"data": {
|
||||
"details": "",
|
||||
"metadata": {
|
||||
"name": "<resource_id>",
|
||||
"id": "<uid>",
|
||||
"source_ranges": [
|
||||
"<value>"
|
||||
],
|
||||
"direction": "INGRESS",
|
||||
"allowed_rules": [
|
||||
{
|
||||
"IPProtocol": "tcp",
|
||||
"ports": [
|
||||
"0-65535"
|
||||
]
|
||||
},
|
||||
{
|
||||
"IPProtocol": "udp",
|
||||
"ports": [
|
||||
"0-65535"
|
||||
]
|
||||
},
|
||||
{
|
||||
"IPProtocol": "icmp"
|
||||
}
|
||||
],
|
||||
"project_id": "<project_id>"
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"name": "networking"
|
||||
},
|
||||
"labels": [],
|
||||
"name": "<resource_id>",
|
||||
"type": "FirewallRule",
|
||||
"uid": "<uid>"
|
||||
}
|
||||
],
|
||||
"category_name": "Findings",
|
||||
"category_uid": 2,
|
||||
"class_name": "Detection Finding",
|
||||
"class_uid": 2004,
|
||||
"cloud": {
|
||||
"account": {
|
||||
"name": "<project_name>",
|
||||
"type": "GCP Account",
|
||||
"type_id": 5,
|
||||
"uid": "<project_id>",
|
||||
"labels": [
|
||||
"tag:test",
|
||||
"tag2:test2"
|
||||
]
|
||||
},
|
||||
"org": {
|
||||
"name": "prowler.com",
|
||||
"uid": "<tenant_uid>"
|
||||
},
|
||||
"provider": "gcp",
|
||||
"region": "global"
|
||||
},
|
||||
"remediation": {
|
||||
"desc": "Ensure that Google Cloud Virtual Private Cloud (VPC) firewall rules do not allow unrestricted access (i.e. 0.0.0.0/0) on TCP port 3389 in order to restrict Remote Desktop Protocol (RDP) traffic to trusted IP addresses or IP ranges only and reduce the attack surface. TCP port 3389 is used for secure remote GUI login to Windows VM instances by connecting a RDP client application with an RDP server.",
|
||||
"references": [
|
||||
"https://docs.prowler.com/checks/gcp/google-cloud-networking-policies/bc_gcp_networking_2#terraform",
|
||||
"https://docs.prowler.com/checks/gcp/google-cloud-networking-policies/bc_gcp_networking_2#cli-command",
|
||||
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudVPC/unrestricted-rdp-access.html",
|
||||
"https://cloud.google.com/vpc/docs/using-firewalls"
|
||||
]
|
||||
},
|
||||
"risk_details": "Allowing unrestricted Remote Desktop Protocol (RDP) access can increase opportunities for malicious activities such as hacking, Man-In-The-Middle attacks (MITM) and Pass-The-Hash (PTH) attacks.",
|
||||
"time": 1739539640,
|
||||
"time_dt": "2025-02-14T14:27:20.697446",
|
||||
"type_uid": 200401,
|
||||
"type_name": "Detection Finding: Create"
|
||||
},
|
||||
{
|
||||
"message": "Firewall <resource_id> does exposes port 3389 (RDP) to the internet.",
|
||||
"metadata": {
|
||||
"event_code": "compute_firewall_rdp_access_from_the_internet_allowed",
|
||||
"product": {
|
||||
"name": "Prowler",
|
||||
"uid": "prowler",
|
||||
"vendor_name": "Prowler",
|
||||
"version": "5.4.0"
|
||||
},
|
||||
"profiles": [
|
||||
"cloud",
|
||||
"datetime"
|
||||
],
|
||||
"tenant_uid": "<tenant_uid>",
|
||||
"version": "1.4.0"
|
||||
},
|
||||
"severity_id": 5,
|
||||
"severity": "Critical",
|
||||
"status": "New",
|
||||
"status_code": "FAIL",
|
||||
"status_detail": "Firewall <resource_id> does exposes port 3389 (RDP) to the internet.",
|
||||
"status_id": 1,
|
||||
"unmapped": {
|
||||
"related_url": "",
|
||||
"categories": [
|
||||
"internet-exposed"
|
||||
],
|
||||
"depends_on": [],
|
||||
"related_to": [],
|
||||
"notes": "",
|
||||
"compliance": {
|
||||
"MITRE-ATTACK": [
|
||||
"T1190",
|
||||
"T1199",
|
||||
"T1048",
|
||||
"T1498",
|
||||
"T1046"
|
||||
],
|
||||
"CIS-2.0": [
|
||||
"3.7"
|
||||
],
|
||||
"ENS-RD2022": [
|
||||
"mp.com.1.gcp.fw.1"
|
||||
],
|
||||
"CIS-3.0": [
|
||||
"3.7"
|
||||
]
|
||||
}
|
||||
},
|
||||
"activity_name": "Create",
|
||||
"activity_id": 1,
|
||||
"finding_info": {
|
||||
"created_time": 1739539640,
|
||||
"created_time_dt": "2025-02-14T14:27:20.697446",
|
||||
"desc": "GCP `Firewall Rules` are specific to a `VPC Network`. Each rule either `allows` or `denies` traffic when its conditions are met. Its conditions allow users to specify the type of traffic, such as ports and protocols, and the source or destination of the traffic, including IP addresses, subnets, and instances. Firewall rules are defined at the VPC network level and are specific to the network in which they are defined. The rules themselves cannot be shared among networks. Firewall rules only support IPv4 traffic. When specifying a source for an ingress rule or a destination for an egress rule by address, an `IPv4` address or `IPv4 block in CIDR` notation can be used. Generic `(0.0.0.0/0)` incoming traffic from the Internet to a VPC or VM instance using `RDP` on `Port 3389` can be avoided.",
|
||||
"product_uid": "prowler",
|
||||
"title": "Ensure That RDP Access Is Restricted From the Internet",
|
||||
"types": [],
|
||||
"uid": "<finding_uid>"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"region": "global",
|
||||
"data": {
|
||||
"details": "",
|
||||
"metadata": {
|
||||
"name": "<resource_id>",
|
||||
"id": "<uid>",
|
||||
"source_ranges": [
|
||||
"<value>"
|
||||
],
|
||||
"direction": "INGRESS",
|
||||
"allowed_rules": [
|
||||
{
|
||||
"IPProtocol": "tcp",
|
||||
"ports": [
|
||||
"3389"
|
||||
]
|
||||
}
|
||||
],
|
||||
"project_id": "<project_id>"
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"name": "networking"
|
||||
},
|
||||
"labels": [],
|
||||
"name": "<resource_id>",
|
||||
"type": "FirewallRule",
|
||||
"uid": "<uid>"
|
||||
}
|
||||
],
|
||||
"category_name": "Findings",
|
||||
"category_uid": 2,
|
||||
"class_name": "Detection Finding",
|
||||
"class_uid": 2004,
|
||||
"cloud": {
|
||||
"account": {
|
||||
"name": "<project_name>",
|
||||
"type": "GCP Account",
|
||||
"type_id": 5,
|
||||
"uid": "<project_id>",
|
||||
"labels": [
|
||||
"tag:test",
|
||||
"tag2:test2"
|
||||
]
|
||||
},
|
||||
"org": {
|
||||
"name": "prowler.com",
|
||||
"uid": "<tenant_uid>"
|
||||
},
|
||||
"provider": "gcp",
|
||||
"region": "global"
|
||||
},
|
||||
"remediation": {
|
||||
"desc": "Ensure that Google Cloud Virtual Private Cloud (VPC) firewall rules do not allow unrestricted access (i.e. 0.0.0.0/0) on TCP port 3389 in order to restrict Remote Desktop Protocol (RDP) traffic to trusted IP addresses or IP ranges only and reduce the attack surface. TCP port 3389 is used for secure remote GUI login to Windows VM instances by connecting a RDP client application with an RDP server.",
|
||||
"references": [
|
||||
"https://docs.prowler.com/checks/gcp/google-cloud-networking-policies/bc_gcp_networking_2#terraform",
|
||||
"https://docs.prowler.com/checks/gcp/google-cloud-networking-policies/bc_gcp_networking_2#cli-command",
|
||||
"https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudVPC/unrestricted-rdp-access.html",
|
||||
"https://cloud.google.com/vpc/docs/using-firewalls"
|
||||
]
|
||||
},
|
||||
"risk_details": "Allowing unrestricted Remote Desktop Protocol (RDP) access can increase opportunities for malicious activities such as hacking, Man-In-The-Middle attacks (MITM) and Pass-The-Hash (PTH) attacks.",
|
||||
"time": 1739539640,
|
||||
"time_dt": "2025-02-14T14:27:20.697446",
|
||||
"type_uid": 200401,
|
||||
"type_name": "Detection Finding: Create"
|
||||
}
|
||||
]
|
||||
@@ -1,5 +0,0 @@
|
||||
AUTH_METHOD;TIMESTAMP;ACCOUNT_UID;ACCOUNT_NAME;ACCOUNT_EMAIL;ACCOUNT_ORGANIZATION_UID;ACCOUNT_ORGANIZATION_NAME;ACCOUNT_TAGS;FINDING_UID;PROVIDER;CHECK_ID;CHECK_TITLE;CHECK_TYPE;STATUS;STATUS_EXTENDED;MUTED;SERVICE_NAME;SUBSERVICE_NAME;SEVERITY;RESOURCE_TYPE;RESOURCE_UID;RESOURCE_NAME;RESOURCE_DETAILS;RESOURCE_TAGS;PARTITION;REGION;DESCRIPTION;RISK;RELATED_URL;REMEDIATION_RECOMMENDATION_TEXT;REMEDIATION_RECOMMENDATION_URL;REMEDIATION_CODE_NATIVEIAC;REMEDIATION_CODE_TERRAFORM;REMEDIATION_CODE_CLI;REMEDIATION_CODE_OTHER;COMPLIANCE;CATEGORIES;DEPENDS_ON;RELATED_TO;NOTES;PROWLER_VERSION
|
||||
<auth_method>;2025-02-14 14:27:38.533897;<account_uid>;context: <context>;;;;;<finding_uid>;kubernetes;apiserver_always_pull_images_plugin;Ensure that the admission control plugin AlwaysPullImages is set;;FAIL;AlwaysPullImages admission control plugin is not set in pod <resource_uid>;False;apiserver;;medium;KubernetesAPIServer;<resource_id>;<resource_name>;;;;namespace: kube-system;This check verifies that the AlwaysPullImages admission control plugin is enabled in the Kubernetes API server. This plugin ensures that every new pod always pulls the required images, enforcing image access control and preventing the use of possibly outdated or altered images.;Without AlwaysPullImages, once an image is pulled to a node, any pod can use it without any authorization check, potentially leading to security risks.;https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#alwayspullimages;Configure the API server to use the AlwaysPullImages admission control plugin to ensure image security and integrity.;https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers;https://docs.prowler.com/checks/kubernetes/kubernetes-policy-index/ensure-that-the-admission-control-plugin-alwayspullimages-is-set#kubernetes;;--enable-admission-plugins=...,AlwaysPullImages,...;;CIS-1.10: 1.2.11 | CIS-1.8: 1.2.11;cluster-security;;;Enabling AlwaysPullImages can increase network and registry load and decrease container startup speed. It may not be suitable for all environments.;<prowler_version>
|
||||
<auth_method>;2025-02-14 14:27:38.533897;<account_uid>;context: <context>;;;;;<finding_uid>;kubernetes;apiserver_anonymous_requests;Ensure that the --anonymous-auth argument is set to false;;PASS;API Server does not have anonymous-auth enabled in pod <resource_uid>;False;apiserver;;high;KubernetesAPIServer;<resource_id>;<resource_name>;;;;namespace: kube-system;Disable anonymous requests to the API server. When enabled, requests that are not rejected by other configured authentication methods are treated as anonymous requests, which are then served by the API server. Disallowing anonymous requests strengthens security by ensuring all access is authenticated.;Enabling anonymous access to the API server can expose the cluster to unauthorized access and potential security vulnerabilities.;https://kubernetes.io/docs/admin/authentication/#anonymous-requests;Ensure the --anonymous-auth argument in the API server is set to false. This will reject all anonymous requests, enforcing authenticated access to the server.;https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/;https://docs.prowler.com/checks/kubernetes/kubernetes-policy-index/ensure-that-the-anonymous-auth-argument-is-set-to-false-1#kubernetes;;--anonymous-auth=false;;CIS-1.10: 1.2.1 | CIS-1.8: 1.2.1;trustboundaries;;;While anonymous access can be useful for health checks and discovery, consider the security implications for your specific environment.;<prowler_version>
|
||||
<auth_method>;2025-02-14 14:27:38.533897;<account_uid>;context: <context>;;;;;<finding_uid>;kubernetes;apiserver_audit_log_maxage_set;Ensure that the --audit-log-maxage argument is set to 30 or as appropriate;;FAIL;Audit log max age is not set to 30 or as appropriate in pod <resource_uid>;False;apiserver;;medium;KubernetesAPIServer;<resource_id>;<resource_name>;;;;namespace: kube-system;This check ensures that the Kubernetes API server is configured with an appropriate audit log retention period. Setting --audit-log-maxage to 30 or as per business requirements helps in maintaining logs for sufficient time to investigate past events.;Without an adequate log retention period, there may be insufficient audit history to investigate and analyze past events or security incidents.;https://kubernetes.io/docs/concepts/cluster-administration/audit/;Configure the API server audit log retention period to retain logs for at least 30 days or as per your organization's requirements.;https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/;https://docs.prowler.com/checks/kubernetes/kubernetes-policy-index/ensure-that-the-audit-log-maxage-argument-is-set-to-30-or-as-appropriate#kubernetes;;--audit-log-maxage=30;;CIS-1.10: 1.2.17 | CIS-1.8: 1.2.18;logging;;;Ensure the audit log retention period is set appropriately to balance between storage constraints and the need for historical data.;<prowler_version>
|
||||
<auth_method>;2025-02-14 14:27:38.533897;<account_uid>;context: <context>;;;;;<finding_uid>;kubernetes;apiserver_audit_log_maxbackup_set;Ensure that the --audit-log-maxbackup argument is set to 10 or as appropriate;;FAIL;Audit log max backup is not set to 10 or as appropriate in pod <resource_uid>;False;apiserver;;medium;KubernetesAPIServer;<resource_id>;<resource_name>;;;;namespace: kube-system;This check ensures that the Kubernetes API server is configured with an appropriate number of audit log backups. Setting --audit-log-maxbackup to 10 or as per business requirements helps maintain a sufficient log backup for investigations or analysis.;Without an adequate number of audit log backups, there may be insufficient log history to investigate past events or security incidents.;https://kubernetes.io/docs/concepts/cluster-administration/audit/;Configure the API server audit log backup retention to 10 or as per your organization's requirements.;https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/;https://docs.prowler.com/checks/kubernetes/kubernetes-policy-index/ensure-that-the-audit-log-maxbackup-argument-is-set-to-10-or-as-appropriate#kubernetes;;--audit-log-maxbackup=10;;CIS-1.10: 1.2.18 | CIS-1.8: 1.2.19;logging;;;Ensure the audit log backup retention period is set appropriately to balance between storage constraints and the need for historical data.;<prowler_version>
|
||||
|
@@ -1,800 +0,0 @@
|
||||
[
|
||||
{
|
||||
"message": "AlwaysPullImages admission control plugin is not set in pod <pod>.",
|
||||
"metadata": {
|
||||
"event_code": "apiserver_always_pull_images_plugin",
|
||||
"product": {
|
||||
"name": "Prowler",
|
||||
"uid": "prowler",
|
||||
"vendor_name": "Prowler",
|
||||
"version": "5.4.0"
|
||||
},
|
||||
"profiles": [
|
||||
"container",
|
||||
"datetime"
|
||||
],
|
||||
"version": "1.4.0"
|
||||
},
|
||||
"severity_id": 3,
|
||||
"severity": "Medium",
|
||||
"status": "New",
|
||||
"status_code": "FAIL",
|
||||
"status_detail": "AlwaysPullImages admission control plugin is not set in pod <pod>.",
|
||||
"status_id": 1,
|
||||
"unmapped": {
|
||||
"related_url": "https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#alwayspullimages",
|
||||
"categories": [
|
||||
"cluster-security"
|
||||
],
|
||||
"depends_on": [],
|
||||
"related_to": [],
|
||||
"notes": "Enabling AlwaysPullImages can increase network and registry load and decrease container startup speed. It may not be suitable for all environments.",
|
||||
"compliance": {
|
||||
"CIS-1.10": [
|
||||
"1.2.11"
|
||||
],
|
||||
"CIS-1.8": [
|
||||
"1.2.11"
|
||||
]
|
||||
}
|
||||
},
|
||||
"activity_name": "Create",
|
||||
"activity_id": 1,
|
||||
"finding_info": {
|
||||
"created_time": 1739539658,
|
||||
"created_time_dt": "2025-02-14T14:27:38.533897",
|
||||
"desc": "This check verifies that the AlwaysPullImages admission control plugin is enabled in the Kubernetes API server. This plugin ensures that every new pod always pulls the required images, enforcing image access control and preventing the use of possibly outdated or altered images.",
|
||||
"product_uid": "prowler",
|
||||
"title": "Ensure that the admission control plugin AlwaysPullImages is set",
|
||||
"types": [],
|
||||
"uid": "<finding_uid>"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"data": {
|
||||
"details": "",
|
||||
"metadata": {
|
||||
"name": "<pod>",
|
||||
"uid": "<uid>",
|
||||
"namespace": "<namespace>",
|
||||
"labels": {
|
||||
"component": "kube-apiserver",
|
||||
"tier": "control-plane"
|
||||
},
|
||||
"annotations": {
|
||||
"kubernetes.io/config.source": "file"
|
||||
},
|
||||
"node_name": "<node_name>",
|
||||
"service_account": null,
|
||||
"status_phase": "Running",
|
||||
"pod_ip": "<ip>",
|
||||
"host_ip": "<ip>",
|
||||
"host_pid": null,
|
||||
"host_ipc": null,
|
||||
"host_network": "True",
|
||||
"security_context": {
|
||||
"app_armor_profile": null,
|
||||
"fs_group": null,
|
||||
"fs_group_change_policy": null,
|
||||
"run_as_group": null,
|
||||
"run_as_non_root": null,
|
||||
"run_as_user": null,
|
||||
"se_linux_change_policy": null,
|
||||
"se_linux_options": null,
|
||||
"seccomp_profile": {
|
||||
"localhost_profile": null,
|
||||
"type": "RuntimeDefault"
|
||||
},
|
||||
"supplemental_groups": null,
|
||||
"supplemental_groups_policy": null,
|
||||
"sysctls": null,
|
||||
"windows_options": null
|
||||
},
|
||||
"containers": {
|
||||
"kube-apiserver": {
|
||||
"name": "kube-apiserver",
|
||||
"image": "<image>",
|
||||
"command": [
|
||||
"<command>"
|
||||
],
|
||||
"ports": null,
|
||||
"env": null,
|
||||
"security_context": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"name": "apiserver"
|
||||
},
|
||||
"labels": [],
|
||||
"name": "<pod>",
|
||||
"namespace": "<namespace>",
|
||||
"type": "KubernetesAPIServer",
|
||||
"uid": "<resource_uid>"
|
||||
}
|
||||
],
|
||||
"category_name": "Findings",
|
||||
"category_uid": 2,
|
||||
"class_name": "Detection Finding",
|
||||
"class_uid": 2004,
|
||||
"remediation": {
|
||||
"desc": "Configure the API server to use the AlwaysPullImages admission control plugin to ensure image security and integrity.",
|
||||
"references": [
|
||||
"https://docs.prowler.com/checks/kubernetes/kubernetes-policy-index/ensure-that-the-admission-control-plugin-alwayspullimages-is-set#kubernetes",
|
||||
"--enable-admission-plugins=...,AlwaysPullImages,...",
|
||||
"https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers"
|
||||
]
|
||||
},
|
||||
"risk_details": "Without AlwaysPullImages, once an image is pulled to a node, any pod can use it without any authorization check, potentially leading to security risks.",
|
||||
"time": 1739539658,
|
||||
"time_dt": "2025-02-14T14:27:38.533897",
|
||||
"type_uid": 200401,
|
||||
"type_name": "Detection Finding: Create"
|
||||
},
|
||||
{
|
||||
"message": "API Server does not have anonymous-auth enabled in pod <pod>.",
|
||||
"metadata": {
|
||||
"event_code": "apiserver_anonymous_requests",
|
||||
"product": {
|
||||
"name": "Prowler",
|
||||
"uid": "prowler",
|
||||
"vendor_name": "Prowler",
|
||||
"version": "5.4.0"
|
||||
},
|
||||
"profiles": [
|
||||
"container",
|
||||
"datetime"
|
||||
],
|
||||
"version": "1.4.0"
|
||||
},
|
||||
"severity_id": 4,
|
||||
"severity": "High",
|
||||
"status": "New",
|
||||
"status_code": "PASS",
|
||||
"status_detail": "API Server does not have anonymous-auth enabled in pod <pod>.",
|
||||
"status_id": 1,
|
||||
"unmapped": {
|
||||
"related_url": "https://kubernetes.io/docs/admin/authentication/#anonymous-requests",
|
||||
"categories": [
|
||||
"trustboundaries"
|
||||
],
|
||||
"depends_on": [],
|
||||
"related_to": [],
|
||||
"notes": "While anonymous access can be useful for health checks and discovery, consider the security implications for your specific environment.",
|
||||
"compliance": {
|
||||
"CIS-1.10": [
|
||||
"1.2.1"
|
||||
],
|
||||
"CIS-1.8": [
|
||||
"1.2.1"
|
||||
]
|
||||
}
|
||||
},
|
||||
"activity_name": "Create",
|
||||
"activity_id": 1,
|
||||
"finding_info": {
|
||||
"created_time": 1739539658,
|
||||
"created_time_dt": "2025-02-14T14:27:38.533897",
|
||||
"desc": "Disable anonymous requests to the API server. When enabled, requests that are not rejected by other configured authentication methods are treated as anonymous requests, which are then served by the API server. Disallowing anonymous requests strengthens security by ensuring all access is authenticated.",
|
||||
"product_uid": "prowler",
|
||||
"title": "Ensure that the --anonymous-auth argument is set to false",
|
||||
"types": [],
|
||||
"uid": "<finding_uid>"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"data": {
|
||||
"details": "",
|
||||
"metadata": {
|
||||
"name": "<pod>",
|
||||
"uid": "<resource_uid>",
|
||||
"namespace": "<namespace>",
|
||||
"labels": {
|
||||
"component": "kube-apiserver",
|
||||
"tier": "control-plane"
|
||||
},
|
||||
"annotations": {
|
||||
"kubernetes.io/config.source": "file"
|
||||
},
|
||||
"node_name": "<node_name>",
|
||||
"service_account": null,
|
||||
"status_phase": "Running",
|
||||
"pod_ip": "<ip>",
|
||||
"host_ip": "<ip>",
|
||||
"host_pid": null,
|
||||
"host_ipc": null,
|
||||
"host_network": "True",
|
||||
"security_context": {
|
||||
"app_armor_profile": null,
|
||||
"fs_group": null,
|
||||
"fs_group_change_policy": null,
|
||||
"run_as_group": null,
|
||||
"run_as_non_root": null,
|
||||
"run_as_user": null,
|
||||
"se_linux_change_policy": null,
|
||||
"se_linux_options": null,
|
||||
"seccomp_profile": {
|
||||
"localhost_profile": null,
|
||||
"type": "RuntimeDefault"
|
||||
},
|
||||
"supplemental_groups": null,
|
||||
"supplemental_groups_policy": null,
|
||||
"sysctls": null,
|
||||
"windows_options": null
|
||||
},
|
||||
"containers": {
|
||||
"kube-apiserver": {
|
||||
"name": "kube-apiserver",
|
||||
"image": "<image>",
|
||||
"command": [
|
||||
"<command>"
|
||||
],
|
||||
"ports": null,
|
||||
"env": null,
|
||||
"security_context": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"name": "apiserver"
|
||||
},
|
||||
"labels": [],
|
||||
"name": "<pod>",
|
||||
"namespace": "<namespace>",
|
||||
"type": "KubernetesAPIServer",
|
||||
"uid": "<resource_uid>"
|
||||
}
|
||||
],
|
||||
"category_name": "Findings",
|
||||
"category_uid": 2,
|
||||
"class_name": "Detection Finding",
|
||||
"class_uid": 2004,
|
||||
"remediation": {
|
||||
"desc": "Ensure the --anonymous-auth argument in the API server is set to false. This will reject all anonymous requests, enforcing authenticated access to the server.",
|
||||
"references": [
|
||||
"https://docs.prowler.com/checks/kubernetes/kubernetes-policy-index/ensure-that-the-anonymous-auth-argument-is-set-to-false-1#kubernetes",
|
||||
"--anonymous-auth=false",
|
||||
"https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/"
|
||||
]
|
||||
},
|
||||
"risk_details": "Enabling anonymous access to the API server can expose the cluster to unauthorized access and potential security vulnerabilities.",
|
||||
"time": 1739539658,
|
||||
"time_dt": "2025-02-14T14:27:38.533897",
|
||||
"type_uid": 200401,
|
||||
"type_name": "Detection Finding: Create"
|
||||
},
|
||||
{
|
||||
"message": "Audit log max age is not set to 30 or as appropriate in pod <pod>.",
|
||||
"metadata": {
|
||||
"event_code": "apiserver_audit_log_maxage_set",
|
||||
"product": {
|
||||
"name": "Prowler",
|
||||
"uid": "prowler",
|
||||
"vendor_name": "Prowler",
|
||||
"version": "5.4.0"
|
||||
},
|
||||
"profiles": [
|
||||
"container",
|
||||
"datetime"
|
||||
],
|
||||
"version": "1.4.0"
|
||||
},
|
||||
"severity_id": 3,
|
||||
"severity": "Medium",
|
||||
"status": "New",
|
||||
"status_code": "FAIL",
|
||||
"status_detail": "Audit log max age is not set to 30 or as appropriate in pod <pod>.",
|
||||
"status_id": 1,
|
||||
"unmapped": {
|
||||
"related_url": "https://kubernetes.io/docs/concepts/cluster-administration/audit/",
|
||||
"categories": [
|
||||
"logging"
|
||||
],
|
||||
"depends_on": [],
|
||||
"related_to": [],
|
||||
"notes": "Ensure the audit log retention period is set appropriately to balance between storage constraints and the need for historical data.",
|
||||
"compliance": {
|
||||
"CIS-1.10": [
|
||||
"1.2.17"
|
||||
],
|
||||
"CIS-1.8": [
|
||||
"1.2.18"
|
||||
]
|
||||
}
|
||||
},
|
||||
"activity_name": "Create",
|
||||
"activity_id": 1,
|
||||
"finding_info": {
|
||||
"created_time": 1739539658,
|
||||
"created_time_dt": "2025-02-14T14:27:38.533897",
|
||||
"desc": "This check ensures that the Kubernetes API server is configured with an appropriate audit log retention period. Setting --audit-log-maxage to 30 or as per business requirements helps in maintaining logs for sufficient time to investigate past events.",
|
||||
"product_uid": "prowler",
|
||||
"title": "Ensure that the --audit-log-maxage argument is set to 30 or as appropriate",
|
||||
"types": [],
|
||||
"uid": "<finding_uid>"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"data": {
|
||||
"details": "",
|
||||
"metadata": {
|
||||
"name": "<pod>",
|
||||
"uid": "<resource_uid>",
|
||||
"namespace": "<namespace>",
|
||||
"labels": {
|
||||
"component": "kube-apiserver",
|
||||
"tier": "control-plane"
|
||||
},
|
||||
"annotations": {
|
||||
"kubernetes.io/config.source": "file"
|
||||
},
|
||||
"node_name": "<node_name>",
|
||||
"service_account": null,
|
||||
"status_phase": "Running",
|
||||
"pod_ip": "<ip>",
|
||||
"host_ip": "<ip>",
|
||||
"host_pid": null,
|
||||
"host_ipc": null,
|
||||
"host_network": "True",
|
||||
"security_context": {
|
||||
"app_armor_profile": null,
|
||||
"fs_group": null,
|
||||
"fs_group_change_policy": null,
|
||||
"run_as_group": null,
|
||||
"run_as_non_root": null,
|
||||
"run_as_user": null,
|
||||
"se_linux_change_policy": null,
|
||||
"se_linux_options": null,
|
||||
"seccomp_profile": {
|
||||
"localhost_profile": null,
|
||||
"type": "RuntimeDefault"
|
||||
},
|
||||
"supplemental_groups": null,
|
||||
"supplemental_groups_policy": null,
|
||||
"sysctls": null,
|
||||
"windows_options": null
|
||||
},
|
||||
"containers": {
|
||||
"kube-apiserver": {
|
||||
"name": "kube-apiserver",
|
||||
"image": "<image>",
|
||||
"command": [
|
||||
"<command>"
|
||||
],
|
||||
"ports": null,
|
||||
"env": null,
|
||||
"security_context": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"name": "apiserver"
|
||||
},
|
||||
"labels": [],
|
||||
"name": "<pod>",
|
||||
"namespace": "<namespace>",
|
||||
"type": "KubernetesAPIServer",
|
||||
"uid": "<resource_uid>"
|
||||
}
|
||||
],
|
||||
"category_name": "Findings",
|
||||
"category_uid": 2,
|
||||
"class_name": "Detection Finding",
|
||||
"class_uid": 2004,
|
||||
"remediation": {
|
||||
"desc": "Configure the API server audit log retention period to retain logs for at least 30 days or as per your organization's requirements.",
|
||||
"references": [
|
||||
"https://docs.prowler.com/checks/kubernetes/kubernetes-policy-index/ensure-that-the-audit-log-maxage-argument-is-set-to-30-or-as-appropriate#kubernetes",
|
||||
"--audit-log-maxage=30",
|
||||
"https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/"
|
||||
]
|
||||
},
|
||||
"risk_details": "Without an adequate log retention period, there may be insufficient audit history to investigate and analyze past events or security incidents.",
|
||||
"time": 1739539658,
|
||||
"time_dt": "2025-02-14T14:27:38.533897",
|
||||
"type_uid": 200401,
|
||||
"type_name": "Detection Finding: Create"
|
||||
},
|
||||
{
|
||||
"message": "Audit log max backup is not set to 10 or as appropriate in pod <pod>.",
|
||||
"metadata": {
|
||||
"event_code": "apiserver_audit_log_maxbackup_set",
|
||||
"product": {
|
||||
"name": "Prowler",
|
||||
"uid": "prowler",
|
||||
"vendor_name": "Prowler",
|
||||
"version": "5.4.0"
|
||||
},
|
||||
"profiles": [
|
||||
"container",
|
||||
"datetime"
|
||||
],
|
||||
"version": "1.4.0"
|
||||
},
|
||||
"severity_id": 3,
|
||||
"severity": "Medium",
|
||||
"status": "New",
|
||||
"status_code": "FAIL",
|
||||
"status_detail": "Audit log max backup is not set to 10 or as appropriate in pod <pod>.",
|
||||
"status_id": 1,
|
||||
"unmapped": {
|
||||
"related_url": "https://kubernetes.io/docs/concepts/cluster-administration/audit/",
|
||||
"categories": [
|
||||
"logging"
|
||||
],
|
||||
"depends_on": [],
|
||||
"related_to": [],
|
||||
"notes": "Ensure the audit log backup retention period is set appropriately to balance between storage constraints and the need for historical data.",
|
||||
"compliance": {
|
||||
"CIS-1.10": [
|
||||
"1.2.18"
|
||||
],
|
||||
"CIS-1.8": [
|
||||
"1.2.19"
|
||||
]
|
||||
}
|
||||
},
|
||||
"activity_name": "Create",
|
||||
"activity_id": 1,
|
||||
"finding_info": {
|
||||
"created_time": 1739539658,
|
||||
"created_time_dt": "2025-02-14T14:27:38.533897",
|
||||
"desc": "This check ensures that the Kubernetes API server is configured with an appropriate number of audit log backups. Setting --audit-log-maxbackup to 10 or as per business requirements helps maintain a sufficient log backup for investigations or analysis.",
|
||||
"product_uid": "prowler",
|
||||
"title": "Ensure that the --audit-log-maxbackup argument is set to 10 or as appropriate",
|
||||
"types": [],
|
||||
"uid": "<finding_uid>"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"data": {
|
||||
"details": "",
|
||||
"metadata": {
|
||||
"name": "<pod>",
|
||||
"uid": "<resource_uid>",
|
||||
"namespace": "<namespace>",
|
||||
"labels": {
|
||||
"component": "kube-apiserver",
|
||||
"tier": "control-plane"
|
||||
},
|
||||
"annotations": {
|
||||
"kubernetes.io/config.source": "file"
|
||||
},
|
||||
"node_name": "<node_name>",
|
||||
"service_account": null,
|
||||
"status_phase": "Running",
|
||||
"pod_ip": "<ip>",
|
||||
"host_ip": "<ip>",
|
||||
"host_pid": null,
|
||||
"host_ipc": null,
|
||||
"host_network": "True",
|
||||
"security_context": {
|
||||
"app_armor_profile": null,
|
||||
"fs_group": null,
|
||||
"fs_group_change_policy": null,
|
||||
"run_as_group": null,
|
||||
"run_as_non_root": null,
|
||||
"run_as_user": null,
|
||||
"se_linux_change_policy": null,
|
||||
"se_linux_options": null,
|
||||
"seccomp_profile": {
|
||||
"localhost_profile": null,
|
||||
"type": "RuntimeDefault"
|
||||
},
|
||||
"supplemental_groups": null,
|
||||
"supplemental_groups_policy": null,
|
||||
"sysctls": null,
|
||||
"windows_options": null
|
||||
},
|
||||
"containers": {
|
||||
"kube-apiserver": {
|
||||
"name": "kube-apiserver",
|
||||
"image": "<image>",
|
||||
"command": [
|
||||
"<command>"
|
||||
],
|
||||
"ports": null,
|
||||
"env": null,
|
||||
"security_context": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"name": "apiserver"
|
||||
},
|
||||
"labels": [],
|
||||
"name": "<pod>",
|
||||
"namespace": "<namespace>",
|
||||
"type": "KubernetesAPIServer",
|
||||
"uid": "<resource_uid>"
|
||||
}
|
||||
],
|
||||
"category_name": "Findings",
|
||||
"category_uid": 2,
|
||||
"class_name": "Detection Finding",
|
||||
"class_uid": 2004,
|
||||
"remediation": {
|
||||
"desc": "Configure the API server audit log backup retention to 10 or as per your organization's requirements.",
|
||||
"references": [
|
||||
"https://docs.prowler.com/checks/kubernetes/kubernetes-policy-index/ensure-that-the-audit-log-maxbackup-argument-is-set-to-10-or-as-appropriate#kubernetes",
|
||||
"--audit-log-maxbackup=10",
|
||||
"https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/"
|
||||
]
|
||||
},
|
||||
"risk_details": "Without an adequate number of audit log backups, there may be insufficient log history to investigate past events or security incidents.",
|
||||
"time": 1739539658,
|
||||
"time_dt": "2025-02-14T14:27:38.533897",
|
||||
"type_uid": 200401,
|
||||
"type_name": "Detection Finding: Create"
|
||||
},
|
||||
{
|
||||
"message": "Audit log max size is not set to 100 MB or as appropriate in pod <pod>.",
|
||||
"metadata": {
|
||||
"event_code": "apiserver_audit_log_maxsize_set",
|
||||
"product": {
|
||||
"name": "Prowler",
|
||||
"uid": "prowler",
|
||||
"vendor_name": "Prowler",
|
||||
"version": "5.4.0"
|
||||
},
|
||||
"profiles": [
|
||||
"container",
|
||||
"datetime"
|
||||
],
|
||||
"version": "1.4.0"
|
||||
},
|
||||
"severity_id": 3,
|
||||
"severity": "Medium",
|
||||
"status": "New",
|
||||
"status_code": "FAIL",
|
||||
"status_detail": "Audit log max size is not set to 100 MB or as appropriate in pod <pod>.",
|
||||
"status_id": 1,
|
||||
"unmapped": {
|
||||
"related_url": "https://kubernetes.io/docs/concepts/cluster-administration/audit/",
|
||||
"categories": [
|
||||
"logging"
|
||||
],
|
||||
"depends_on": [],
|
||||
"related_to": [],
|
||||
"notes": "Adjust the audit log file size limit based on your organization's storage capabilities and logging requirements.",
|
||||
"compliance": {
|
||||
"CIS-1.10": [
|
||||
"1.2.19"
|
||||
],
|
||||
"CIS-1.8": [
|
||||
"1.2.20"
|
||||
]
|
||||
}
|
||||
},
|
||||
"activity_name": "Create",
|
||||
"activity_id": 1,
|
||||
"finding_info": {
|
||||
"created_time": 1739539658,
|
||||
"created_time_dt": "2025-02-14T14:27:38.533897",
|
||||
"desc": "This check ensures that the Kubernetes API server is configured with an appropriate audit log file size limit. Setting --audit-log-maxsize to 100 MB or as per business requirements helps manage the size of log files and prevents them from growing excessively large.",
|
||||
"product_uid": "prowler",
|
||||
"title": "Ensure that the --audit-log-maxsize argument is set to 100 or as appropriate",
|
||||
"types": [],
|
||||
"uid": "<finding_uid>"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"data": {
|
||||
"details": "",
|
||||
"metadata": {
|
||||
"name": "<pod>",
|
||||
"uid": "<resource_uid>",
|
||||
"namespace": "<namespace>",
|
||||
"labels": {
|
||||
"component": "kube-apiserver",
|
||||
"tier": "control-plane"
|
||||
},
|
||||
"annotations": {
|
||||
"kubernetes.io/config.source": "file"
|
||||
},
|
||||
"node_name": "<node_name>",
|
||||
"service_account": null,
|
||||
"status_phase": "Running",
|
||||
"pod_ip": "<ip>",
|
||||
"host_ip": "<ip>",
|
||||
"host_pid": null,
|
||||
"host_ipc": null,
|
||||
"host_network": "True",
|
||||
"security_context": {
|
||||
"app_armor_profile": null,
|
||||
"fs_group": null,
|
||||
"fs_group_change_policy": null,
|
||||
"run_as_group": null,
|
||||
"run_as_non_root": null,
|
||||
"run_as_user": null,
|
||||
"se_linux_change_policy": null,
|
||||
"se_linux_options": null,
|
||||
"seccomp_profile": {
|
||||
"localhost_profile": null,
|
||||
"type": "RuntimeDefault"
|
||||
},
|
||||
"supplemental_groups": null,
|
||||
"supplemental_groups_policy": null,
|
||||
"sysctls": null,
|
||||
"windows_options": null
|
||||
},
|
||||
"containers": {
|
||||
"kube-apiserver": {
|
||||
"name": "kube-apiserver",
|
||||
"image": "<image>",
|
||||
"command": [
|
||||
"<command>"
|
||||
],
|
||||
"ports": null,
|
||||
"env": null,
|
||||
"security_context": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"name": "apiserver"
|
||||
},
|
||||
"labels": [],
|
||||
"name": "<pod>",
|
||||
"namespace": "<namespace>",
|
||||
"type": "KubernetesAPIServer",
|
||||
"uid": "<resource_uid>"
|
||||
}
|
||||
],
|
||||
"category_name": "Findings",
|
||||
"category_uid": 2,
|
||||
"class_name": "Detection Finding",
|
||||
"class_uid": 2004,
|
||||
"remediation": {
|
||||
"desc": "Configure the API server audit log file size limit to 100 MB or as per your organization's requirements.",
|
||||
"references": [
|
||||
"https://docs.prowler.com/checks/kubernetes/kubernetes-policy-index/ensure-that-the-audit-log-maxsize-argument-is-set-to-100-or-as-appropriate#kubernetes",
|
||||
"--audit-log-maxsize=100",
|
||||
"https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/"
|
||||
]
|
||||
},
|
||||
"risk_details": "Without an appropriate audit log file size limit, log files can grow excessively large, potentially leading to storage issues and difficulty in log analysis.",
|
||||
"time": 1739539658,
|
||||
"time_dt": "2025-02-14T14:27:38.533897",
|
||||
"type_uid": 200401,
|
||||
"type_name": "Detection Finding: Create"
|
||||
},
|
||||
{
|
||||
"message": "Audit log path is not set in pod <pod>.",
|
||||
"metadata": {
|
||||
"event_code": "apiserver_audit_log_path_set",
|
||||
"product": {
|
||||
"name": "Prowler",
|
||||
"uid": "prowler",
|
||||
"vendor_name": "Prowler",
|
||||
"version": "5.4.0"
|
||||
},
|
||||
"profiles": [
|
||||
"container",
|
||||
"datetime"
|
||||
],
|
||||
"version": "1.4.0"
|
||||
},
|
||||
"severity_id": 4,
|
||||
"severity": "High",
|
||||
"status": "New",
|
||||
"status_code": "FAIL",
|
||||
"status_detail": "Audit log path is not set in pod <pod>.",
|
||||
"status_id": 1,
|
||||
"unmapped": {
|
||||
"related_url": "https://kubernetes.io/docs/concepts/cluster-administration/audit/",
|
||||
"categories": [
|
||||
"logging"
|
||||
],
|
||||
"depends_on": [],
|
||||
"related_to": [],
|
||||
"notes": "Audit logs are not enabled by default in Kubernetes. Configuring them is essential for security monitoring and forensic analysis.",
|
||||
"compliance": {
|
||||
"CIS-1.10": [
|
||||
"1.2.16"
|
||||
],
|
||||
"CIS-1.8": [
|
||||
"1.2.17"
|
||||
]
|
||||
}
|
||||
},
|
||||
"activity_name": "Create",
|
||||
"activity_id": 1,
|
||||
"finding_info": {
|
||||
"created_time": 1739539658,
|
||||
"created_time_dt": "2025-02-14T14:27:38.533897",
|
||||
"desc": "This check verifies that the Kubernetes API server is configured with an audit log path. Enabling audit logs helps in maintaining a chronological record of all activities and operations which can be critical for security analysis and troubleshooting.",
|
||||
"product_uid": "prowler",
|
||||
"title": "Ensure that the --audit-log-path argument is set",
|
||||
"types": [],
|
||||
"uid": "<finding_uid>"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"data": {
|
||||
"details": "",
|
||||
"metadata": {
|
||||
"name": "<pod>",
|
||||
"uid": "<resource_uid>",
|
||||
"namespace": "<namespace>",
|
||||
"labels": {
|
||||
"component": "kube-apiserver",
|
||||
"tier": "control-plane"
|
||||
},
|
||||
"annotations": {
|
||||
"kubernetes.io/config.source": "file"
|
||||
},
|
||||
"node_name": "<node_name>",
|
||||
"service_account": null,
|
||||
"status_phase": "Running",
|
||||
"pod_ip": "<ip>",
|
||||
"host_ip": "<ip>",
|
||||
"host_pid": null,
|
||||
"host_ipc": null,
|
||||
"host_network": "True",
|
||||
"security_context": {
|
||||
"app_armor_profile": null,
|
||||
"fs_group": null,
|
||||
"fs_group_change_policy": null,
|
||||
"run_as_group": null,
|
||||
"run_as_non_root": null,
|
||||
"run_as_user": null,
|
||||
"se_linux_change_policy": null,
|
||||
"se_linux_options": null,
|
||||
"seccomp_profile": {
|
||||
"localhost_profile": null,
|
||||
"type": "RuntimeDefault"
|
||||
},
|
||||
"supplemental_groups": null,
|
||||
"supplemental_groups_policy": null,
|
||||
"sysctls": null,
|
||||
"windows_options": null
|
||||
},
|
||||
"containers": {
|
||||
"kube-apiserver": {
|
||||
"name": "kube-apiserver",
|
||||
"image": "<image>",
|
||||
"command": [
|
||||
"<command>"
|
||||
],
|
||||
"ports": null,
|
||||
"env": null,
|
||||
"security_context": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"name": "apiserver"
|
||||
},
|
||||
"labels": [],
|
||||
"name": "<pod>",
|
||||
"namespace": "<namespace>",
|
||||
"type": "KubernetesAPIServer",
|
||||
"uid": "<resource_uid>"
|
||||
}
|
||||
],
|
||||
"category_name": "Findings",
|
||||
"category_uid": 2,
|
||||
"class_name": "Detection Finding",
|
||||
"class_uid": 2004,
|
||||
"remediation": {
|
||||
"desc": "Enable audit logging in the API server by specifying a valid path for --audit-log-path to ensure comprehensive activity logging within the cluster.",
|
||||
"references": [
|
||||
"https://docs.prowler.com/checks/kubernetes/kubernetes-policy-index/ensure-that-the-audit-log-path-argument-is-set#kubernetes",
|
||||
"--audit-log-path=/var/log/apiserver/audit.log",
|
||||
"https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/"
|
||||
]
|
||||
},
|
||||
"risk_details": "Without audit logs, it becomes difficult to track changes and activities within the cluster, potentially obscuring the detection of malicious activities or operational issues.",
|
||||
"time": 1739539658,
|
||||
"time_dt": "2025-02-14T14:27:38.533897",
|
||||
"type_uid": 200401,
|
||||
"type_name": "Detection Finding: Create"
|
||||
}
|
||||
]
|
||||
@@ -1,418 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Setup and Configure Logger\n",
|
||||
"This section configures the Python logging system to filter Prowler's output messages during security scans. We set the logging level to `CRITICAL`."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import logging\n",
|
||||
"\n",
|
||||
"logging.basicConfig(level=logging.CRITICAL)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Initialize AWS Provider"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Import the Prowler's provider you want to scan\n",
|
||||
"from prowler.providers.aws.aws_provider import AwsProvider\n",
|
||||
"import json\n",
|
||||
"\n",
|
||||
"# Path to credentials file\n",
|
||||
"credentials_path = \"./secrets-sdk/credentials.json\"\n",
|
||||
"\n",
|
||||
"# Load credentials from JSON file\n",
|
||||
"try:\n",
|
||||
" with open(credentials_path, \"r\") as f:\n",
|
||||
" aws_credentials = json.load(f)\n",
|
||||
" print(\"AWS credentials loaded successfully from file\")\n",
|
||||
"except (FileNotFoundError, json.JSONDecodeError):\n",
|
||||
" print(\"Invalid or missing JSON credentials file\")\n",
|
||||
" aws_credentials = {\n",
|
||||
" \"aws_access_key_id\": \"YOUR_ACCESS_KEY\",\n",
|
||||
" \"aws_secret_access_key\": \"YOUR_SECRET_KEY\",\n",
|
||||
" \"aws_session_token\": \"YOUR_SESSION_TOKEN\"\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
"# Optional: Test the AWS provider credentials before instantiation to verify that credentials work\n",
|
||||
"aws_connection = AwsProvider.test_connection(**aws_credentials)\n",
|
||||
"print(f\"AWS Test Connection:\\n\\t- Connected: {aws_connection.is_connected}\\n\\t- Error (if any): {aws_connection.error}\\n\")\n",
|
||||
"\n",
|
||||
"# Initialize the AWS provider with static credentials\n",
|
||||
"aws = AwsProvider(**aws_credentials)\n",
|
||||
"\n",
|
||||
"# AWS Identity Information\n",
|
||||
"print(f\"AWS Identity Information:\\n\\t- Account Number: {aws.identity.account}\\n\\t- User ID: {aws.identity.user_id}\\n\")\n",
|
||||
"\n",
|
||||
"# Alternative Providers (commented out)\n",
|
||||
"# from prowler.providers.gcp.gcp_provider import GcpProvider\n",
|
||||
"# from prowler.providers.azure.azure_provider import AzureProvider\n",
|
||||
"# from prowler.providers.kubernetes.kubernetes_provider import KubernetesProvider\n",
|
||||
"# from prowler.providers.microsoft365.microsoft365_provider import Microsoft365Provider"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pprint\n",
|
||||
"pprint.pp(aws.identity)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Mutelist"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Mutelist\n",
|
||||
"from prowler.providers.aws.lib.mutelist.mutelist import AWSMutelist\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"mutelist_content = {\n",
|
||||
" \"Accounts\": {\n",
|
||||
" \"*\": {\n",
|
||||
" \"Checks\": {\n",
|
||||
" \"s3_account_level_public_access_blocks\": {\n",
|
||||
" \"Tags\": [\"*\"],\n",
|
||||
" \"Regions\": [\"*\"],\n",
|
||||
" \"Resources\": [\"*\"],\n",
|
||||
" }\n",
|
||||
" }\n",
|
||||
" }\n",
|
||||
" }\n",
|
||||
"}\n",
|
||||
"mutelist_object = AWSMutelist(\n",
|
||||
" mutelist_content=mutelist_content,\n",
|
||||
")\n",
|
||||
"aws._mutelist = mutelist_object"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## List Available Security Checks\n",
|
||||
"Explore different ways to list security checks by provider, service, severity, and category."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Import the CheckMetadata class to list security checks\n",
|
||||
"from prowler.lib.check.models import CheckMetadata\n",
|
||||
"\n",
|
||||
"# List all available checks\n",
|
||||
"checks = CheckMetadata.list()\n",
|
||||
"print(f\"# Checks: {len(checks)}\")\n",
|
||||
"\n",
|
||||
"# List all AWS S3 checks\n",
|
||||
"aws_s3_checks = CheckMetadata.list(provider=\"aws\", service=\"s3\")\n",
|
||||
"print(f\"AWS S3 Checks:\\n\\t- {'\\n\\t- '.join(aws_s3_checks)}\")\n",
|
||||
"\n",
|
||||
"# List all critical severity checks\n",
|
||||
"critical_checks = CheckMetadata.list(provider=\"aws\", severity=\"critical\")\n",
|
||||
"print(f\"\\n# Critical Checks: {len(critical_checks)}\")\n",
|
||||
"\n",
|
||||
"# List all checks in the internet-exposed category\n",
|
||||
"internet_exposed_checks = CheckMetadata.list(provider=\"aws\", category=\"internet-exposed\")\n",
|
||||
"print(f\"\\n# Internet-Exposed Category Checks: {len(internet_exposed_checks)}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Execute Security Scans\n",
|
||||
"Set up and execute security scans on AWS resources with different filtering options."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Import necessary libraries for scanning\n",
|
||||
"from prowler.lib.scan.scan import Scan\n",
|
||||
"# Auxiliary libraries\n",
|
||||
"import itertools\n",
|
||||
"\n",
|
||||
"# Set up the Scan class to scan all checks for the provider\n",
|
||||
"scan = Scan(provider=aws)\n",
|
||||
"\n",
|
||||
"# Parametrize the Scan to execute several checks, services, categories, compliances, etc.\n",
|
||||
"scan_s3 = Scan(provider=aws, services=[\"s3\"], severities=[\"critical\", \"high\"])\n",
|
||||
"# scan_critical = Scan(provider=aws, severities=[\"critical\"])\n",
|
||||
"# scan_internet_exposed = Scan(provider=aws, categories=[\"internet-exposed\"])\n",
|
||||
"\n",
|
||||
"# Start the scan with the `scan` method. This returns a generator with findings and progress.\n",
|
||||
"print(\"\\n##### Scanning AWS #####\")\n",
|
||||
"all_findings = []\n",
|
||||
"total_findings = 0\n",
|
||||
"for progress, findings in scan_s3.scan():\n",
|
||||
" all_findings.extend(findings)\n",
|
||||
" total_findings += len(findings)\n",
|
||||
" print(f\"- Scan Progress: {progress}\\n- # Findings: {total_findings}\\n\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Process and Display Findings\n",
|
||||
"Process the scan results and display detailed information about each finding."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"print(\"Finding's Details:\")\n",
|
||||
"for finding in all_findings:\n",
|
||||
" print(f\"\"\"\n",
|
||||
" - Check ID: {finding.metadata.CheckID}\n",
|
||||
" - Status: {str(finding.status)}\n",
|
||||
" - Status Extended: {finding.status_extended}\n",
|
||||
" - Resource ID: {finding.resource_uid}\n",
|
||||
" - Resource Metadata: {finding.resource_metadata}\n",
|
||||
" \"\"\")\n",
|
||||
"\n",
|
||||
"# Retrieve all findings in one line\n",
|
||||
"print(\"\\n##### Getting all findings in one line #####\")\n",
|
||||
"scan_s3 = Scan(provider=aws, services=[\"s3\"], severities=[\"critical\"])\n",
|
||||
"all_findings_one_line = list(itertools.chain.from_iterable(findings for _, findings in scan_s3.scan()))\n",
|
||||
"print(f\"Total findings collected in one line: {len(all_findings_one_line)}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Check Metatada\n",
|
||||
"```plain\n",
|
||||
"CheckMetadata(\n",
|
||||
" Provider='aws'\n",
|
||||
" CheckID='s3_bucket_policy_public_write_access'\n",
|
||||
" CheckTitle='Check if S3 buckets have policies which allow WRITE access.'\n",
|
||||
" CheckType=['IAM']\n",
|
||||
" CheckAliases=[]\n",
|
||||
" ServiceName='s3'\n",
|
||||
" SubServiceName=''\n",
|
||||
" ResourceIdTemplate='arn:partition:s3:::bucket_name'\n",
|
||||
" Severity=<Severity.critical: 'critical'>\n",
|
||||
" ResourceType='AwsS3Bucket'\n",
|
||||
" Description='Check if S3 buckets have policies which allow WRITE access.'\n",
|
||||
" Risk='Non intended users can put objects in a given bucket.'\n",
|
||||
" RelatedUrl=''\n",
|
||||
" Remediation=\n",
|
||||
" Remediation(\n",
|
||||
" Code=Code(\n",
|
||||
" NativeIaC=''\n",
|
||||
" Terraform=''\n",
|
||||
" CLI=''\n",
|
||||
" Other='https://docs.prowler.com/checks/aws/s3-policies/s3_18-write-permissions-public#aws-console')\n",
|
||||
" Recommendation=Recommendation(Text='Ensure proper bucket policy is in place with the least privilege principle applied.'\n",
|
||||
" Url='https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_s3_rw-bucket.html'\n",
|
||||
" )\n",
|
||||
" )\n",
|
||||
" Categories=['internet-exposed']\n",
|
||||
" DependsOn=[]\n",
|
||||
" RelatedTo=[]\n",
|
||||
" Notes=''\n",
|
||||
" # Compliance framework: A list of requirement IDs where the check is present.\n",
|
||||
" Compliance={\n",
|
||||
" \"CIS-1.10\": [\"5.2.13\"],\n",
|
||||
" \"CIS-1.8\": [\"5.2.13\"]\n",
|
||||
" }\n",
|
||||
")\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Output Formats"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Import necessary libraries for output\n",
|
||||
"from prowler.lib.outputs.csv.csv import CSV\n",
|
||||
"from prowler.lib.outputs.ocsf.ocsf import OCSF\n",
|
||||
"from prowler.lib.outputs.asff.asff import ASFF # Only for AWS\n",
|
||||
"from prowler.lib.outputs.html.html import HTML\n",
|
||||
"from prowler.lib.outputs.outputs import extract_findings_statistics\n",
|
||||
"import datetime\n",
|
||||
"\n",
|
||||
"# Get current date and time in YYYY-MM-DD_HH-MM-SS format for filenames\n",
|
||||
"current_datetime = datetime.datetime.now().strftime(\"%Y-%m-%d_%H-%M-%S\")\n",
|
||||
"\n",
|
||||
"# Write findings to CSV file\n",
|
||||
"print(\"Writing findings to CSV file...\")\n",
|
||||
"csv_filename = f\"./output/findings_{current_datetime}.csv\"\n",
|
||||
"csv = CSV(findings=all_findings, create_file_descriptor=True, file_path=csv_filename)\n",
|
||||
"csv.batch_write_data_to_file()\n",
|
||||
"print(f\"Done! CSV File Path: {csv._file_descriptor.name}\")\n",
|
||||
"\n",
|
||||
"# Write findings to OCSF file\n",
|
||||
"print(\"Writing findings to OCSF file...\")\n",
|
||||
"ocsf_filename = f\"./output/findings_{current_datetime}.ocsf\"\n",
|
||||
"ocsf = OCSF(findings=all_findings, create_file_descriptor=True, file_path=ocsf_filename)\n",
|
||||
"ocsf.batch_write_data_to_file()\n",
|
||||
"print(f\"Done! OCSF File Path: {ocsf._file_descriptor.name}\")\n",
|
||||
"\n",
|
||||
"# Write findings to ASFF file\n",
|
||||
"print(\"Writing findings to ASFF file...\")\n",
|
||||
"asff_filename = f\"./output/findings_{current_datetime}.asff\"\n",
|
||||
"asff = ASFF(findings=all_findings, create_file_descriptor=True, file_path=asff_filename)\n",
|
||||
"asff.batch_write_data_to_file()\n",
|
||||
"print(f\"Done! ASFF File Path: {asff._file_descriptor.name}\")\n",
|
||||
"\n",
|
||||
"# Write findings to HTML file\n",
|
||||
"print(\"Writing findings to HTML file...\")\n",
|
||||
"html_filename = f\"./output/findings_{current_datetime}.html\"\n",
|
||||
"stats = extract_findings_statistics(all_findings)\n",
|
||||
"html = HTML(findings=all_findings, create_file_descriptor=True, file_path=html_filename)\n",
|
||||
"html.batch_write_data_to_file(provider=aws, stats=stats)\n",
|
||||
"print(f\"Done! HTML File Path: {html._file_descriptor.name}\")\n",
|
||||
"\n",
|
||||
"# IMPORTANT: The create_file_descriptor parameter will be removed in 5.4.0\n",
|
||||
"# The file descriptor will be created by default when the Output class is instantiated"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Integrate with AWS S3\n",
|
||||
"Send findings to AWS S3."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Import the S3 class to send findings to AWS S3\n",
|
||||
"from prowler.providers.aws.lib.s3.s3 import S3\n",
|
||||
"\n",
|
||||
"print(\"\\n##### Sending findings to S3 bucket #####\")\n",
|
||||
"generated_outputs = {\"regular\": [csv, ocsf, asff, html], \"compliance\": []}\n",
|
||||
"s3_integration = S3(aws.session.current_session, bucket_name=\"sdk-core\", output_directory=\"output\")\n",
|
||||
"s3_integration.send_to_bucket(generated_outputs)\n",
|
||||
"\n",
|
||||
"# This upload the output files to the S3 bucket. In this case:\n",
|
||||
"# sdk-core/output/csv/findings_2025-02-26_16-25-30.csv\n",
|
||||
"# sdk-core/output/ocsf/findings_2025-02-26_16-25-30.ocsf\n",
|
||||
"# sdk-core/output/asff/findings_2025-02-26_16-25-30.asff"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Integrate with AWS Security Hub\n",
|
||||
"Send findings to AWS Security Hub for centralized security monitoring."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Import the SecurityHub class to send findings to AWS Security Hub\n",
|
||||
"from prowler.providers.aws.lib.security_hub.security_hub import SecurityHub\n",
|
||||
"\n",
|
||||
"# Print message indicating the start of the process\n",
|
||||
"print(\"\\n##### Sending findings to AWS Security Hub #####\")\n",
|
||||
"\n",
|
||||
"# Get available AWS regions for Security Hub.\n",
|
||||
"# Each finding can only be sent to Security Hub within its own region.\n",
|
||||
"# Additionally, it verifies that Prowler’s integration is active in\n",
|
||||
"# Security Hub before sending\n",
|
||||
"available_regions = aws.get_available_aws_service_regions(\n",
|
||||
" \"securityhub\",\n",
|
||||
" aws.identity.partition,\n",
|
||||
" aws.identity.audited_regions,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Initialize the SecurityHub class with necessary parameters\n",
|
||||
"security_hub = SecurityHub(\n",
|
||||
" aws_account_id=aws.identity.account,\n",
|
||||
" aws_partition=aws.identity.partition,\n",
|
||||
" aws_session=aws.session.current_session,\n",
|
||||
" findings=asff.data,\n",
|
||||
" send_only_fails=False,\n",
|
||||
" aws_security_hub_available_regions=available_regions,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Send findings to AWS Security Hub\n",
|
||||
"findings_sent_to_security_hub = security_hub.batch_send_to_security_hub()\n",
|
||||
"\n",
|
||||
"# Print the number of findings sent to AWS Security Hub\n",
|
||||
"print(f\"{findings_sent_to_security_hub} findings sent to AWS Security Hub!\")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "prowler-HDV3a8VZ-py3.12",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.8"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
@@ -94,9 +94,6 @@ nav:
|
||||
- In-Cluster Execution: tutorials/kubernetes/in-cluster.md
|
||||
- Non In-Cluster Execution: tutorials/kubernetes/outside-cluster.md
|
||||
- Miscellaneous: tutorials/kubernetes/misc.md
|
||||
- Microsoft 365:
|
||||
- Authentication: tutorials/microsoft365/authentication.md
|
||||
- Create Prowler Service Principal: tutorials/microsoft365/create-prowler-service-principal.md
|
||||
- Developer Guide:
|
||||
- Introduction: developer-guide/introduction.md
|
||||
- Provider: developer-guide/provider.md
|
||||
|
||||
Generated
+201
-208
@@ -1,4 +1,4 @@
|
||||
# This file is automatically @generated by Poetry 1.8.0 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "about-time"
|
||||
@@ -680,13 +680,13 @@ dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
|
||||
|
||||
[[package]]
|
||||
name = "bandit"
|
||||
version = "1.8.3"
|
||||
version = "1.8.2"
|
||||
description = "Security oriented static analyser for python code."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "bandit-1.8.3-py3-none-any.whl", hash = "sha256:28f04dc0d258e1dd0f99dee8eefa13d1cb5e3fde1a5ab0c523971f97b289bcd8"},
|
||||
{file = "bandit-1.8.3.tar.gz", hash = "sha256:f5847beb654d309422985c36644649924e0ea4425c76dec2e89110b87506193a"},
|
||||
{file = "bandit-1.8.2-py3-none-any.whl", hash = "sha256:df6146ad73dd30e8cbda4e29689ddda48364e36ff655dbfc86998401fcf1721f"},
|
||||
{file = "bandit-1.8.2.tar.gz", hash = "sha256:e00ad5a6bc676c0954669fe13818024d66b70e42cf5adb971480cf3b671e835f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -704,33 +704,33 @@ yaml = ["PyYAML"]
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "25.1.0"
|
||||
version = "24.10.0"
|
||||
description = "The uncompromising code formatter."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32"},
|
||||
{file = "black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da"},
|
||||
{file = "black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7"},
|
||||
{file = "black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9"},
|
||||
{file = "black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0"},
|
||||
{file = "black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299"},
|
||||
{file = "black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096"},
|
||||
{file = "black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2"},
|
||||
{file = "black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b"},
|
||||
{file = "black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc"},
|
||||
{file = "black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f"},
|
||||
{file = "black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba"},
|
||||
{file = "black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f"},
|
||||
{file = "black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3"},
|
||||
{file = "black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171"},
|
||||
{file = "black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18"},
|
||||
{file = "black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0"},
|
||||
{file = "black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f"},
|
||||
{file = "black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e"},
|
||||
{file = "black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355"},
|
||||
{file = "black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717"},
|
||||
{file = "black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666"},
|
||||
{file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"},
|
||||
{file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"},
|
||||
{file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"},
|
||||
{file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"},
|
||||
{file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"},
|
||||
{file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"},
|
||||
{file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"},
|
||||
{file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"},
|
||||
{file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"},
|
||||
{file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"},
|
||||
{file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"},
|
||||
{file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"},
|
||||
{file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"},
|
||||
{file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"},
|
||||
{file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"},
|
||||
{file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"},
|
||||
{file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"},
|
||||
{file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"},
|
||||
{file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"},
|
||||
{file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"},
|
||||
{file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"},
|
||||
{file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1072,74 +1072,73 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.6.12"
|
||||
version = "7.6.10"
|
||||
description = "Code coverage measurement for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "coverage-7.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:704c8c8c6ce6569286ae9622e534b4f5b9759b6f2cd643f1c1a61f666d534fe8"},
|
||||
{file = "coverage-7.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad7525bf0241e5502168ae9c643a2f6c219fa0a283001cee4cf23a9b7da75879"},
|
||||
{file = "coverage-7.6.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06097c7abfa611c91edb9e6920264e5be1d6ceb374efb4986f38b09eed4cb2fe"},
|
||||
{file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:220fa6c0ad7d9caef57f2c8771918324563ef0d8272c94974717c3909664e674"},
|
||||
{file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3688b99604a24492bcfe1c106278c45586eb819bf66a654d8a9a1433022fb2eb"},
|
||||
{file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d1a987778b9c71da2fc8948e6f2656da6ef68f59298b7e9786849634c35d2c3c"},
|
||||
{file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cec6b9ce3bd2b7853d4a4563801292bfee40b030c05a3d29555fd2a8ee9bd68c"},
|
||||
{file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ace9048de91293e467b44bce0f0381345078389814ff6e18dbac8fdbf896360e"},
|
||||
{file = "coverage-7.6.12-cp310-cp310-win32.whl", hash = "sha256:ea31689f05043d520113e0552f039603c4dd71fa4c287b64cb3606140c66f425"},
|
||||
{file = "coverage-7.6.12-cp310-cp310-win_amd64.whl", hash = "sha256:676f92141e3c5492d2a1596d52287d0d963df21bf5e55c8b03075a60e1ddf8aa"},
|
||||
{file = "coverage-7.6.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e18aafdfb3e9ec0d261c942d35bd7c28d031c5855dadb491d2723ba54f4c3015"},
|
||||
{file = "coverage-7.6.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66fe626fd7aa5982cdebad23e49e78ef7dbb3e3c2a5960a2b53632f1f703ea45"},
|
||||
{file = "coverage-7.6.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ef01d70198431719af0b1f5dcbefc557d44a190e749004042927b2a3fed0702"},
|
||||
{file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e92ae5a289a4bc4c0aae710c0948d3c7892e20fd3588224ebe242039573bf0"},
|
||||
{file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e695df2c58ce526eeab11a2e915448d3eb76f75dffe338ea613c1201b33bab2f"},
|
||||
{file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d74c08e9aaef995f8c4ef6d202dbd219c318450fe2a76da624f2ebb9c8ec5d9f"},
|
||||
{file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e995b3b76ccedc27fe4f477b349b7d64597e53a43fc2961db9d3fbace085d69d"},
|
||||
{file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b1f097878d74fe51e1ddd1be62d8e3682748875b461232cf4b52ddc6e6db0bba"},
|
||||
{file = "coverage-7.6.12-cp311-cp311-win32.whl", hash = "sha256:1f7ffa05da41754e20512202c866d0ebfc440bba3b0ed15133070e20bf5aeb5f"},
|
||||
{file = "coverage-7.6.12-cp311-cp311-win_amd64.whl", hash = "sha256:e216c5c45f89ef8971373fd1c5d8d1164b81f7f5f06bbf23c37e7908d19e8558"},
|
||||
{file = "coverage-7.6.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b172f8e030e8ef247b3104902cc671e20df80163b60a203653150d2fc204d1ad"},
|
||||
{file = "coverage-7.6.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:641dfe0ab73deb7069fb972d4d9725bf11c239c309ce694dd50b1473c0f641c3"},
|
||||
{file = "coverage-7.6.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e549f54ac5f301e8e04c569dfdb907f7be71b06b88b5063ce9d6953d2d58574"},
|
||||
{file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959244a17184515f8c52dcb65fb662808767c0bd233c1d8a166e7cf74c9ea985"},
|
||||
{file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bda1c5f347550c359f841d6614fb8ca42ae5cb0b74d39f8a1e204815ebe25750"},
|
||||
{file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ceeb90c3eda1f2d8c4c578c14167dbd8c674ecd7d38e45647543f19839dd6ea"},
|
||||
{file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f16f44025c06792e0fb09571ae454bcc7a3ec75eeb3c36b025eccf501b1a4c3"},
|
||||
{file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b076e625396e787448d27a411aefff867db2bffac8ed04e8f7056b07024eed5a"},
|
||||
{file = "coverage-7.6.12-cp312-cp312-win32.whl", hash = "sha256:00b2086892cf06c7c2d74983c9595dc511acca00665480b3ddff749ec4fb2a95"},
|
||||
{file = "coverage-7.6.12-cp312-cp312-win_amd64.whl", hash = "sha256:7ae6eabf519bc7871ce117fb18bf14e0e343eeb96c377667e3e5dd12095e0288"},
|
||||
{file = "coverage-7.6.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:488c27b3db0ebee97a830e6b5a3ea930c4a6e2c07f27a5e67e1b3532e76b9ef1"},
|
||||
{file = "coverage-7.6.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d1095bbee1851269f79fd8e0c9b5544e4c00c0c24965e66d8cba2eb5bb535fd"},
|
||||
{file = "coverage-7.6.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0533adc29adf6a69c1baa88c3d7dbcaadcffa21afbed3ca7a225a440e4744bf9"},
|
||||
{file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53c56358d470fa507a2b6e67a68fd002364d23c83741dbc4c2e0680d80ca227e"},
|
||||
{file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64cbb1a3027c79ca6310bf101014614f6e6e18c226474606cf725238cf5bc2d4"},
|
||||
{file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79cac3390bfa9836bb795be377395f28410811c9066bc4eefd8015258a7578c6"},
|
||||
{file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b148068e881faa26d878ff63e79650e208e95cf1c22bd3f77c3ca7b1d9821a3"},
|
||||
{file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8bec2ac5da793c2685ce5319ca9bcf4eee683b8a1679051f8e6ec04c4f2fd7dc"},
|
||||
{file = "coverage-7.6.12-cp313-cp313-win32.whl", hash = "sha256:200e10beb6ddd7c3ded322a4186313d5ca9e63e33d8fab4faa67ef46d3460af3"},
|
||||
{file = "coverage-7.6.12-cp313-cp313-win_amd64.whl", hash = "sha256:2b996819ced9f7dbb812c701485d58f261bef08f9b85304d41219b1496b591ef"},
|
||||
{file = "coverage-7.6.12-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:299cf973a7abff87a30609879c10df0b3bfc33d021e1adabc29138a48888841e"},
|
||||
{file = "coverage-7.6.12-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b467a8c56974bf06e543e69ad803c6865249d7a5ccf6980457ed2bc50312703"},
|
||||
{file = "coverage-7.6.12-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2458f275944db8129f95d91aee32c828a408481ecde3b30af31d552c2ce284a0"},
|
||||
{file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a9d8be07fb0832636a0f72b80d2a652fe665e80e720301fb22b191c3434d924"},
|
||||
{file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d47376a4f445e9743f6c83291e60adb1b127607a3618e3185bbc8091f0467b"},
|
||||
{file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b95574d06aa9d2bd6e5cc35a5bbe35696342c96760b69dc4287dbd5abd4ad51d"},
|
||||
{file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ecea0c38c9079570163d663c0433a9af4094a60aafdca491c6a3d248c7432827"},
|
||||
{file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2251fabcfee0a55a8578a9d29cecfee5f2de02f11530e7d5c5a05859aa85aee9"},
|
||||
{file = "coverage-7.6.12-cp313-cp313t-win32.whl", hash = "sha256:eb5507795caabd9b2ae3f1adc95f67b1104971c22c624bb354232d65c4fc90b3"},
|
||||
{file = "coverage-7.6.12-cp313-cp313t-win_amd64.whl", hash = "sha256:f60a297c3987c6c02ffb29effc70eadcbb412fe76947d394a1091a3615948e2f"},
|
||||
{file = "coverage-7.6.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e7575ab65ca8399c8c4f9a7d61bbd2d204c8b8e447aab9d355682205c9dd948d"},
|
||||
{file = "coverage-7.6.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8161d9fbc7e9fe2326de89cd0abb9f3599bccc1287db0aba285cb68d204ce929"},
|
||||
{file = "coverage-7.6.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a1e465f398c713f1b212400b4e79a09829cd42aebd360362cd89c5bdc44eb87"},
|
||||
{file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f25d8b92a4e31ff1bd873654ec367ae811b3a943583e05432ea29264782dc32c"},
|
||||
{file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a936309a65cc5ca80fa9f20a442ff9e2d06927ec9a4f54bcba9c14c066323f2"},
|
||||
{file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aa6f302a3a0b5f240ee201297fff0bbfe2fa0d415a94aeb257d8b461032389bd"},
|
||||
{file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f973643ef532d4f9be71dd88cf7588936685fdb576d93a79fe9f65bc337d9d73"},
|
||||
{file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:78f5243bb6b1060aed6213d5107744c19f9571ec76d54c99cc15938eb69e0e86"},
|
||||
{file = "coverage-7.6.12-cp39-cp39-win32.whl", hash = "sha256:69e62c5034291c845fc4df7f8155e8544178b6c774f97a99e2734b05eb5bed31"},
|
||||
{file = "coverage-7.6.12-cp39-cp39-win_amd64.whl", hash = "sha256:b01a840ecc25dce235ae4c1b6a0daefb2a203dba0e6e980637ee9c2f6ee0df57"},
|
||||
{file = "coverage-7.6.12-pp39.pp310-none-any.whl", hash = "sha256:7e39e845c4d764208e7b8f6a21c541ade741e2c41afabdfa1caa28687a3c98cf"},
|
||||
{file = "coverage-7.6.12-py3-none-any.whl", hash = "sha256:eb8668cfbc279a536c633137deeb9435d2962caec279c3f8cf8b91fff6ff8953"},
|
||||
{file = "coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2"},
|
||||
{file = "coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78"},
|
||||
{file = "coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c"},
|
||||
{file = "coverage-7.6.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3b204c11e2b2d883946fe1d97f89403aa1811df28ce0447439178cc7463448a"},
|
||||
{file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32ee6d8491fcfc82652a37109f69dee9a830e9379166cb73c16d8dc5c2915165"},
|
||||
{file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675cefc4c06e3b4c876b85bfb7c59c5e2218167bbd4da5075cbe3b5790a28988"},
|
||||
{file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f4f620668dbc6f5e909a0946a877310fb3d57aea8198bde792aae369ee1c23b5"},
|
||||
{file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4eea95ef275de7abaef630c9b2c002ffbc01918b726a39f5a4353916ec72d2f3"},
|
||||
{file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2f0280519e42b0a17550072861e0bc8a80a0870de260f9796157d3fca2733c5"},
|
||||
{file = "coverage-7.6.10-cp310-cp310-win32.whl", hash = "sha256:bc67deb76bc3717f22e765ab3e07ee9c7a5e26b9019ca19a3b063d9f4b874244"},
|
||||
{file = "coverage-7.6.10-cp310-cp310-win_amd64.whl", hash = "sha256:0f460286cb94036455e703c66988851d970fdfd8acc2a1122ab7f4f904e4029e"},
|
||||
{file = "coverage-7.6.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ea3c8f04b3e4af80e17bab607c386a830ffc2fb88a5484e1df756478cf70d1d3"},
|
||||
{file = "coverage-7.6.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:507a20fc863cae1d5720797761b42d2d87a04b3e5aeb682ef3b7332e90598f43"},
|
||||
{file = "coverage-7.6.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37a84878285b903c0fe21ac8794c6dab58150e9359f1aaebbeddd6412d53132"},
|
||||
{file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a534738b47b0de1995f85f582d983d94031dffb48ab86c95bdf88dc62212142f"},
|
||||
{file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d7a2bf79378d8fb8afaa994f91bfd8215134f8631d27eba3e0e2c13546ce994"},
|
||||
{file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6713ba4b4ebc330f3def51df1d5d38fad60b66720948112f114968feb52d3f99"},
|
||||
{file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab32947f481f7e8c763fa2c92fd9f44eeb143e7610c4ca9ecd6a36adab4081bd"},
|
||||
{file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7bbd8c8f1b115b892e34ba66a097b915d3871db7ce0e6b9901f462ff3a975377"},
|
||||
{file = "coverage-7.6.10-cp311-cp311-win32.whl", hash = "sha256:299e91b274c5c9cdb64cbdf1b3e4a8fe538a7a86acdd08fae52301b28ba297f8"},
|
||||
{file = "coverage-7.6.10-cp311-cp311-win_amd64.whl", hash = "sha256:489a01f94aa581dbd961f306e37d75d4ba16104bbfa2b0edb21d29b73be83609"},
|
||||
{file = "coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853"},
|
||||
{file = "coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078"},
|
||||
{file = "coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0"},
|
||||
{file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50"},
|
||||
{file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022"},
|
||||
{file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b"},
|
||||
{file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0"},
|
||||
{file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852"},
|
||||
{file = "coverage-7.6.10-cp312-cp312-win32.whl", hash = "sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359"},
|
||||
{file = "coverage-7.6.10-cp312-cp312-win_amd64.whl", hash = "sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247"},
|
||||
{file = "coverage-7.6.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05fca8ba6a87aabdd2d30d0b6c838b50510b56cdcfc604d40760dae7153b73d9"},
|
||||
{file = "coverage-7.6.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e80eba8801c386f72e0712a0453431259c45c3249f0009aff537a517b52942b"},
|
||||
{file = "coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690"},
|
||||
{file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec22b5e7fe7a0fa8509181c4aac1db48f3dd4d3a566131b313d1efc102892c18"},
|
||||
{file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26bcf5c4df41cad1b19c84af71c22cbc9ea9a547fc973f1f2cc9a290002c8b3c"},
|
||||
{file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd"},
|
||||
{file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e"},
|
||||
{file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694"},
|
||||
{file = "coverage-7.6.10-cp313-cp313-win32.whl", hash = "sha256:9e1747bab246d6ff2c4f28b4d186b205adced9f7bd9dc362051cc37c4a0c7bd6"},
|
||||
{file = "coverage-7.6.10-cp313-cp313-win_amd64.whl", hash = "sha256:254f1a3b1eef5f7ed23ef265eaa89c65c8c5b6b257327c149db1ca9d4a35f25e"},
|
||||
{file = "coverage-7.6.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ccf240eb719789cedbb9fd1338055de2761088202a9a0b73032857e53f612fe"},
|
||||
{file = "coverage-7.6.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c807ca74d5a5e64427c8805de15b9ca140bba13572d6d74e262f46f50b13273"},
|
||||
{file = "coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8"},
|
||||
{file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0de1e902669dccbf80b0415fb6b43d27edca2fbd48c74da378923b05316098"},
|
||||
{file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7b444c42bbc533aaae6b5a2166fd1a797cdb5eb58ee51a92bee1eb94a1e1cb"},
|
||||
{file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0"},
|
||||
{file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf"},
|
||||
{file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2"},
|
||||
{file = "coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312"},
|
||||
{file = "coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d"},
|
||||
{file = "coverage-7.6.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:656c82b8a0ead8bba147de9a89bda95064874c91a3ed43a00e687f23cc19d53a"},
|
||||
{file = "coverage-7.6.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccc2b70a7ed475c68ceb548bf69cec1e27305c1c2606a5eb7c3afff56a1b3b27"},
|
||||
{file = "coverage-7.6.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5e37dc41d57ceba70956fa2fc5b63c26dba863c946ace9705f8eca99daecdc4"},
|
||||
{file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0aa9692b4fdd83a4647eeb7db46410ea1322b5ed94cd1715ef09d1d5922ba87f"},
|
||||
{file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa744da1820678b475e4ba3dfd994c321c5b13381d1041fe9c608620e6676e25"},
|
||||
{file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0b1818063dc9e9d838c09e3a473c1422f517889436dd980f5d721899e66f315"},
|
||||
{file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:59af35558ba08b758aec4d56182b222976330ef8d2feacbb93964f576a7e7a90"},
|
||||
{file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7ed2f37cfce1ce101e6dffdfd1c99e729dd2ffc291d02d3e2d0af8b53d13840d"},
|
||||
{file = "coverage-7.6.10-cp39-cp39-win32.whl", hash = "sha256:4bcc276261505d82f0ad426870c3b12cb177752834a633e737ec5ee79bbdff18"},
|
||||
{file = "coverage-7.6.10-cp39-cp39-win_amd64.whl", hash = "sha256:457574f4599d2b00f7f637a0700a6422243b3565509457b2dbd3f50703e11f59"},
|
||||
{file = "coverage-7.6.10-pp39.pp310-none-any.whl", hash = "sha256:fd34e7b3405f0cc7ab03d54a334c17a9e802897580d964bd8c2001f4b9fd488f"},
|
||||
{file = "coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1150,55 +1149,51 @@ toml = ["tomli"]
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "44.0.1"
|
||||
version = "43.0.1"
|
||||
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||
optional = false
|
||||
python-versions = "!=3.9.0,!=3.9.1,>=3.7"
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "cryptography-44.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf688f615c29bfe9dfc44312ca470989279f0e94bb9f631f85e3459af8efc009"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd7c7e2d71d908dc0f8d2027e1604102140d84b155e658c20e8ad1304317691f"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:887143b9ff6bad2b7570da75a7fe8bbf5f65276365ac259a5d2d5147a73775f2"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:322eb03ecc62784536bc173f1483e76747aafeb69c8728df48537eb431cd1911"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:21377472ca4ada2906bc313168c9dc7b1d7ca417b63c1c3011d0c74b7de9ae69"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:df978682c1504fc93b3209de21aeabf2375cb1571d4e61907b3e7a2540e83026"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:eb3889330f2a4a148abead555399ec9a32b13b7c8ba969b72d8e500eb7ef84cd"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:8e6a85a93d0642bd774460a86513c5d9d80b5c002ca9693e63f6e540f1815ed0"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6f76fdd6fd048576a04c5210d53aa04ca34d2ed63336d4abd306d0cbe298fddf"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6c8acf6f3d1f47acb2248ec3ea261171a671f3d9428e34ad0357148d492c7864"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-win32.whl", hash = "sha256:24979e9f2040c953a94bf3c6782e67795a4c260734e5264dceea65c8f4bae64a"},
|
||||
{file = "cryptography-44.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:fd0ee90072861e276b0ff08bd627abec29e32a53b2be44e41dbcdf87cbee2b00"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a2d8a7045e1ab9b9f803f0d9531ead85f90c5f2859e653b61497228b18452008"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8272f257cf1cbd3f2e120f14c68bff2b6bdfcc157fafdee84a1b795efd72862"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e8d181e90a777b63f3f0caa836844a1182f1f265687fac2115fcf245f5fbec3"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:436df4f203482f41aad60ed1813811ac4ab102765ecae7a2bbb1dbb66dcff5a7"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4f422e8c6a28cf8b7f883eb790695d6d45b0c385a2583073f3cec434cc705e1a"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:72198e2b5925155497a5a3e8c216c7fb3e64c16ccee11f0e7da272fa93b35c4c"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2a46a89ad3e6176223b632056f321bc7de36b9f9b93b2cc1cccf935a3849dc62"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:53f23339864b617a3dfc2b0ac8d5c432625c80014c25caac9082314e9de56f41"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:888fcc3fce0c888785a4876ca55f9f43787f4c5c1cc1e2e0da71ad481ff82c5b"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:00918d859aa4e57db8299607086f793fa7813ae2ff5a4637e318a25ef82730f7"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-win32.whl", hash = "sha256:9b336599e2cb77b1008cb2ac264b290803ec5e8e89d618a5e978ff5eb6f715d9"},
|
||||
{file = "cryptography-44.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:e403f7f766ded778ecdb790da786b418a9f2394f36e8cc8b796cc056ab05f44f"},
|
||||
{file = "cryptography-44.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1f9a92144fa0c877117e9748c74501bea842f93d21ee00b0cf922846d9d0b183"},
|
||||
{file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:610a83540765a8d8ce0f351ce42e26e53e1f774a6efb71eb1b41eb01d01c3d12"},
|
||||
{file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5fed5cd6102bb4eb843e3315d2bf25fede494509bddadb81e03a859c1bc17b83"},
|
||||
{file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:f4daefc971c2d1f82f03097dc6f216744a6cd2ac0f04c68fb935ea2ba2a0d420"},
|
||||
{file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94f99f2b943b354a5b6307d7e8d19f5c423a794462bde2bf310c770ba052b1c4"},
|
||||
{file = "cryptography-44.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d9c5b9f698a83c8bd71e0f4d3f9f839ef244798e5ffe96febfa9714717db7af7"},
|
||||
{file = "cryptography-44.0.1.tar.gz", hash = "sha256:f51f5705ab27898afda1aaa430f34ad90dc117421057782022edf0600bec5f14"},
|
||||
{file = "cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d"},
|
||||
{file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062"},
|
||||
{file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962"},
|
||||
{file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277"},
|
||||
{file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a"},
|
||||
{file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042"},
|
||||
{file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494"},
|
||||
{file = "cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2"},
|
||||
{file = "cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d"},
|
||||
{file = "cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d"},
|
||||
{file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806"},
|
||||
{file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85"},
|
||||
{file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c"},
|
||||
{file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1"},
|
||||
{file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa"},
|
||||
{file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4"},
|
||||
{file = "cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47"},
|
||||
{file = "cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb"},
|
||||
{file = "cryptography-43.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034"},
|
||||
{file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d"},
|
||||
{file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289"},
|
||||
{file = "cryptography-43.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84"},
|
||||
{file = "cryptography-43.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365"},
|
||||
{file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96"},
|
||||
{file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172"},
|
||||
{file = "cryptography-43.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2"},
|
||||
{file = "cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""}
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"]
|
||||
docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"]
|
||||
nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"]
|
||||
pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"]
|
||||
sdist = ["build (>=1.0.0)"]
|
||||
docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"]
|
||||
docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"]
|
||||
nox = ["nox"]
|
||||
pep8test = ["check-sdist", "click", "mypy", "ruff"]
|
||||
sdist = ["build"]
|
||||
ssh = ["bcrypt (>=3.1.5)"]
|
||||
test = ["certifi (>=2024)", "cryptography-vectors (==44.0.1)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"]
|
||||
test = ["certifi", "cryptography-vectors (==43.0.1)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"]
|
||||
test-randomorder = ["pytest-randomly"]
|
||||
|
||||
[[package]]
|
||||
@@ -1470,13 +1465,13 @@ typing = ["typing-extensions (>=4.7.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "flake8"
|
||||
version = "7.1.2"
|
||||
version = "7.1.1"
|
||||
description = "the modular source code checker: pep8 pyflakes and co"
|
||||
optional = false
|
||||
python-versions = ">=3.8.1"
|
||||
files = [
|
||||
{file = "flake8-7.1.2-py2.py3-none-any.whl", hash = "sha256:1cbc62e65536f65e6d754dfe6f1bada7f5cf392d6f5db3c2b85892466c3e7c1a"},
|
||||
{file = "flake8-7.1.2.tar.gz", hash = "sha256:c586ffd0b41540951ae41af572e6790dbd49fc12b3aa2541685d253d9bd504bd"},
|
||||
{file = "flake8-7.1.1-py2.py3-none-any.whl", hash = "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213"},
|
||||
{file = "flake8-7.1.1.tar.gz", hash = "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1697,13 +1692,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"]
|
||||
|
||||
[[package]]
|
||||
name = "google-api-python-client"
|
||||
version = "2.162.0"
|
||||
version = "2.160.0"
|
||||
description = "Google API Client Library for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "google_api_python_client-2.162.0-py2.py3-none-any.whl", hash = "sha256:49365fa4f7795fe81a747f5544d6528ea94314fa59664e0ea1005f603facf1ec"},
|
||||
{file = "google_api_python_client-2.162.0.tar.gz", hash = "sha256:5f8bc934a5b6eea73a7d12d999e6585c1823179f48340234acb385e2502e735a"},
|
||||
{file = "google_api_python_client-2.160.0-py2.py3-none-any.whl", hash = "sha256:63d61fb3e4cf3fb31a70a87f45567c22f6dfe87bbfa27252317e3e2c42900db4"},
|
||||
{file = "google_api_python_client-2.160.0.tar.gz", hash = "sha256:a8ccafaecfa42d15d5b5c3134ced8de08380019717fc9fb1ed510ca58eca3b7e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2127,13 +2122,13 @@ referencing = ">=0.31.0"
|
||||
|
||||
[[package]]
|
||||
name = "kubernetes"
|
||||
version = "32.0.1"
|
||||
version = "32.0.0"
|
||||
description = "Kubernetes python client"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "kubernetes-32.0.1-py2.py3-none-any.whl", hash = "sha256:35282ab8493b938b08ab5526c7ce66588232df00ef5e1dbe88a419107dc10998"},
|
||||
{file = "kubernetes-32.0.1.tar.gz", hash = "sha256:42f43d49abd437ada79a79a16bd48a604d3471a117a8347e87db693f2ba0ba28"},
|
||||
{file = "kubernetes-32.0.0-py2.py3-none-any.whl", hash = "sha256:60fd8c29e8e43d9c553ca4811895a687426717deba9c0a66fb2dcc3f5ef96692"},
|
||||
{file = "kubernetes-32.0.0.tar.gz", hash = "sha256:319fa840345a482001ac5d6062222daeb66ec4d1bcb3087402aed685adf0aecb"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2541,13 +2536,13 @@ dev = ["click", "codecov", "mkdocs-gen-files", "mkdocs-git-authors-plugin", "mkd
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-material"
|
||||
version = "9.6.5"
|
||||
version = "9.6.3"
|
||||
description = "Documentation that simply works"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "mkdocs_material-9.6.5-py3-none-any.whl", hash = "sha256:aad3e6fb860c20870f75fb2a69ef901f1be727891e41adb60b753efcae19453b"},
|
||||
{file = "mkdocs_material-9.6.5.tar.gz", hash = "sha256:b714679a8c91b0ffe2188e11ed58c44d2523e9c2ae26a29cc652fa7478faa21f"},
|
||||
{file = "mkdocs_material-9.6.3-py3-none-any.whl", hash = "sha256:1125622067e26940806701219303b27c0933e04533560725d97ec26fd16a39cf"},
|
||||
{file = "mkdocs_material-9.6.3.tar.gz", hash = "sha256:c87f7d1c39ce6326da5e10e232aed51bae46252e646755900f4b0fc9192fa832"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -3459,19 +3454,19 @@ test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"]
|
||||
|
||||
[[package]]
|
||||
name = "py-ocsf-models"
|
||||
version = "0.3.1"
|
||||
version = "0.3.0"
|
||||
description = "This is a Python implementation of the OCSF models. The models are used to represent the data of the OCSF Schema defined in https://schema.ocsf.io/."
|
||||
optional = false
|
||||
python-versions = "<3.13,>3.9.1"
|
||||
python-versions = "<3.13,>=3.9"
|
||||
files = [
|
||||
{file = "py_ocsf_models-0.3.1-py3-none-any.whl", hash = "sha256:e722d567a7f3e5190fdd053c2e75a69cf33fab6f5c0a4b7de678768ba340ae3a"},
|
||||
{file = "py_ocsf_models-0.3.1.tar.gz", hash = "sha256:60defd2cc86e8882f42dc9c6dacca6dc16d6bc05f9477c2a3486a0d4b5882b94"},
|
||||
{file = "py_ocsf_models-0.3.0-py3-none-any.whl", hash = "sha256:3d31e379be5e4271f7faf62dee9c36798559a1f7f98dff142c0e4cfdb35e291c"},
|
||||
{file = "py_ocsf_models-0.3.0.tar.gz", hash = "sha256:ad46b7d9761b74010f06a894df2d9541989252b7ff738cd5c7edbf4283df2279"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cryptography = "44.0.1"
|
||||
cryptography = "43.0.1"
|
||||
email-validator = "2.2.0"
|
||||
pydantic = "1.10.21"
|
||||
pydantic = "1.10.18"
|
||||
|
||||
[[package]]
|
||||
name = "py-partiql-parser"
|
||||
@@ -3536,61 +3531,54 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "1.10.21"
|
||||
version = "1.10.18"
|
||||
description = "Data validation and settings management using python type hints"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pydantic-1.10.21-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:245e486e0fec53ec2366df9cf1cba36e0bbf066af7cd9c974bbbd9ba10e1e586"},
|
||||
{file = "pydantic-1.10.21-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6c54f8d4c151c1de784c5b93dfbb872067e3414619e10e21e695f7bb84d1d1fd"},
|
||||
{file = "pydantic-1.10.21-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b64708009cfabd9c2211295144ff455ec7ceb4c4fb45a07a804309598f36187"},
|
||||
{file = "pydantic-1.10.21-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a148410fa0e971ba333358d11a6dea7b48e063de127c2b09ece9d1c1137dde4"},
|
||||
{file = "pydantic-1.10.21-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:36ceadef055af06e7756eb4b871cdc9e5a27bdc06a45c820cd94b443de019bbf"},
|
||||
{file = "pydantic-1.10.21-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c0501e1d12df6ab1211b8cad52d2f7b2cd81f8e8e776d39aa5e71e2998d0379f"},
|
||||
{file = "pydantic-1.10.21-cp310-cp310-win_amd64.whl", hash = "sha256:c261127c275d7bce50b26b26c7d8427dcb5c4803e840e913f8d9df3f99dca55f"},
|
||||
{file = "pydantic-1.10.21-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8b6350b68566bb6b164fb06a3772e878887f3c857c46c0c534788081cb48adf4"},
|
||||
{file = "pydantic-1.10.21-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:935b19fdcde236f4fbf691959fa5c3e2b6951fff132964e869e57c70f2ad1ba3"},
|
||||
{file = "pydantic-1.10.21-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b6a04efdcd25486b27f24c1648d5adc1633ad8b4506d0e96e5367f075ed2e0b"},
|
||||
{file = "pydantic-1.10.21-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1ba253eb5af8d89864073e6ce8e6c8dec5f49920cff61f38f5c3383e38b1c9f"},
|
||||
{file = "pydantic-1.10.21-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:57f0101e6c97b411f287a0b7cf5ebc4e5d3b18254bf926f45a11615d29475793"},
|
||||
{file = "pydantic-1.10.21-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e85834f0370d737c77a386ce505c21b06bfe7086c1c568b70e15a568d9670d"},
|
||||
{file = "pydantic-1.10.21-cp311-cp311-win_amd64.whl", hash = "sha256:6a497bc66b3374b7d105763d1d3de76d949287bf28969bff4656206ab8a53aa9"},
|
||||
{file = "pydantic-1.10.21-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2ed4a5f13cf160d64aa331ab9017af81f3481cd9fd0e49f1d707b57fe1b9f3ae"},
|
||||
{file = "pydantic-1.10.21-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3b7693bb6ed3fbe250e222f9415abb73111bb09b73ab90d2d4d53f6390e0ccc1"},
|
||||
{file = "pydantic-1.10.21-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:185d5f1dff1fead51766da9b2de4f3dc3b8fca39e59383c273f34a6ae254e3e2"},
|
||||
{file = "pydantic-1.10.21-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38e6d35cf7cd1727822c79e324fa0677e1a08c88a34f56695101f5ad4d5e20e5"},
|
||||
{file = "pydantic-1.10.21-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:1d7c332685eafacb64a1a7645b409a166eb7537f23142d26895746f628a3149b"},
|
||||
{file = "pydantic-1.10.21-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c9b782db6f993a36092480eeaab8ba0609f786041b01f39c7c52252bda6d85f"},
|
||||
{file = "pydantic-1.10.21-cp312-cp312-win_amd64.whl", hash = "sha256:7ce64d23d4e71d9698492479505674c5c5b92cda02b07c91dfc13633b2eef805"},
|
||||
{file = "pydantic-1.10.21-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0067935d35044950be781933ab91b9a708eaff124bf860fa2f70aeb1c4be7212"},
|
||||
{file = "pydantic-1.10.21-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5e8148c2ce4894ce7e5a4925d9d3fdce429fb0e821b5a8783573f3611933a251"},
|
||||
{file = "pydantic-1.10.21-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4973232c98b9b44c78b1233693e5e1938add5af18042f031737e1214455f9b8"},
|
||||
{file = "pydantic-1.10.21-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:662bf5ce3c9b1cef32a32a2f4debe00d2f4839fefbebe1d6956e681122a9c839"},
|
||||
{file = "pydantic-1.10.21-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:98737c3ab5a2f8a85f2326eebcd214510f898881a290a7939a45ec294743c875"},
|
||||
{file = "pydantic-1.10.21-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0bb58bbe65a43483d49f66b6c8474424d551a3fbe8a7796c42da314bac712738"},
|
||||
{file = "pydantic-1.10.21-cp313-cp313-win_amd64.whl", hash = "sha256:e622314542fb48542c09c7bd1ac51d71c5632dd3c92dc82ede6da233f55f4848"},
|
||||
{file = "pydantic-1.10.21-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d356aa5b18ef5a24d8081f5c5beb67c0a2a6ff2a953ee38d65a2aa96526b274f"},
|
||||
{file = "pydantic-1.10.21-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08caa8c0468172d27c669abfe9e7d96a8b1655ec0833753e117061febaaadef5"},
|
||||
{file = "pydantic-1.10.21-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c677aa39ec737fec932feb68e4a2abe142682f2885558402602cd9746a1c92e8"},
|
||||
{file = "pydantic-1.10.21-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:79577cc045d3442c4e845df53df9f9202546e2ba54954c057d253fc17cd16cb1"},
|
||||
{file = "pydantic-1.10.21-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:b6b73ab347284719f818acb14f7cd80696c6fdf1bd34feee1955d7a72d2e64ce"},
|
||||
{file = "pydantic-1.10.21-cp37-cp37m-win_amd64.whl", hash = "sha256:46cffa24891b06269e12f7e1ec50b73f0c9ab4ce71c2caa4ccf1fb36845e1ff7"},
|
||||
{file = "pydantic-1.10.21-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:298d6f765e3c9825dfa78f24c1efd29af91c3ab1b763e1fd26ae4d9e1749e5c8"},
|
||||
{file = "pydantic-1.10.21-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f2f4a2305f15eff68f874766d982114ac89468f1c2c0b97640e719cf1a078374"},
|
||||
{file = "pydantic-1.10.21-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35b263b60c519354afb3a60107d20470dd5250b3ce54c08753f6975c406d949b"},
|
||||
{file = "pydantic-1.10.21-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e23a97a6c2f2db88995496db9387cd1727acdacc85835ba8619dce826c0b11a6"},
|
||||
{file = "pydantic-1.10.21-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:3c96fed246ccc1acb2df032ff642459e4ae18b315ecbab4d95c95cfa292e8517"},
|
||||
{file = "pydantic-1.10.21-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b92893ebefc0151474f682e7debb6ab38552ce56a90e39a8834734c81f37c8a9"},
|
||||
{file = "pydantic-1.10.21-cp38-cp38-win_amd64.whl", hash = "sha256:b8460bc256bf0de821839aea6794bb38a4c0fbd48f949ea51093f6edce0be459"},
|
||||
{file = "pydantic-1.10.21-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d387940f0f1a0adb3c44481aa379122d06df8486cc8f652a7b3b0caf08435f7"},
|
||||
{file = "pydantic-1.10.21-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:266ecfc384861d7b0b9c214788ddff75a2ea123aa756bcca6b2a1175edeca0fe"},
|
||||
{file = "pydantic-1.10.21-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61da798c05a06a362a2f8c5e3ff0341743e2818d0f530eaac0d6898f1b187f1f"},
|
||||
{file = "pydantic-1.10.21-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a621742da75ce272d64ea57bd7651ee2a115fa67c0f11d66d9dcfc18c2f1b106"},
|
||||
{file = "pydantic-1.10.21-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9e3e4000cd54ef455694b8be9111ea20f66a686fc155feda1ecacf2322b115da"},
|
||||
{file = "pydantic-1.10.21-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f198c8206640f4c0ef5a76b779241efb1380a300d88b1bce9bfe95a6362e674d"},
|
||||
{file = "pydantic-1.10.21-cp39-cp39-win_amd64.whl", hash = "sha256:e7f0cda108b36a30c8fc882e4fc5b7eec8ef584aa43aa43694c6a7b274fb2b56"},
|
||||
{file = "pydantic-1.10.21-py3-none-any.whl", hash = "sha256:db70c920cba9d05c69ad4a9e7f8e9e83011abb2c6490e561de9ae24aee44925c"},
|
||||
{file = "pydantic-1.10.21.tar.gz", hash = "sha256:64b48e2b609a6c22178a56c408ee1215a7206077ecb8a193e2fda31858b2362a"},
|
||||
{file = "pydantic-1.10.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e405ffcc1254d76bb0e760db101ee8916b620893e6edfbfee563b3c6f7a67c02"},
|
||||
{file = "pydantic-1.10.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e306e280ebebc65040034bff1a0a81fd86b2f4f05daac0131f29541cafd80b80"},
|
||||
{file = "pydantic-1.10.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11d9d9b87b50338b1b7de4ebf34fd29fdb0d219dc07ade29effc74d3d2609c62"},
|
||||
{file = "pydantic-1.10.18-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b661ce52c7b5e5f600c0c3c5839e71918346af2ef20062705ae76b5c16914cab"},
|
||||
{file = "pydantic-1.10.18-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c20f682defc9ef81cd7eaa485879ab29a86a0ba58acf669a78ed868e72bb89e0"},
|
||||
{file = "pydantic-1.10.18-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c5ae6b7c8483b1e0bf59e5f1843e4fd8fd405e11df7de217ee65b98eb5462861"},
|
||||
{file = "pydantic-1.10.18-cp310-cp310-win_amd64.whl", hash = "sha256:74fe19dda960b193b0eb82c1f4d2c8e5e26918d9cda858cbf3f41dd28549cb70"},
|
||||
{file = "pydantic-1.10.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:72fa46abace0a7743cc697dbb830a41ee84c9db8456e8d77a46d79b537efd7ec"},
|
||||
{file = "pydantic-1.10.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ef0fe7ad7cbdb5f372463d42e6ed4ca9c443a52ce544472d8842a0576d830da5"},
|
||||
{file = "pydantic-1.10.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a00e63104346145389b8e8f500bc6a241e729feaf0559b88b8aa513dd2065481"},
|
||||
{file = "pydantic-1.10.18-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae6fa2008e1443c46b7b3a5eb03800121868d5ab6bc7cda20b5df3e133cde8b3"},
|
||||
{file = "pydantic-1.10.18-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9f463abafdc92635da4b38807f5b9972276be7c8c5121989768549fceb8d2588"},
|
||||
{file = "pydantic-1.10.18-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3445426da503c7e40baccefb2b2989a0c5ce6b163679dd75f55493b460f05a8f"},
|
||||
{file = "pydantic-1.10.18-cp311-cp311-win_amd64.whl", hash = "sha256:467a14ee2183bc9c902579bb2f04c3d3dac00eff52e252850509a562255b2a33"},
|
||||
{file = "pydantic-1.10.18-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:efbc8a7f9cb5fe26122acba1852d8dcd1e125e723727c59dcd244da7bdaa54f2"},
|
||||
{file = "pydantic-1.10.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:24a4a159d0f7a8e26bf6463b0d3d60871d6a52eac5bb6a07a7df85c806f4c048"},
|
||||
{file = "pydantic-1.10.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b74be007703547dc52e3c37344d130a7bfacca7df112a9e5ceeb840a9ce195c7"},
|
||||
{file = "pydantic-1.10.18-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcb20d4cb355195c75000a49bb4a31d75e4295200df620f454bbc6bdf60ca890"},
|
||||
{file = "pydantic-1.10.18-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:46f379b8cb8a3585e3f61bf9ae7d606c70d133943f339d38b76e041ec234953f"},
|
||||
{file = "pydantic-1.10.18-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cbfbca662ed3729204090c4d09ee4beeecc1a7ecba5a159a94b5a4eb24e3759a"},
|
||||
{file = "pydantic-1.10.18-cp312-cp312-win_amd64.whl", hash = "sha256:c6d0a9f9eccaf7f438671a64acf654ef0d045466e63f9f68a579e2383b63f357"},
|
||||
{file = "pydantic-1.10.18-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3d5492dbf953d7d849751917e3b2433fb26010d977aa7a0765c37425a4026ff1"},
|
||||
{file = "pydantic-1.10.18-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe734914977eed33033b70bfc097e1baaffb589517863955430bf2e0846ac30f"},
|
||||
{file = "pydantic-1.10.18-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15fdbe568beaca9aacfccd5ceadfb5f1a235087a127e8af5e48df9d8a45ae85c"},
|
||||
{file = "pydantic-1.10.18-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c3e742f62198c9eb9201781fbebe64533a3bbf6a76a91b8d438d62b813079dbc"},
|
||||
{file = "pydantic-1.10.18-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:19a3bd00b9dafc2cd7250d94d5b578edf7a0bd7daf102617153ff9a8fa37871c"},
|
||||
{file = "pydantic-1.10.18-cp37-cp37m-win_amd64.whl", hash = "sha256:2ce3fcf75b2bae99aa31bd4968de0474ebe8c8258a0110903478bd83dfee4e3b"},
|
||||
{file = "pydantic-1.10.18-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:335a32d72c51a313b33fa3a9b0fe283503272ef6467910338e123f90925f0f03"},
|
||||
{file = "pydantic-1.10.18-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:34a3613c7edb8c6fa578e58e9abe3c0f5e7430e0fc34a65a415a1683b9c32d9a"},
|
||||
{file = "pydantic-1.10.18-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9ee4e6ca1d9616797fa2e9c0bfb8815912c7d67aca96f77428e316741082a1b"},
|
||||
{file = "pydantic-1.10.18-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23e8ec1ce4e57b4f441fc91e3c12adba023fedd06868445a5b5f1d48f0ab3682"},
|
||||
{file = "pydantic-1.10.18-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:44ae8a3e35a54d2e8fa88ed65e1b08967a9ef8c320819a969bfa09ce5528fafe"},
|
||||
{file = "pydantic-1.10.18-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5389eb3b48a72da28c6e061a247ab224381435256eb541e175798483368fdd3"},
|
||||
{file = "pydantic-1.10.18-cp38-cp38-win_amd64.whl", hash = "sha256:069b9c9fc645474d5ea3653788b544a9e0ccd3dca3ad8c900c4c6eac844b4620"},
|
||||
{file = "pydantic-1.10.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:80b982d42515632eb51f60fa1d217dfe0729f008e81a82d1544cc392e0a50ddf"},
|
||||
{file = "pydantic-1.10.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:aad8771ec8dbf9139b01b56f66386537c6fe4e76c8f7a47c10261b69ad25c2c9"},
|
||||
{file = "pydantic-1.10.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941a2eb0a1509bd7f31e355912eb33b698eb0051730b2eaf9e70e2e1589cae1d"},
|
||||
{file = "pydantic-1.10.18-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65f7361a09b07915a98efd17fdec23103307a54db2000bb92095457ca758d485"},
|
||||
{file = "pydantic-1.10.18-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6951f3f47cb5ca4da536ab161ac0163cab31417d20c54c6de5ddcab8bc813c3f"},
|
||||
{file = "pydantic-1.10.18-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7a4c5eec138a9b52c67f664c7d51d4c7234c5ad65dd8aacd919fb47445a62c86"},
|
||||
{file = "pydantic-1.10.18-cp39-cp39-win_amd64.whl", hash = "sha256:49e26c51ca854286bffc22b69787a8d4063a62bf7d83dc21d44d2ff426108518"},
|
||||
{file = "pydantic-1.10.18-py3-none-any.whl", hash = "sha256:06a189b81ffc52746ec9c8c007f16e5167c8b0a696e1a726369327e3db7b2a82"},
|
||||
{file = "pydantic-1.10.18.tar.gz", hash = "sha256:baebdff1907d1d96a139c25136a9bb7d17e118f133a76a2ef3b845e831e3403a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -3709,13 +3697,13 @@ diagrams = ["jinja2", "railroad-diagrams"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "8.3.5"
|
||||
version = "8.3.4"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"},
|
||||
{file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"},
|
||||
{file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"},
|
||||
{file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -4322,6 +4310,7 @@ files = [
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6"},
|
||||
@@ -4330,6 +4319,7 @@ files = [
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632"},
|
||||
@@ -4338,6 +4328,7 @@ files = [
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a"},
|
||||
@@ -4346,6 +4337,7 @@ files = [
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987"},
|
||||
@@ -4354,6 +4346,7 @@ files = [
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2c59aa6170b990d8d2719323e628aaf36f3bfbc1c26279c0eeeb24d05d2d11c7"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win32.whl", hash = "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b"},
|
||||
{file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"},
|
||||
@@ -4721,13 +4714,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "tzlocal"
|
||||
version = "5.3"
|
||||
version = "5.2"
|
||||
description = "tzinfo object for the local timezone"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "tzlocal-5.3-py3-none-any.whl", hash = "sha256:3814135a1bb29763c6e4f08fd6e41dbb435c7a60bfbb03270211bcc537187d8c"},
|
||||
{file = "tzlocal-5.3.tar.gz", hash = "sha256:2fafbfc07e9d8b49ade18f898d6bcd37ae88ce3ad6486842a2e4f03af68323d2"},
|
||||
{file = "tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8"},
|
||||
{file = "tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -5096,5 +5089,5 @@ type = ["pytest-mypy"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">3.9.1,<3.13"
|
||||
content-hash = "df864469828067bcf537b4b5d0b7ab4d711598561bca1cb0ebda59f2e5f6f832"
|
||||
python-versions = ">=3.9,<3.13"
|
||||
content-hash = "b479cce4f4992dab6f81b8118082daad2fd301cf01cbced02c90bc132dd27285"
|
||||
|
||||
+13
-13
@@ -305,20 +305,8 @@ def prowler():
|
||||
print(f"{Style.BRIGHT}{Fore.GREEN}\nNo findings to fix!{Style.RESET_ALL}\n")
|
||||
sys.exit()
|
||||
|
||||
# Outputs
|
||||
# TODO: this part is needed since the checks generates a Check_Report_XXX and the output uses Finding
|
||||
# This will be refactored for the outputs generate directly the Finding
|
||||
finding_outputs = []
|
||||
for finding in findings:
|
||||
try:
|
||||
finding_outputs.append(
|
||||
Finding.generate_output(global_provider, finding, output_options)
|
||||
)
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
# Extract findings stats
|
||||
stats = extract_findings_statistics(finding_outputs)
|
||||
stats = extract_findings_statistics(findings)
|
||||
|
||||
if args.slack:
|
||||
# TODO: this should be also in a config file
|
||||
@@ -341,6 +329,18 @@ def prowler():
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# Outputs
|
||||
# TODO: this part is needed since the checks generates a Check_Report_XXX and the output uses Finding
|
||||
# This will be refactored for the outputs generate directly the Finding
|
||||
finding_outputs = []
|
||||
for finding in findings:
|
||||
try:
|
||||
finding_outputs.append(
|
||||
Finding.generate_output(global_provider, finding, output_options)
|
||||
)
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
generated_outputs = {"regular": [], "compliance": []}
|
||||
|
||||
if args.output_formats:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2932,7 +2932,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "op.pl.2.r1.aws.warch.1",
|
||||
"Id": "op.pl.2.aws.warch.1",
|
||||
"Description": "Sistema de gestión",
|
||||
"Attributes": [
|
||||
{
|
||||
@@ -2956,7 +2956,7 @@
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "op.pl.2.r2.aws.warch.1",
|
||||
"Id": "op.pl.2.aws.warch.1",
|
||||
"Description": "Sistema de gestión de la seguridad con mejora continua",
|
||||
"Attributes": [
|
||||
{
|
||||
@@ -2980,7 +2980,7 @@
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "op.pl.2.r3.aws.warch.1",
|
||||
"Id": "op.pl.2.aws.warch.1",
|
||||
"Description": "Validación de datos",
|
||||
"Attributes": [
|
||||
{
|
||||
@@ -4304,6 +4304,32 @@
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "op.mon.3.aws.cwl.1",
|
||||
"Description": "Vigilancia",
|
||||
"Attributes": [
|
||||
{
|
||||
"IdGrupoControl": "op.mon.3",
|
||||
"Marco": "operacional",
|
||||
"Categoria": "monitorización del sistema",
|
||||
"DescripcionControl": "Deberá asegurarse que todos los servicios que se utilicen en la arquitectura de la aplicación desplegada en AWS estén generando logs",
|
||||
"Nivel": "alto",
|
||||
"Tipo": "requisito",
|
||||
"Dimensiones": [
|
||||
"confidencialidad",
|
||||
"integridad",
|
||||
"trazabilidad",
|
||||
"autenticidad",
|
||||
"disponibilidad"
|
||||
],
|
||||
"ModoEjecucion": "automatico",
|
||||
"Dependencias": []
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"cloudtrail_cloudwatch_logging_enabled"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "mp.com.2.aws.vpn.1",
|
||||
"Description": "Protección de la confidencialidad",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,34 @@
|
||||
"Provider": "AWS",
|
||||
"Description": "System and Organization Controls (SOC), defined by the American Institute of Certified Public Accountants (AICPA), is the name of a set of reports that's produced during an audit. It's intended for use by service organizations (organizations that provide information systems as a service to other organizations) to issue validated reports of internal controls over those information systems to the users of those services. The reports focus on controls grouped into five categories known as Trust Service Principles.",
|
||||
"Requirements": [
|
||||
{
|
||||
"Id": "cc_1_1",
|
||||
"Name": "CC1.1 COSO Principle 1: The entity demonstrates a commitment to integrity and ethical values",
|
||||
"Description": "Sets the Tone at the Top - The board of directors and management, at all levels, demonstrate through their directives, actions, and behavior the importance of integrity and ethical values to support the functioning of the system of internal control. Establishes Standards of Conduct - The expectations of the board of directors and senior management concerning integrity and ethical values are defined in the entity’s standards of conduct and understood at all levels of the entity and by outsourced service providers and business partners. Evaluates Adherence to Standards of Conduct - Processes are in place to evaluate the performance of individuals and teams against the entity’s expected standards of conduct. Addresses Deviations in a Timely Manner - Deviations from the entity’s expected standards of conduct are identified and remedied in a timely and consistent manner.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "cc_1_1",
|
||||
"Section": "CC1.0 - Common Criteria Related to Control Environment",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "cc_1_2",
|
||||
"Name": "CC1.2 COSO Principle 2: The board of directors demonstrates independence from management and exercises oversight of the development and performance of internal control",
|
||||
"Description": "Establishes Oversight Responsibilities - The board of directors identifies and accepts its oversight responsibilities in relation to established requirements and expectations. Applies Relevant Expertise - The board of directors defines, maintains, and periodically evaluates the skills and expertise needed among its members to enable them to ask probing questions of senior management and take commensurate action. Operates Independently - The board of directors has sufficient members who are independent from management and objective in evaluations and decision making. Additional point of focus specifically related to all engagements using the trust services criteria: Supplements Board Expertise - The board of directors supplements its expertise relevant to security, availability, processing integrity, confidentiality, and privacy, as needed, through the use of a subcommittee or consultants.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "cc_1_2",
|
||||
"Section": "CC1.0 - Common Criteria Related to Control Environment",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "cc_1_3",
|
||||
"Name": "CC1.3 COSO Principle 3: Management establishes, with board oversight, structures, reporting lines, and appropriate authorities and responsibilities in the pursuit of objectives",
|
||||
@@ -12,7 +40,7 @@
|
||||
{
|
||||
"ItemId": "cc_1_3",
|
||||
"Section": "CC1.0 - Common Criteria Related to Control Environment",
|
||||
"Service": "iam",
|
||||
"Service": "aws",
|
||||
"Type": "automated"
|
||||
}
|
||||
],
|
||||
@@ -25,6 +53,34 @@
|
||||
"iam_user_console_access_unused"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "cc_1_4",
|
||||
"Name": "CC1.4 COSO Principle 4: The entity demonstrates a commitment to attract, develop, and retain competent individuals in alignment with objectives",
|
||||
"Description": "Establishes Policies and Practices - Policies and practices reflect expectations of competence necessary to support the achievement of objectives. Evaluates Competence and Addresses Shortcomings - The board of directors and management evaluate competence across the entity and in outsourced service providers in relation to established policies and practices and act as necessary to address shortcomings. Attracts, Develops, and Retains Individuals - The entity provides the mentoring and training needed to attract, develop, and retain sufficient and competent personnel and outsourced service providers to support the achievement of objectives. Plans and Prepares for Succession - Senior management and the board of directors develop contingency plans for assignments of responsibility important for internal control. Additional point of focus specifically related to all engagements using the trust services criteria: Considers the Background of Individuals - The entity considers the background of potential and existing personnel, contractors, and vendor employees when determining whether to employ and retain the individuals. Considers the Technical Competency of Individuals - The entity considers the technical competency of potential and existing personnel, contractors, and vendor employees when determining whether to employ and retain the individuals. Provides Training to Maintain Technical Competencies - The entity provides training programs, including continuing education and training, to ensure skill sets and technical competency of existing personnel, contractors, and vendor employees are developed and maintained.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "cc_1_4",
|
||||
"Section": "CC1.0 - Common Criteria Related to Control Environment",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "cc_1_5",
|
||||
"Name": "CC1.5 COSO Principle 5: The entity holds individuals accountable for their internal control responsibilities in the pursuit of objectives",
|
||||
"Description": "Enforces Accountability Through Structures, Authorities, and Responsibilities - Management and the board of directors establish the mechanisms to communicate and hold individuals accountable for performance of internal control responsibilities across the entity and implement corrective action as necessary. Establishes Performance Measures, Incentives, and Rewards - Management and the board of directors establish performance measures, incentives, and other rewards appropriate for responsibilities at all levels of the entity, reflecting appropriate dimensions of performance and expected standards of conduct, and considering the achievement of both short-term and longer-term objectives. Evaluates Performance Measures, Incentives, and Rewards for Ongoing Relevance - Management and the board of directors align incentives and rewards with the fulfillment of internal control responsibilities in the achievement of objectives. Considers Excessive Pressures - Management and the board of directors evaluate and adjust pressures associated with the achievement of objectives as they assign responsibilities, develop performance measures, and evaluate performance. Evaluates Performance and Rewards or Disciplines Individuals - Management and the board of directors evaluate performance of internal control responsibilities, including adherence to standards of conduct and expected levels of competence, and provide rewards or exercise disciplinary action, as appropriate.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "cc_1_5",
|
||||
"Section": "CC1.0 - Common Criteria Related to Control Environment",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "cc_2_1",
|
||||
"Name": "CC2.1 COSO Principle 13: The entity obtains or generates and uses relevant, quality information to support the functioning of internal control",
|
||||
@@ -44,6 +100,34 @@
|
||||
"config_recorder_all_regions_enabled"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "cc_2_2",
|
||||
"Name": "CC2.2 COSO Principle 14: The entity internally communicates information, including objectives and responsibilities for internal control, necessary to support the functioning of internal control",
|
||||
"Description": "Communicates Internal Control Information - A process is in place to communicate required information to enable all personnel to understand and carry out their internal control responsibilities. Communicates With the Board of Directors - Communication exists between management and the board of directors so that both have information needed to fulfill their roles with respect to the entity’s objectives. Provides Separate Communication Lines - Separate communication channels, such as whistle-blower hotlines, are in place and serve as fail-safe mechanisms to enable anonymous or confidential communication when normal channels are inoperative or ineffective. Selects Relevant Method of Communication - The method of communication considers the timing, audience, and nature of the information. Additional point of focus specifically related to all engagements using the trust services criteria: Communicates Responsibilities - Entity personnel with responsibility for designing, developing, implementing,operating, maintaining, or monitoring system controls receive communications about their responsibilities, including changes in their responsibilities, and have the information necessary to carry out those responsibilities. Communicates Information on Reporting Failures, Incidents, Concerns, and Other Matters—Entity personnel are provided with information on how to report systems failures, incidents, concerns, and other complaints to personnel. Communicates Objectives and Changes to Objectives - The entity communicates its objectives and changes to those objectives to personnel in a timely manner. Communicates Information to Improve Security Knowledge and Awareness - The entity communicates information to improve security knowledge and awareness and to model appropriate security behaviors to personnel through a security awareness training program. Additional points of focus that apply only when an engagement using the trust services criteria is performed at the system level: Communicates Information About System Operation and Boundaries - The entity prepares and communicates information about the design and operation of the system and its boundaries to authorized personnel to enable them to understand their role in the system and the results of system operation. Communicates System Objectives - The entity communicates its objectives to personnel to enable them to carry out their responsibilities. Communicates System Changes - System changes that affect responsibilities or the achievement of the entity's objectives are communicated in a timely manner.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "cc_2_2",
|
||||
"Section": "CC2.0 - Common Criteria Related to Communication and Information",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "cc_2_3",
|
||||
"Name": "CC2.3 COSO Principle 15: The entity communicates with external parties regarding matters affecting the functioning of internal control",
|
||||
"Description": "Communicates to External Parties - Processes are in place to communicate relevant and timely information to external parties, including shareholders, partners, owners, regulators, customers, financial analysts, and other external parties. Enables Inbound Communications - Open communication channels allow input from customers, consumers, suppliers, external auditors, regulators, financial analysts, and others, providing management and the board of directors with relevant information. Communicates With the Board of Directors - Relevant information resulting from assessments conducted by external parties is communicated to the board of directors. Provides Separate Communication Lines - Separate communication channels, such as whistle-blower hotlines, are in place and serve as fail-safe mechanisms to enable anonymous or confidential communication when normal channels are inoperative or ineffective. Selects Relevant Method of Communication - The method of communication considers the timing, audience, and nature of the communication and legal, regulatory, and fiduciary requirements and expectations. Communicates Objectives Related to Confidentiality and Changes to Objectives - The entity communicates, to external users, vendors, business partners and others whose products and services are part of the system, objectives and changes to objectives related to confidentiality. Additional point of focus that applies only to an engagement using the trust services criteria for privacy: Communicates Objectives Related to Privacy and Changes to Objectives - The entity communicates, to external users, vendors, business partners and others whose products and services are part of the system, objectives related to privacy and changes to those objectives. Additional points of focus that apply only when an engagement using the trust services criteria is performed at the system level: Communicates Information About System Operation and Boundaries - The entity prepares and communicates information about the design and operation of the system and its boundaries to authorized external users to permit users to understand their role in the system and the results of system operation. Communicates System Objectives - The entity communicates its system objectives to appropriate external users. Communicates System Responsibilities - External users with responsibility for designing, developing, implementing, operating, maintaining, and monitoring system controls receive communications about their responsibilities and have the information necessary to carry out those responsibilities. Communicates Information on Reporting System Failures, Incidents, Concerns, and Other Matters - External users are provided with information on how to report systems failures, incidents, concerns, and other complaints to appropriate personnel.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "cc_2_3",
|
||||
"Section": "CC2.0 - Common Criteria Related to Communication and Information",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "cc_3_1",
|
||||
"Name": "CC3.1 COSO Principle 6: The entity specifies objectives with sufficient clarity to enable the identification and assessment of risks relating to objectives",
|
||||
@@ -78,7 +162,8 @@
|
||||
"ec2_instance_managed_by_ssm",
|
||||
"ssm_managed_compliant_patching",
|
||||
"guardduty_no_high_severity_findings",
|
||||
"guardduty_is_enabled"
|
||||
"guardduty_is_enabled",
|
||||
"ssm_managed_compliant_patching"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -90,16 +175,10 @@
|
||||
"ItemId": "cc_3_3",
|
||||
"Section": "CC3.0 - Common Criteria Related to Risk Assessment",
|
||||
"Service": "aws",
|
||||
"Type": "automated"
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"iam_inline_policy_allows_privilege_escalation",
|
||||
"iam_policy_no_full_access_to_cloudtrail",
|
||||
"cloudtrail_threat_detection_privilege_escalation",
|
||||
"cloudtrail_threat_detection_enumeration",
|
||||
"cloudtrail_threat_detection_llm_jacking"
|
||||
]
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "cc_3_4",
|
||||
@@ -117,6 +196,20 @@
|
||||
"config_recorder_all_regions_enabled"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "cc_4_1",
|
||||
"Name": "CC4.1 COSO Principle 16: The entity selects, develops, and performs ongoing and/or separate evaluations to ascertain whether the components of internal control are present and functioning",
|
||||
"Description": "Considers a Mix of Ongoing and Separate Evaluations - Management includes a balance of ongoing and separate evaluations. Considers Rate of Change - Management considers the rate of change in business and business processes when selecting and developing ongoing and separate evaluations. Establishes Baseline Understanding - The design and current state of an internal control system are used to establish a baseline for ongoing and separate evaluations. Uses Knowledgeable Personnel - Evaluators performing ongoing and separate evaluations have sufficient knowledge to understand what is being evaluated. Integrates With Business Processes - Ongoing evaluations are built into the business processes and adjust to changing conditions. Adjusts Scope and Frequency—Management varies the scope and frequency of separate evaluations depending on risk. Objectively Evaluates - Separate evaluations are performed periodically to provide objective feedback. Considers Different Types of Ongoing and Separate Evaluations - Management uses a variety of different types of ongoing and separate evaluations, including penetration testing, independent certification made against established specifications (for example, ISO certifications), and internal audit assessments.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "cc_4_1",
|
||||
"Section": "CC4.0 - Monitoring Activities",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "cc_4_2",
|
||||
"Name": "CC4.2 COSO Principle 17: The entity evaluates and communicates internal control deficiencies in a timely manner to those parties responsible for taking corrective action, including senior management and the board of directors, as appropriate",
|
||||
@@ -134,6 +227,20 @@
|
||||
"guardduty_no_high_severity_findings"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "cc_5_1",
|
||||
"Name": "CC5.1 COSO Principle 10: The entity selects and develops control activities that contribute to the mitigation of risks to the achievement of objectives to acceptable levels",
|
||||
"Description": "Integrates With Risk Assessment - Control activities help ensure that risk responses that address and mitigate risks are carried out. Considers Entity-Specific Factors - Management considers how the environment, complexity, nature, and scope of its operations, as well as the specific characteristics of its organization, affect the selection and development of control activities. Determines Relevant Business Processes - Management determines which relevant business processes require control activities. Evaluates a Mix of 2017 Data Submitted Types - Control activities include a range and variety of controls and may include a balance of approaches to mitigate risks, considering both manual and automated controls, and preventive and detective controls. Considers at What Level Activities Are Applied - Management considers control activities at various levels in the entity. Addresses Segregation of Duties - Management segregates incompatible duties, and where such segregation is not practical, management selects and develops alternative control activities.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "cc_5_1",
|
||||
"Section": "CC5.0 - Control Activities",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "cc_5_2",
|
||||
"Name": "CC5.2 COSO Principle 11: The entity also selects and develops general control activities over technology to support the achievement of objectives",
|
||||
@@ -142,22 +249,25 @@
|
||||
{
|
||||
"ItemId": "cc_5_2",
|
||||
"Section": "CC5.0 - Control Activities",
|
||||
"Service": "cloudwatch",
|
||||
"Type": "automated"
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"cloudwatch_changes_to_network_acls_alarm_configured",
|
||||
"cloudwatch_changes_to_network_gateways_alarm_configured",
|
||||
"cloudwatch_changes_to_network_route_tables_alarm_configured",
|
||||
"cloudwatch_changes_to_vpcs_alarm_configured",
|
||||
"cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled",
|
||||
"cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled",
|
||||
"cloudwatch_log_metric_filter_aws_organizations_changes",
|
||||
"cloudwatch_log_metric_filter_for_s3_bucket_policy_changes",
|
||||
"cloudwatch_log_metric_filter_policy_changes",
|
||||
"cloudwatch_log_metric_filter_security_group_changes"
|
||||
]
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "cc_5_3",
|
||||
"Name": "CCC5.3 COSO Principle 12: The entity deploys control activities through policies that establish what is expected and in procedures that put policies into action",
|
||||
"Description": "Establishes Policies and Procedures to Support Deployment of Management ‘s Directives - Management establishes control activities that are built into business processes and employees’ day-to-day activities through policies establishing what is expected and relevant procedures specifying actions. Establishes Responsibility and Accountability for Executing Policies and Procedures - Management establishes responsibility and accountability for control activities with management (or other designated personnel) of the business unit or function in which the relevant risks reside. Performs in a Timely Manner - Responsible personnel perform control activities in a timely manner as defined by the policies and procedures. Takes Corrective Action - Responsible personnel investigate and act on matters identified as a result of executing control activities. Performs Using Competent Personnel - Competent personnel with sufficient authority perform control activities with diligence and continuing focus. Reassesses Policies and Procedures - Management periodically reviews control activities to determine their continued relevance and refreshes them when necessary.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "cc_5_3",
|
||||
"Section": "CC5.0 - Control Activities",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "cc_6_1",
|
||||
@@ -209,6 +319,34 @@
|
||||
"iam_inline_policy_no_administrative_privileges"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "cc_6_4",
|
||||
"Name": "CC6.4 The entity restricts physical access to facilities and protected information assets to authorized personnel to meet the entity’s objectives",
|
||||
"Description": "Creates or Modifies Physical Access - Processes are in place to create or modify physical access to facilities such as data centers, office spaces, and work areas, based on authorization from the system's asset owner. Removes Physical Access - Processes are in place to remove access to physical resources when an individual no longer requires access. Reviews Physical Access - Processes are in place to periodically review physical access to ensure consistency with job responsibilities.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "cc_6_4",
|
||||
"Section": "CC6.0 - Logical and Physical Access",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "cc_6_5",
|
||||
"Name": "CC6.5 The entity discontinues logical and physical protections over physical assets only after the ability to read or recover data and software from those assets has been diminished and is no longer required to meet the entity’s objectives",
|
||||
"Description": "Identifies Data and Software for Disposal - Procedures are in place to identify data and software stored on equipment to be disposed and to render such data and software unreadable. Removes Data and Software From Entity Control - Procedures are in place to remove data and software stored on equipment to be removed from the physical control of the entity and to render such data and software unreadable.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "cc_6_5",
|
||||
"Section": "CC6.0 - Logical and Physical Access",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "cc_6_6",
|
||||
"Name": "CC6.6 The entity implements logical access security measures to protect against threats from sources outside its system boundaries",
|
||||
@@ -222,51 +360,7 @@
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"ec2_instance_public_ip",
|
||||
"ec2_ami_public",
|
||||
"ec2_ebs_public_snapshot",
|
||||
"ec2_ebs_snapshot_account_block_public_access",
|
||||
"ec2_launch_template_no_public_ip",
|
||||
"ec2_securitygroup_allow_wide_open_public_ipv4",
|
||||
"vpc_subnet_no_public_ip_by_default",
|
||||
"vpc_subnet_separate_private_public",
|
||||
"ec2_instance_port_cassandra_exposed_to_internet",
|
||||
"ec2_instance_port_cifs_exposed_to_internet",
|
||||
"ec2_instance_port_elasticsearch_kibana_exposed_to_internet",
|
||||
"ec2_instance_port_ftp_exposed_to_internet",
|
||||
"ec2_instance_port_kafka_exposed_to_internet",
|
||||
"ec2_instance_port_kerberos_exposed_to_internet",
|
||||
"ec2_instance_port_ldap_exposed_to_internet",
|
||||
"ec2_instance_port_memcached_exposed_to_internet",
|
||||
"ec2_instance_port_mongodb_exposed_to_internet",
|
||||
"ec2_instance_port_mysql_exposed_to_internet",
|
||||
"ec2_instance_port_oracle_exposed_to_internet",
|
||||
"ec2_instance_port_postgresql_exposed_to_internet",
|
||||
"ec2_instance_port_rdp_exposed_to_internet",
|
||||
"ec2_instance_port_redis_exposed_to_internet",
|
||||
"ec2_instance_port_sqlserver_exposed_to_internet",
|
||||
"ec2_instance_port_ssh_exposed_to_internet",
|
||||
"ec2_instance_port_telnet_exposed_to_internet",
|
||||
"ec2_networkacl_allow_ingress_any_port",
|
||||
"ec2_networkacl_allow_ingress_tcp_port_22",
|
||||
"ec2_networkacl_allow_ingress_tcp_port_3389",
|
||||
"ec2_securitygroup_allow_ingress_from_internet_to_all_ports",
|
||||
"ec2_securitygroup_allow_ingress_from_internet_to_any_port",
|
||||
"ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports",
|
||||
"ec2_securitygroup_allow_ingress_from_internet_to_port_mongodb_27017_27018",
|
||||
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_ftp_port_20_21",
|
||||
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22",
|
||||
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_3389",
|
||||
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_cassandra_7199_9160_8888",
|
||||
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_elasticsearch_kibana_9200_9300_5601",
|
||||
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_kafka_9092",
|
||||
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_memcached_11211",
|
||||
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_mysql_3306",
|
||||
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_oracle_1521_2483",
|
||||
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_postgres_5432",
|
||||
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_redis_6379",
|
||||
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_sql_server_1433_1434",
|
||||
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_telnet_23"
|
||||
"ec2_instance_public_ip"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -282,15 +376,7 @@
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"acm_certificates_expiration_check",
|
||||
"dms_endpoint_redis_in_transit_encryption_enabled",
|
||||
"dynamodb_accelerator_cluster_in_transit_encryption_enabled",
|
||||
"ec2_transitgateway_auto_accept_vpc_attachments",
|
||||
"elasticache_redis_cluster_in_transit_encryption_enabled",
|
||||
"kafka_cluster_in_transit_encryption_enabled",
|
||||
"kafka_connector_in_transit_encryption_enabled",
|
||||
"redshift_cluster_in_transit_encryption_enabled",
|
||||
"transfer_server_in_transit_encryption_enabled"
|
||||
"acm_certificates_expiration_check"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -356,6 +442,7 @@
|
||||
"cloudtrail_multi_region_enabled",
|
||||
"securityhub_enabled",
|
||||
"cloudwatch_log_group_retention_policy_specific_days_enabled",
|
||||
"cloudtrail_multi_region_enabled",
|
||||
"redshift_cluster_audit_logging",
|
||||
"vpc_flow_logs_enabled",
|
||||
"ec2_instance_imdsv2_enabled",
|
||||
@@ -414,10 +501,14 @@
|
||||
"cloudwatch_changes_to_network_route_tables_alarm_configured",
|
||||
"cloudwatch_changes_to_vpcs_alarm_configured",
|
||||
"dynamodb_tables_pitr_enabled",
|
||||
"dynamodb_tables_pitr_enabled",
|
||||
"efs_have_backup_enabled",
|
||||
"efs_have_backup_enabled",
|
||||
"guardduty_is_enabled",
|
||||
"guardduty_no_high_severity_findings",
|
||||
"rds_instance_backup_enabled",
|
||||
"rds_instance_backup_enabled",
|
||||
"rds_instance_backup_enabled",
|
||||
"redshift_cluster_automated_snapshot",
|
||||
"s3_bucket_object_versioning",
|
||||
"securityhub_enabled"
|
||||
@@ -432,17 +523,10 @@
|
||||
"ItemId": "cc_7_5",
|
||||
"Section": "CC7.0 - System Operations",
|
||||
"Service": "aws",
|
||||
"Type": "automated"
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"ec2_ebs_snapshots_encrypted",
|
||||
"ec2_ebs_volume_snapshots_exists",
|
||||
"neptune_cluster_copy_tags_to_snapshots",
|
||||
"neptune_cluster_snapshot_encrypted",
|
||||
"rds_instance_copy_tags_to_snapshots",
|
||||
"redshift_cluster_automated_snapshot"
|
||||
]
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "cc_8_1",
|
||||
@@ -452,7 +536,7 @@
|
||||
{
|
||||
"ItemId": "cc_8_1",
|
||||
"Section": "CC8.0 - Change Management",
|
||||
"Service": "config",
|
||||
"Service": "aws",
|
||||
"Type": "automated"
|
||||
}
|
||||
],
|
||||
@@ -460,59 +544,93 @@
|
||||
"config_recorder_all_regions_enabled"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "cc_9_1",
|
||||
"Name": "CC9.1 The entity identifies, selects, and develops risk mitigation activities for risks arising from potential business disruptions",
|
||||
"Description": "Considers Mitigation of Risks of Business Disruption - Risk mitigation activities include the development of planned policies, procedures, communications, and alternative processing solutions to respond to, mitigate, and recover from security events that disrupt business operations. Those policies and procedures include monitoring processes and information and communications to meet the entity's objectives during response, mitigation, and recovery efforts. Considers the Use of Insurance to Mitigate Financial Impact Risks - The risk management activities consider the use of insurance to offset the financial impact of loss events that would otherwise impair the ability of the entity to meet its objectives.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "cc_9_1",
|
||||
"Section": "CC9.0 - Risk Mitigation",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "cc_9_2",
|
||||
"Name": "CC9.2 The entity assesses and manages risks associated with vendors and business partners",
|
||||
"Description": "Establishes Requirements for Vendor and Business Partner Engagements - The entity establishes specific requirements for a vendor and business partner engagement that includes (1) scope of services and product specifications, (2) roles and responsibilities, (3) compliance requirements, and (4) service levels. Assesses Vendor and Business Partner Risks - The entity assesses, on a periodic basis, the risks that vendors and business partners (and those entities’ vendors and business partners) represent to the achievement of the entity's objectives. Assigns Responsibility and Accountability for Managing Vendors and Business Partners - The entity assigns responsibility and accountability for the management of risks associated with vendors and business partners. Establishes Communication Protocols for Vendors and Business Partners - The entity establishes communication and resolution protocols for service or product issues related to vendors and business partners. Establishes Exception Handling Procedures From Vendors and Business Partners - The entity establishes exception handling procedures for service or product issues related to vendors and business partners. Assesses Vendor and Business Partner Performance - The entity periodically assesses the performance of vendors and business partners. Implements Procedures for Addressing Issues Identified During Vendor and Business Partner Assessments - The entity implements procedures for addressing issues identified with vendor and business partner relationships. Implements Procedures for Terminating Vendor and Business Partner Relationships - The entity implements procedures for terminating vendor and business partner relationships. Obtains Confidentiality Commitments from Vendors and Business Partners - The entity obtains confidentiality commitments that are consistent with the entity’s confidentiality commitments and requirements from vendors and business partners who have access to confidential information. Assesses Compliance With Confidentiality Commitments of Vendors and Business Partners - On a periodic and as-needed basis, the entity assesses compliance by vendors and business partners with the entity’s confidentiality commitments and requirements. Obtains Privacy Commitments from Vendors and Business Partners - The entity obtains privacy commitments, consistent with the entity’s privacy commitments and requirements, from vendors and business partners who have access to personal information. Assesses Compliance with Privacy Commitments of Vendors and Business Partners - On a periodic and as-needed basis, the entity assesses compliance by vendors and business partners with the entity’s privacy commitments and requirements and takes corrective action as necessary.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "cc_9_2",
|
||||
"Section": "CC9.0 - Risk Mitigation",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "cc_a_1_1",
|
||||
"Name": "A1.2 The entity authorizes, designs, develops or acquires, implements, operates, approves, maintains, and monitors environmental protections, software, data back-up processes, and recovery infrastructure to meet its objectives",
|
||||
"Name": "A1.1 The entity maintains, monitors, and evaluates current processing capacity and use of system components (infrastructure, data, and software) to manage capacity demand and to enable the implementation of additional capacity to help meet its objectives",
|
||||
"Description": "Measures Current Usage - The use of the system components is measured to establish a baseline for capacity management and to use when evaluating the risk of impaired availability due to capacity constraints. Forecasts Capacity - The expected average and peak use of system components is forecasted and compared to system capacity and associated tolerances. Forecasting considers capacity in the event of the failure of system components that constrain capacity. Makes Changes Based on Forecasts - The system change management process is initiated when forecasted usage exceeds capacity tolerances.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "cc_a_1_1",
|
||||
"Section": "CCA1.0 - Additional Criterial for Availability",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "cc_a_1_2",
|
||||
"Name": "A1.2 The entity authorizes, designs, develops or acquires, implements, operates, approves, maintains, and monitors environmental protections, software, data back-up processes, and recovery infrastructure to meet its objectives",
|
||||
"Description": "Measures Current Usage - The use of the system components is measured to establish a baseline for capacity management and to use when evaluating the risk of impaired availability due to capacity constraints. Forecasts Capacity - The expected average and peak use of system components is forecasted and compared to system capacity and associated tolerances. Forecasting considers capacity in the event of the failure of system components that constrain capacity. Makes Changes Based on Forecasts - The system change management process is initiated when forecasted usage exceeds capacity tolerances.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "cc_a_1_2",
|
||||
"Section": "CCA1.0 - Additional Criterial for Availability",
|
||||
"Service": "aws",
|
||||
"Type": "automated"
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"apigateway_restapi_logging_enabled",
|
||||
"cloudtrail_multi_region_enabled",
|
||||
"cloudtrail_multi_region_enabled",
|
||||
"cloudtrail_cloudwatch_logging_enabled",
|
||||
"dynamodb_tables_pitr_enabled",
|
||||
"dynamodb_tables_pitr_enabled",
|
||||
"efs_have_backup_enabled",
|
||||
"efs_have_backup_enabled",
|
||||
"elbv2_logging_enabled",
|
||||
"elb_logging_enabled",
|
||||
"rds_instance_backup_enabled",
|
||||
"rds_instance_backup_enabled",
|
||||
"rds_instance_integration_cloudwatch_logs",
|
||||
"rds_instance_backup_enabled",
|
||||
"redshift_cluster_automated_snapshot",
|
||||
"s3_bucket_object_versioning",
|
||||
"apigatewayv2_api_access_logging_enabled",
|
||||
"appsync_field_level_logging_enabled",
|
||||
"athena_workgroup_logging_enabled",
|
||||
"awslambda_function_invoke_api_operations_cloudtrail_logging_enabled",
|
||||
"bedrock_model_invocation_logging_enabled",
|
||||
"cloudfront_distributions_logging_enabled",
|
||||
"cloudtrail_logs_s3_bucket_access_logging_enabled",
|
||||
"codebuild_project_logging_enabled",
|
||||
"datasync_task_logging_enabled",
|
||||
"dms_replication_task_source_logging_enabled",
|
||||
"dms_replication_task_target_logging_enabled",
|
||||
"ec2_client_vpn_endpoint_connection_logging_enabled",
|
||||
"ecs_task_definitions_logging_enabled",
|
||||
"elasticbeanstalk_environment_cloudwatch_logging_enabled",
|
||||
"elb_logging_enabled",
|
||||
"elbv2_logging_enabled",
|
||||
"glue_etl_jobs_logging_enabled",
|
||||
"mq_broker_logging_enabled",
|
||||
"networkfirewall_logging_enabled",
|
||||
"opensearch_service_domains_audit_logging_enabled",
|
||||
"opensearch_service_domains_cloudwatch_logging_enabled",
|
||||
"route53_public_hosted_zones_cloudwatch_logging_enabled",
|
||||
"s3_bucket_server_access_logging_enabled",
|
||||
"stepfunctions_statemachine_logging_enabled",
|
||||
"waf_global_webacl_logging_enabled",
|
||||
"wafv2_webacl_logging_enabled",
|
||||
"wafv2_webacl_rule_logging_enabled"
|
||||
"s3_bucket_object_versioning"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "cc_a_1_3",
|
||||
"Name": "A1.3 The entity tests recovery plan procedures supporting system recovery to meet its objectives",
|
||||
"Description": "Implements Business Continuity Plan Testing - Business continuity plan testing is performed on a periodic basis. The testing includes (1) development of testing scenarios based on threat likelihood and magnitude; (2) consideration of system components from across the entity that can impair the availability; (3) scenarios that consider the potential for the lack of availability of key personnel; and (4) revision of continuity plans and systems based on test results. Tests Integrity and Completeness of Back-Up Data - The integrity and completeness of back-up information is tested on a periodic basis.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "cc_a_1_3",
|
||||
"Section": "CCA1.0 - Additional Criterial for Availability",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "cc_c_1_1",
|
||||
"Name": "C1.1 The entity identifies and maintains confidential information to meet the entity’s objectives related to confidentiality",
|
||||
@@ -521,7 +639,7 @@
|
||||
{
|
||||
"ItemId": "cc_c_1_1",
|
||||
"Section": "CCC1.0 - Additional Criterial for Confidentiality",
|
||||
"Service": "rds",
|
||||
"Service": "aws",
|
||||
"Type": "automated"
|
||||
}
|
||||
],
|
||||
@@ -537,15 +655,265 @@
|
||||
{
|
||||
"ItemId": "cc_c_1_2",
|
||||
"Section": "CCC1.0 - Additional Criterial for Confidentiality",
|
||||
"Service": "aws",
|
||||
"Service": "s3",
|
||||
"Type": "automated"
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"s3_bucket_object_versioning",
|
||||
"cloudwatch_log_group_retention_policy_specific_days_enabled",
|
||||
"kinesis_stream_data_retention_period"
|
||||
"s3_bucket_object_versioning"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "p_1_1",
|
||||
"Name": "P1.1 The entity provides notice to data subjects about its privacy practices to meet the entity’s objectives related to privacy",
|
||||
"Description": "The entity provides notice to data subjects about its privacy practices to meet the entity’s objectives related to privacy. The notice is updated and communicated to data subjects in a timely manner for changes to the entity’s privacy practices, including changes in the use of personal information, to meet the entity’s objectives related to privacy. Communicates to Data Subjects - Notice is provided to data subjects regarding the following: Purpose for collecting personal informationChoice and consentTypes of personal information collectedMethods of collection (for example, use of cookies or other tracking techniques)Use, retention, and disposalAccessDisclosure to third partiesSecurity for privacyQuality, including data subjects’ responsibilities for qualityMonitoring and enforcementIf personal information is collected from sources other than the individual, such sources are described in the privacy notice. Provides Notice to Data Subjects - Notice is provided to data subjects (1) at or before the time personal information is collected or as soon as practical thereafter, (2) at or before the entity changes its privacy notice or as soon as practical thereafter, or (3) before personal information is used for new purposes not previously identified. Covers Entities and Activities in Notice - An objective description of the entities and activities covered is included in the entity’s privacy notice. Uses Clear and Conspicuous Language - The entity’s privacy notice is conspicuous and uses clear language.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "p_1_1",
|
||||
"Section": "P1.0 - Privacy Criteria Related to Notice and Communication of Objectives Related to Privacy",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "p_2_1",
|
||||
"Name": "P2.1 The entity communicates choices available regarding the collection, use, retention, disclosure, and disposal of personal information to the data subjects and the consequences, if any, of each choice",
|
||||
"Description": "The entity communicates choices available regarding the collection, use, retention, disclosure, and disposal of personal information to the data subjects and the consequences, if any, of each choice. Explicit consent for the collection, use, retention, disclosure, and disposal of personal information is obtained from data subjects or other authorized persons, if required. Such consent is obtained only for the intended purpose of the information to meet the entity’s objectives related to privacy. The entity’s basis for determining implicit consent for the collection, use, retention, disclosure, and disposal of personal information is documented. Communicates to Data Subjects - Data subjects are informed (a) about the choices available to them with respect to the collection, use, and disclosure of personal information and (b) that implicit or explicit consent is required to collect, use, and disclose personal information, unless a law or regulation specifically requires or allows otherwise. Communicates Consequences of Denying or Withdrawing Consent - When personal information is collected, data subjects are informed of the consequences of refusing to provide personal information or denying or withdrawing consent to use personal information for purposes identified in the notice. Obtains Implicit or Explicit Consent - Implicit or explicit consent is obtained from data subjects at or before the time personal information is collected or soon thereafter. The individual’s preferences expressed in his or her consent are confirmed and implemented. Documents and Obtains Consent for New Purposes and Uses - If information that was previously collected is to be used for purposes not previously identified in the privacy notice, the new purpose is documented, the data subject is notified, and implicit or explicit consent is obtained prior to such new use or purpose. Obtains Explicit Consent for Sensitive Information - Explicit consent is obtained directly from the data subject when sensitive personal information is collected, used, or disclosed, unless a law or regulation specifically requires otherwise.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "p_2_1",
|
||||
"Section": "P2.0 - Privacy Criteria Related to Choice and Consent",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "p_3_1",
|
||||
"Name": "P3.1 Personal information is collected consistent with the entity’s objectives related to privacy",
|
||||
"Description": "Limits the Collection of Personal Information - The collection of personal information is limited to that necessary to meet the entity’s objectives. Collects Information by Fair and Lawful Means - Methods of collecting personal information are reviewed by management before they are implemented to confirm that personal information is obtained (a) fairly, without intimidation or deception, and (b) lawfully, adhering to all relevant rules of law, whether derived from statute or common law, relating to the collection of personal information. Collects Information From Reliable Sources - Management confirms that third parties from whom personal information is collected (that is, sources other than the individual) are reliable sources that collect information fairly and lawfully. Informs Data Subjects When Additional Information Is Acquired - Data subjects are informed if the entity develops or acquires additional information about them for its use.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "p_3_1",
|
||||
"Section": "P3.0 - Privacy Criteria Related to Collection",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "p_3_2",
|
||||
"Name": "P3.2 For information requiring explicit consent, the entity communicates the need for such consent, as well as the consequences of a failure to provide consent for the request for personal information, and obtains the consent prior to the collection of the information to meet the entity’s objectives related to privacy",
|
||||
"Description": "Obtains Explicit Consent for Sensitive Information - Explicit consent is obtained directly from the data subject when sensitive personal information is collected, used, or disclosed, unless a law or regulation specifically requires otherwise. Documents Explicit Consent to Retain Information - Documentation of explicit consent for the collection, use, or disclosure of sensitive personal information is retained in accordance with objectives related to privacy.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "p_3_2",
|
||||
"Section": "P3.0 - Privacy Criteria Related to Collection",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "p_4_1",
|
||||
"Name": "P4.1 The entity limits the use of personal information to the purposes identified in the entity’s objectives related to privacy",
|
||||
"Description": "Uses Personal Information for Intended Purposes - Personal information is used only for the intended purposes for which it was collected and only when implicit or explicit consent has been obtained unless a law or regulation specifically requires otherwise.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "p_4_1",
|
||||
"Section": "P4.0 - Privacy Criteria Related to Use, Retention, and Disposal",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "p_4_2",
|
||||
"Name": "P4.2 The entity retains personal information consistent with the entity’s objectives related to privacy",
|
||||
"Description": "Retains Personal Information - Personal information is retained for no longer than necessary to fulfill the stated purposes, unless a law or regulation specifically requires otherwise. Protects Personal Information - Policies and procedures have been implemented to protect personal information from erasure or destruction during the specified retention period of the information.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "p_4_2",
|
||||
"Section": "P4.0 - Privacy Criteria Related to Use, Retention, and Disposal",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "p_4_3",
|
||||
"Name": "P4.3 The entity securely disposes of personal information to meet the entity’s objectives related to privacy",
|
||||
"Description": "Captures, Identifies, and Flags Requests for Deletion - Requests for deletion of personal information are captured, and information related to the requests is identified and flagged for destruction to meet the entity’s objectives related to privacy. Disposes of, Destroys, and Redacts Personal Information - Personal information no longer retained is anonymized, disposed of, or destroyed in a manner that prevents loss, theft, misuse, or unauthorized access. Destroys Personal Information - Policies and procedures are implemented to erase or otherwise destroy personal information that has been identified for destruction.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "p_4_3",
|
||||
"Section": "P4.0 - Privacy Criteria Related to Use, Retention, and Disposal",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "p_5_1",
|
||||
"Name": "P5.1 The entity grants identified and authenticated data subjects the ability to access their stored personal information for review and, upon request, provides physical or electronic copies of that information to data subjects to meet the entity’s objectives related to privacy",
|
||||
"Description": "The entity grants identified and authenticated data subjects the ability to access their stored personal information for review and, upon request, provides physical or electronic copies of that information to data subjects to meet the entity’s objectives related to privacy. If access is denied, data subjects are informed of the denial and reason for such denial, as required, to meet the entity’s objectives related to privacy. Authenticates Data Subjects’ Identity - The identity of data subjects who request access to their personal information is authenticated before they are given access to that information. Permits Data Subjects Access to Their Personal Information - Data subjects are able to determine whether the entity maintains personal information about them and, upon request, may obtain access to their personal information. Provides Understandable Personal Information Within Reasonable Time - Personal information is provided to data subjects in an understandable form, in a reasonable time frame, and at a reasonable cost, if any. Informs Data Subjects If Access Is Denied - When data subjects are denied access to their personal information, the entity informs them of the denial and the reason for the denial in a timely manner, unless prohibited by law or regulation.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "p_5_1",
|
||||
"Section": "P5.0 - Privacy Criteria Related to Access",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "p_5_2",
|
||||
"Name": "P5.2 The entity corrects, amends, or appends personal information based on information provided by data subjects and communicates such information to third parties, as committed or required, to meet the entity’s objectives related to privacy",
|
||||
"Description": "The entity corrects, amends, or appends personal information based on information provided by data subjects and communicates such information to third parties, as committed or required, to meet the entity’s objectives related to privacy. If a request for correction is denied, data subjects are informed of the denial and reason for such denial to meet the entity’s objectives related to privacy. Communicates Denial of Access Requests - Data subjects are informed, in writing, of the reason a request for access to their personal information was denied, the source of the entity’s legal right to deny such access, if applicable, and the individual’s right, if any, to challenge such denial, as specifically permitted or required by law or regulation. Permits Data Subjects to Update or Correct Personal Information - Data subjects are able to update or correct personal information held by the entity. The entity provides such updated or corrected information to third parties that were previously provided with the data subject’s personal information consistent with the entity’s objective related to privacy. Communicates Denial of Correction Requests - Data subjects are informed, in writing, about the reason a request for correction of personal information was denied and how they may appeal.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "p_5_2",
|
||||
"Section": "P5.0 - Privacy Criteria Related to Access",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "p_6_1",
|
||||
"Name": "P6.1 The entity discloses personal information to third parties with the explicit consent of data subjects, and such consent is obtained prior to disclosure to meet the entity’s objectives related to privacy",
|
||||
"Description": "Communicates Privacy Policies to Third Parties - Privacy policies or other specific instructions or requirements for handling personal information are communicated to third parties to whom personal information is disclosed. Discloses Personal Information Only When Appropriate - Personal information is disclosed to third parties only for the purposes for which it was collected or created and only when implicit or explicit consent has been obtained from the data subject, unless a law or regulation specifically requires otherwise. Discloses Personal Information Only to Appropriate Third Parties - Personal information is disclosed only to third parties who have agreements with the entity to protect personal information in a manner consistent with the relevant aspects of the entity’s privacy notice or other specific instructions or requirements. The entity has procedures in place to evaluate that the third parties have effective controls to meet the terms of the agreement, instructions, or requirements.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "p_6_1",
|
||||
"Section": "P6.0 - Privacy Criteria Related to Disclosure and Notification",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "p_6_2",
|
||||
"Name": "P6.2 The entity creates and retains a complete, accurate, and timely record of authorized disclosures of personal information to meet the entity’s objectives related to privacy",
|
||||
"Description": "Creates and Retains Record of Authorized Disclosures - The entity creates and maintains a record of authorized disclosures of personal information that is complete, accurate, and timely.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "p_6_2",
|
||||
"Section": "P6.0 - Privacy Criteria Related to Disclosure and Notification",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "p_6_3",
|
||||
"Name": "P6.3 The entity creates and retains a complete, accurate, and timely record of detected or reported unauthorized disclosures (including breaches) of personal information to meet the entity’s objectives related to privacy",
|
||||
"Description": "Creates and Retains Record of Detected or Reported Unauthorized Disclosures - The entity creates and maintains a record of detected or reported unauthorized disclosures of personal information that is complete, accurate, and timely.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "p_6_3",
|
||||
"Section": "P6.0 - Privacy Criteria Related to Disclosure and Notification",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "p_6_4",
|
||||
"Name": "P6.4 The entity obtains privacy commitments from vendors and other third parties who have access to personal information to meet the entity’s objectives related to privacy",
|
||||
"Description": "The entity obtains privacy commitments from vendors and other third parties who have access to personal information to meet the entity’s objectives related to privacy. The entity assesses those parties’ compliance on a periodic and as-needed basis and takes corrective action, if necessary. Discloses Personal Information Only to Appropriate Third Parties - Personal information is disclosed only to third parties who have agreements with the entity to protect personal information in a manner consistent with the relevant aspects of the entity’s privacy notice or other specific instructions or requirements. The entity has procedures in place to evaluate that the third parties have effective controls to meet the terms of the agreement, instructions, or requirements. Remediates Misuse of Personal Information by a Third Party - The entity takes remedial action in response to misuse of personal information by a third party to whom the entity has transferred such information.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "p_6_4",
|
||||
"Section": "P6.0 - Privacy Criteria Related to Disclosure and Notification",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "p_6_5",
|
||||
"Name": "P6.5 The entity obtains commitments from vendors and other third parties with access to personal information to notify the entity in the event of actual or suspected unauthorized disclosures of personal information",
|
||||
"Description": "The entity obtains commitments from vendors and other third parties with access to personal information to notify the entity in the event of actual or suspected unauthorized disclosures of personal information. Such notifications are reported to appropriate personnel and acted on in accordance with established incident response procedures to meet the entity’s objectives related to privacy. Remediates Misuse of Personal Information by a Third Party - The entity takes remedial action in response to misuse of personal information by a third party to whom the entity has transferred such information. Reports Actual or Suspected Unauthorized Disclosures - A process exists for obtaining commitments from vendors and other third parties to report to the entity actual or suspected unauthorized disclosures of personal information.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "p_6_5",
|
||||
"Section": "P6.0 - Privacy Criteria Related to Disclosure and Notification",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "p_6_6",
|
||||
"Name": "P6.6 The entity provides notification of breaches and incidents to affected data subjects, regulators, and others to meet the entity’s objectives related to privacy",
|
||||
"Description": "Remediates Misuse of Personal Information by a Third Party - The entity takes remedial action in response to misuse of personal information by a third party to whom the entity has transferred such information. Reports Actual or Suspected Unauthorized Disclosures - A process exists for obtaining commitments from vendors and other third parties to report to the entity actual or suspected unauthorized disclosures of personal information.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "p_6_6",
|
||||
"Section": "P6.0 - Privacy Criteria Related to Disclosure and Notification",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "p_6_7",
|
||||
"Name": "P6.7 The entity provides data subjects with an accounting of the personal information held and disclosure of the data subjects’ personal information, upon the data subjects’ request, to meet the entity’s objectives related to privacy",
|
||||
"Description": "Identifies Types of Personal Information and Handling Process - The types of personal information and sensitive personal information and the related processes, systems, and third parties involved in the handling of such information are identified. Captures, Identifies, and Communicates Requests for Information - Requests for an accounting of personal information held and disclosures of the data subjects’ personal information are captured, and information related to the requests is identified and communicated to data subjects to meet the entity’s objectives related to privacy.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "p_6_7",
|
||||
"Section": "P6.0 - Privacy Criteria Related to Disclosure and Notification",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "p_7_1",
|
||||
"Name": "P7.1 The entity collects and maintains accurate, up-to-date, complete, and relevant personal information to meet the entity’s objectives related to privacy",
|
||||
"Description": "Ensures Accuracy and Completeness of Personal Information - Personal information is accurate and complete for the purposes for which it is to be used. Ensures Relevance of Personal Information - Personal information is relevant to the purposes for which it is to be used.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "p_7_1",
|
||||
"Section": "P7.0 - Privacy Criteria Related to Quality",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
},
|
||||
{
|
||||
"Id": "p_8_1",
|
||||
"Name": "P8.1 The entity implements a process for receiving, addressing, resolving, and communicating the resolution of inquiries, complaints, and disputes from data subjects and others and periodically monitors compliance to meet the entity’s objectives related to privacy",
|
||||
"Description": "The entity implements a process for receiving, addressing, resolving, and communicating the resolution of inquiries, complaints, and disputes from data subjects and others and periodically monitors compliance to meet the entity’s objectives related to privacy. Corrections and other necessary actions related to identified deficiencies are made or taken in a timely manner. Communicates to Data Subjects—Data subjects are informed about how to contact the entity with inquiries, complaints, and disputes. Addresses Inquiries, Complaints, and Disputes - A process is in place to address inquiries, complaints, and disputes. Documents and Communicates Dispute Resolution and Recourse - Each complaint is addressed, and the resolution is documented and communicated to the individual. Documents and Reports Compliance Review Results - Compliance with objectives related to privacy are reviewed and documented, and the results of such reviews are reported to management. If problems are identified, remediation plans are developed and implemented. Documents and Reports Instances of Noncompliance - Instances of noncompliance with objectives related to privacy are documented and reported and, if needed, corrective and disciplinary measures are taken on a timely basis. Performs Ongoing Monitoring - Ongoing procedures are performed for monitoring the effectiveness of controls over personal information and for taking timely corrective actions when necessary.",
|
||||
"Attributes": [
|
||||
{
|
||||
"ItemId": "p_8_1",
|
||||
"Section": "P8.0 - Privacy Criteria Related to Monitoring and Enforcement",
|
||||
"Service": "aws",
|
||||
"Type": "manual"
|
||||
}
|
||||
],
|
||||
"Checks": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -222,7 +222,7 @@
|
||||
"Id": "1.11",
|
||||
"Description": "Ensure That Separation of Duties Is Enforced While Assigning KMS Related Roles to Users",
|
||||
"Checks": [
|
||||
"iam_role_kms_enforce_separation_of_duties"
|
||||
"iam_role_kms_enforce_separation_of_duties”"
|
||||
],
|
||||
"Attributes": [
|
||||
{
|
||||
@@ -394,7 +394,7 @@
|
||||
"Id": "2.2",
|
||||
"Description": "Ensure That Sinks Are Configured for All Log Entries",
|
||||
"Checks": [
|
||||
"cloudstorage_bucket_log_retention_policy_lock"
|
||||
"cloudstorage_bucket_log_retention_policy_lock”"
|
||||
],
|
||||
"Attributes": [
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,7 @@ from prowler.lib.logger import logger
|
||||
|
||||
timestamp = datetime.today()
|
||||
timestamp_utc = datetime.now(timezone.utc).replace(tzinfo=timezone.utc)
|
||||
prowler_version = "5.4.5"
|
||||
prowler_version = "5.3.0"
|
||||
html_logo_url = "https://github.com/prowler-cloud/prowler/"
|
||||
square_logo_img = "https://prowler.com/wp-content/uploads/logo-html.png"
|
||||
aws_logo = "https://user-images.githubusercontent.com/38561120/235953920-3e3fba08-0795-41dc-b480-9bea57db9f2e.png"
|
||||
@@ -123,10 +123,7 @@ def load_and_validate_config_file(provider: str, config_file_path: str) -> dict:
|
||||
|
||||
# Not to introduce a breaking change, allow the old format config file without any provider keys
|
||||
# and a new format with a key for each provider to include their configuration values within.
|
||||
if any(
|
||||
key in config_file
|
||||
for key in ["aws", "gcp", "azure", "kubernetes", "microsoft365"]
|
||||
):
|
||||
if any(key in config_file for key in ["aws", "gcp", "azure", "kubernetes"]):
|
||||
config = config_file.get(provider, {})
|
||||
else:
|
||||
config = config_file if config_file else {}
|
||||
|
||||
@@ -95,7 +95,6 @@ aws:
|
||||
"python3.6",
|
||||
"python2.7",
|
||||
"python3.7",
|
||||
"python3.8",
|
||||
"nodejs4.3",
|
||||
"nodejs4.3-edge",
|
||||
"nodejs6.10",
|
||||
@@ -106,7 +105,6 @@ aws:
|
||||
"nodejs14.x",
|
||||
"nodejs16.x",
|
||||
"dotnet5.0",
|
||||
"dotnet6",
|
||||
"dotnet7",
|
||||
"dotnetcore1.0",
|
||||
"dotnetcore2.0",
|
||||
@@ -310,8 +308,6 @@ aws:
|
||||
"ListFoundationModelAgreementOffers", # Lists available agreement offers for accessing foundation models (List).
|
||||
"ListFoundationModels", # Lists the available foundation models in Bedrock (List).
|
||||
"ListProvisionedModelThroughputs", # Lists the provisioned throughput for previously created models (List).
|
||||
"SearchAgreements", # Searches for agreements based on specified criteria (List).
|
||||
"AcceptAgreementRequest", # Accepts a request for an agreement to use a foundation model (Write).
|
||||
]
|
||||
|
||||
# AWS RDS Configuration
|
||||
@@ -364,11 +360,6 @@ aws:
|
||||
# Minimum number of Availability Zones that an ELBv2 must be in
|
||||
elbv2_min_azs: 2
|
||||
|
||||
# AWS Elasticache Configuration
|
||||
# aws.elasticache_redis_cluster_backup_enabled
|
||||
# Minimum number of days that a Redis cluster must have backups retention period
|
||||
minimum_snapshot_retention_period: 7
|
||||
|
||||
|
||||
# AWS Secrets Configuration
|
||||
# Patterns to ignore in the secrets checks
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user