Compare commits

..

1 Commits

Author SHA1 Message Date
Sergio Garcia
e83a135742 release version 2023-02-21 17:01:24 +01:00
2803 changed files with 30641 additions and 173946 deletions

2
.github/CODEOWNERS vendored
View File

@@ -1 +1 @@
* @prowler-cloud/prowler-oss @prowler-cloud/prowler-dev
* @prowler-cloud/prowler-team

52
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,52 @@
---
name: Bug report
about: Create a report to help us improve
title: "[Bug]: "
labels: bug, status/needs-triage
assignees: ''
---
<!--
Please use this template to create your bug report. By providing as much info as possible you help us understand the issue, reproduce it and resolve it for you quicker. Therefore, take a couple of extra minutes to make sure you have provided all info needed.
PROTIP: record your screen and attach it as a gif to showcase the issue.
- How to record and attach gif: https://bit.ly/2Mi8T6K
-->
**What happened?**
A clear and concise description of what the bug is or what is not working as expected
**How to reproduce it**
Steps to reproduce the behavior:
1. What command are you running?
2. Cloud provider you are launching
3. Environment you have like single account, multi-account, organizations, multi or single subsctiption, etc.
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots or Logs**
If applicable, add screenshots to help explain your problem.
Also, you can add logs (anonymize them first!). Here a command that may help to share a log
`prowler <your arguments> --log-level DEBUG --log-file $(date +%F)_debug.log` then attach here the log file.
**From where are you running Prowler?**
Please, complete the following information:
- Resource: (e.g. EC2 instance, Fargate task, Docker container manually, EKS, Cloud9, CodeBuild, workstation, etc.)
- OS: [e.g. Amazon Linux 2, Mac, Alpine, Windows, etc. ]
- Prowler Version [`prowler --version`]:
- Python version [`python --version`]:
- Pip version [`pip --version`]:
- Installation method (Are you running it from pip package or cloning the github repo?):
- Others:
**Additional context**
Add any other context about the problem here.

View File

@@ -1,97 +0,0 @@
name: 🐞 Bug Report
description: Create a report to help us improve
title: "[Bug]: "
labels: ["bug", "status/needs-triage"]
body:
- type: textarea
id: reproduce
attributes:
label: Steps to Reproduce
description: Steps to reproduce the behavior
placeholder: |-
1. What command are you running?
2. Cloud provider you are launching
3. Environment you have, like single account, multi-account, organizations, multi or single subscription, etc.
4. See error
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behavior
description: A clear and concise description of what you expected to happen.
validations:
required: true
- type: textarea
id: actual
attributes:
label: Actual Result with Screenshots or Logs
description: If applicable, add screenshots to help explain your problem. Also, you can add logs (anonymize them first!). Here a command that may help to share a log `prowler <your arguments> --log-level DEBUG --log-file $(date +%F)_debug.log` then attach here the log file.
validations:
required: true
- type: dropdown
id: type
attributes:
label: How did you install Prowler?
options:
- Cloning the repository from github.com (git clone)
- From pip package (pip install prowler)
- From brew (brew install prowler)
- Docker (docker pull toniblyx/prowler)
validations:
required: true
- type: textarea
id: environment
attributes:
label: Environment Resource
description: From where are you running Prowler?
placeholder: |-
1. EC2 instance
2. Fargate task
3. Docker container locally
4. EKS
5. Cloud9
6. CodeBuild
7. Workstation
8. Other(please specify)
validations:
required: true
- type: textarea
id: os
attributes:
label: OS used
description: Which OS are you using?
placeholder: |-
1. Amazon Linux 2
2. MacOS
3. Alpine Linux
4. Windows
5. Other(please specify)
validations:
required: true
- type: input
id: prowler-version
attributes:
label: Prowler version
description: Which Prowler version are you using?
placeholder: |-
prowler --version
validations:
required: true
- type: input
id: pip-version
attributes:
label: Pip version
description: Which pip version are you using?
placeholder: |-
pip --version
validations:
required: true
- type: textarea
id: additional
attributes:
description: Additional context
label: Context
validations:
required: false

View File

@@ -1,36 +0,0 @@
name: 💡 Feature Request
description: Suggest an idea for this project
labels: ["feature-request", "status/needs-triage"]
body:
- type: textarea
id: Problem
attributes:
label: New feature motivation
description: Is your feature request related to a problem? Please describe
placeholder: |-
1. A clear and concise description of what the problem is. Ex. I'm always frustrated when
validations:
required: true
- type: textarea
id: Solution
attributes:
label: Solution Proposed
description: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
id: Alternatives
attributes:
label: Describe alternatives you've considered
description: A clear and concise description of any alternative solutions or features you've considered.
validations:
required: true
- type: textarea
id: Context
attributes:
label: Additional context
description: Add any other context or screenshots about the feature request here.
validations:
required: false

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement, status/needs-triage
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -5,38 +5,11 @@
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
- package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
open-pull-requests-limit: 10
interval: "daily"
target-branch: master
labels:
- "dependencies"
- "pip"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
target-branch: master
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
target-branch: v3
labels:
- "dependencies"
- "pip"
- "v3"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
target-branch: v3
labels:
- "github_actions"
- "v3"

27
.github/labeler.yml vendored
View File

@@ -1,27 +0,0 @@
documentation:
- changed-files:
- any-glob-to-any-file: "docs/**"
provider/aws:
- changed-files:
- any-glob-to-any-file: "prowler/providers/aws/**"
- any-glob-to-any-file: "tests/providers/aws/**"
provider/azure:
- changed-files:
- any-glob-to-any-file: "prowler/providers/azure/**"
- any-glob-to-any-file: "tests/providers/azure/**"
provider/gcp:
- changed-files:
- any-glob-to-any-file: "prowler/providers/gcp/**"
- any-glob-to-any-file: "tests/providers/gcp/**"
provider/kubernetes:
- changed-files:
- any-glob-to-any-file: "prowler/providers/kubernetes/**"
- any-glob-to-any-file: "tests/providers/kubernetes/**"
github_actions:
- changed-files:
- any-glob-to-any-file: ".github/workflows/*"

View File

@@ -1,24 +0,0 @@
name: Pull Request Documentation Link
on:
pull_request:
branches:
- 'master'
- 'v3'
paths:
- 'docs/**'
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
jobs:
documentation-link:
name: Documentation Link
runs-on: ubuntu-latest
steps:
- name: Leave PR comment with the SaaS Documentation URI
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ env.PR_NUMBER }}
body: |
You can check the documentation for this PR here -> [SaaS Documentation](https://prowler-prowler-docs--${{ env.PR_NUMBER }}.com.readthedocs.build/projects/prowler-open-source/en/${{ env.PR_NUMBER }}/)

View File

@@ -3,7 +3,6 @@ name: build-lint-push-containers
on:
push:
branches:
- "v3"
- "master"
paths-ignore:
- ".github/**"
@@ -14,161 +13,167 @@ on:
types: [published]
env:
# AWS Configuration
AWS_REGION_STG: eu-west-1
AWS_REGION_PLATFORM: eu-west-1
AWS_REGION: us-east-1
# Container's configuration
AWS_REGION_PRO: us-east-1
IMAGE_NAME: prowler
DOCKERFILE_PATH: ./Dockerfile
# Tags
LATEST_TAG: latest
STABLE_TAG: stable
# The RELEASE_TAG is set during runtime in releases
RELEASE_TAG: ""
# The PROWLER_VERSION and PROWLER_VERSION_MAJOR are set during runtime in releases
PROWLER_VERSION: ""
PROWLER_VERSION_MAJOR: ""
# TEMPORARY_TAG: temporary
# Python configuration
PYTHON_VERSION: 3.12
TEMPORARY_TAG: temporary
DOCKERFILE_PATH: ./Dockerfile
jobs:
# Lint Dockerfile using Hadolint
# dockerfile-linter:
# runs-on: ubuntu-latest
# steps:
# -
# name: Checkout
# uses: actions/checkout@v3
# -
# name: Install Hadolint
# run: |
# VERSION=$(curl --silent "https://api.github.com/repos/hadolint/hadolint/releases/latest" | \
# grep '"tag_name":' | \
# sed -E 's/.*"v([^"]+)".*/\1/' \
# ) && curl -L -o /tmp/hadolint https://github.com/hadolint/hadolint/releases/download/v${VERSION}/hadolint-Linux-x86_64 \
# && chmod +x /tmp/hadolint
# -
# name: Run Hadolint
# run: |
# /tmp/hadolint util/Dockerfile
# Build Prowler OSS container
container-build-push:
container-build:
# needs: dockerfile-linter
runs-on: ubuntu-latest
outputs:
prowler_version_major: ${{ steps.get-prowler-version.outputs.PROWLER_VERSION_MAJOR }}
prowler_version: ${{ steps.update-prowler-version.outputs.PROWLER_VERSION }}
env:
POETRY_VIRTUALENVS_CREATE: "false"
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build
uses: docker/build-push-action@v2
with:
python-version: ${{ env.PYTHON_VERSION }}
# Without pushing to registries
push: false
tags: ${{ env.IMAGE_NAME }}:${{ env.TEMPORARY_TAG }}
file: ${{ env.DOCKERFILE_PATH }}
outputs: type=docker,dest=/tmp/${{ env.IMAGE_NAME }}.tar
- name: Share image between jobs
uses: actions/upload-artifact@v2
with:
name: ${{ env.IMAGE_NAME }}.tar
path: /tmp/${{ env.IMAGE_NAME }}.tar
- name: Install Poetry
# Lint Prowler OSS container using Dockle
# container-linter:
# needs: container-build
# runs-on: ubuntu-latest
# steps:
# -
# name: Get container image from shared
# uses: actions/download-artifact@v2
# with:
# name: ${{ env.IMAGE_NAME }}.tar
# path: /tmp
# -
# name: Load Docker image
# run: |
# docker load --input /tmp/${{ env.IMAGE_NAME }}.tar
# docker image ls -a
# -
# name: Install Dockle
# run: |
# VERSION=$(curl --silent "https://api.github.com/repos/goodwithtech/dockle/releases/latest" | \
# grep '"tag_name":' | \
# sed -E 's/.*"v([^"]+)".*/\1/' \
# ) && curl -L -o dockle.deb https://github.com/goodwithtech/dockle/releases/download/v${VERSION}/dockle_${VERSION}_Linux-64bit.deb \
# && sudo dpkg -i dockle.deb && rm dockle.deb
# -
# name: Run Dockle
# run: dockle ${{ env.IMAGE_NAME }}:${{ env.TEMPORARY_TAG }}
# Push Prowler OSS container to registries
container-push:
# needs: container-linter
needs: container-build
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read # This is required for actions/checkout
steps:
- name: Get container image from shared
uses: actions/download-artifact@v2
with:
name: ${{ env.IMAGE_NAME }}.tar
path: /tmp
- name: Load Docker image
run: |
pipx install poetry
pipx inject poetry poetry-bumpversion
- name: Get Prowler version
id: get-prowler-version
run: |
PROWLER_VERSION="$(poetry version -s 2>/dev/null)"
# Store prowler version major just for the release
PROWLER_VERSION_MAJOR="${PROWLER_VERSION%%.*}"
echo "PROWLER_VERSION_MAJOR=${PROWLER_VERSION_MAJOR}" >> "${GITHUB_ENV}"
echo "PROWLER_VERSION_MAJOR=${PROWLER_VERSION_MAJOR}" >> "${GITHUB_OUTPUT}"
case ${PROWLER_VERSION_MAJOR} in
3)
echo "LATEST_TAG=v3-latest" >> "${GITHUB_ENV}"
echo "STABLE_TAG=v3-stable" >> "${GITHUB_ENV}"
;;
4)
echo "LATEST_TAG=latest" >> "${GITHUB_ENV}"
echo "STABLE_TAG=stable" >> "${GITHUB_ENV}"
;;
*)
# Fallback if any other version is present
echo "Releasing another Prowler major version, aborting..."
exit 1
;;
esac
- name: Update Prowler version (release)
id: update-prowler-version
if: github.event_name == 'release'
run: |
PROWLER_VERSION="${{ github.event.release.tag_name }}"
poetry version "${PROWLER_VERSION}"
echo "PROWLER_VERSION=${PROWLER_VERSION}" >> "${GITHUB_ENV}"
echo "PROWLER_VERSION=${PROWLER_VERSION}" >> "${GITHUB_OUTPUT}"
docker load --input /tmp/${{ env.IMAGE_NAME }}.tar
docker image ls -a
- name: Login to DockerHub
uses: docker/login-action@v3
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to Public ECR
uses: docker/login-action@v3
uses: docker/login-action@v2
with:
registry: public.ecr.aws
username: ${{ secrets.PUBLIC_ECR_AWS_ACCESS_KEY_ID }}
password: ${{ secrets.PUBLIC_ECR_AWS_SECRET_ACCESS_KEY }}
env:
AWS_REGION: ${{ env.AWS_REGION }}
AWS_REGION: ${{ env.AWS_REGION_PRO }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push container image (latest)
- name: Tag (latest)
if: github.event_name == 'push'
uses: docker/build-push-action@v5
with:
push: true
tags: |
${{ secrets.DOCKER_HUB_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ env.LATEST_TAG }}
${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ env.LATEST_TAG }}
file: ${{ env.DOCKERFILE_PATH }}
cache-from: type=gha
cache-to: type=gha,mode=max
run: |
docker tag ${{ env.IMAGE_NAME }}:${{ env.TEMPORARY_TAG }} ${{ secrets.DOCKER_HUB_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ env.LATEST_TAG }}
docker tag ${{ env.IMAGE_NAME }}:${{ env.TEMPORARY_TAG }} ${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ env.LATEST_TAG }}
- name: Build and push container image (release)
- # Push to master branch - push "latest" tag
name: Push (latest)
if: github.event_name == 'push'
run: |
docker push ${{ secrets.DOCKER_HUB_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ env.LATEST_TAG }}
docker push ${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ env.LATEST_TAG }}
- # Tag the new release (stable and release tag)
name: Tag (release)
if: github.event_name == 'release'
uses: docker/build-push-action@v5
run: |
docker tag ${{ env.IMAGE_NAME }}:${{ env.TEMPORARY_TAG }} ${{ secrets.DOCKER_HUB_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ github.event.release.tag_name }}
docker tag ${{ env.IMAGE_NAME }}:${{ env.TEMPORARY_TAG }} ${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ github.event.release.tag_name }}
docker tag ${{ env.IMAGE_NAME }}:${{ env.TEMPORARY_TAG }} ${{ secrets.DOCKER_HUB_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ env.STABLE_TAG }}
docker tag ${{ env.IMAGE_NAME }}:${{ env.TEMPORARY_TAG }} ${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ env.STABLE_TAG }}
- # Push the new release (stable and release tag)
name: Push (release)
if: github.event_name == 'release'
run: |
docker push ${{ secrets.DOCKER_HUB_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ github.event.release.tag_name }}
docker push ${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ github.event.release.tag_name }}
docker push ${{ secrets.DOCKER_HUB_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ env.STABLE_TAG }}
docker push ${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ env.STABLE_TAG }}
- name: Delete artifacts
if: always()
uses: geekyeggo/delete-artifact@v1
with:
# Use local context to get changes
# https://github.com/docker/build-push-action#path-context
context: .
push: true
tags: |
${{ secrets.DOCKER_HUB_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ env.PROWLER_VERSION }}
${{ secrets.DOCKER_HUB_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ env.STABLE_TAG }}
${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ env.PROWLER_VERSION }}
${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ env.STABLE_TAG }}
file: ${{ env.DOCKERFILE_PATH }}
cache-from: type=gha
cache-to: type=gha,mode=max
name: ${{ env.IMAGE_NAME }}.tar
dispatch-action:
needs: container-build-push
needs: container-push
runs-on: ubuntu-latest
steps:
- name: Get latest commit info (latest)
- name: Dispatch event for latest
if: github.event_name == 'push'
run: |
LATEST_COMMIT_HASH=$(echo ${{ github.event.after }} | cut -b -7)
echo "LATEST_COMMIT_HASH=${LATEST_COMMIT_HASH}" >> $GITHUB_ENV
- name: Dispatch event (latest)
if: github.event_name == 'push' && needs.container-build-push.outputs.prowler_version_major == '3'
curl https://api.github.com/repos/${{ secrets.DISPATCH_OWNER }}/${{ secrets.DISPATCH_REPO }}/dispatches -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.ACCESS_TOKEN }}" -H "X-GitHub-Api-Version: 2022-11-28" --data '{"event_type":"dispatch","client_payload":{"version":"latest"}'
- name: Dispatch event for release
if: github.event_name == 'release'
run: |
curl https://api.github.com/repos/${{ secrets.DISPATCH_OWNER }}/${{ secrets.DISPATCH_REPO }}/dispatches \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.ACCESS_TOKEN }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
--data '{"event_type":"dispatch","client_payload":{"version":"v3-latest", "tag": "${{ env.LATEST_COMMIT_HASH }}"}}'
- name: Dispatch event (release)
if: github.event_name == 'release' && needs.container-build-push.outputs.prowler_version_major == '3'
run: |
curl https://api.github.com/repos/${{ secrets.DISPATCH_OWNER }}/${{ secrets.DISPATCH_REPO }}/dispatches \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.ACCESS_TOKEN }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
--data '{"event_type":"dispatch","client_payload":{"version":"release", "tag":"${{ needs.container-build-push.outputs.prowler_version }}"}}'
curl https://api.github.com/repos/${{ secrets.DISPATCH_OWNER }}/${{ secrets.DISPATCH_REPO }}/dispatches -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.ACCESS_TOKEN }}" -H "X-GitHub-Api-Version: 2022-11-28" --data '{"event_type":"dispatch","client_payload":{"version":"release", "tag":"${{ github.event.release.tag_name }}"}}'

View File

@@ -13,10 +13,10 @@ name: "CodeQL"
on:
push:
branches: [ "master", "v3" ]
branches: [ "master", prowler-2, prowler-3.0-dev ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "master", "v3" ]
branches: [ "master" ]
schedule:
- cron: '00 12 * * *'
@@ -37,11 +37,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -52,6 +52,6 @@ jobs:
# queries: security-extended,security-and-quality
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"

View File

@@ -7,13 +7,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: TruffleHog OSS
uses: trufflesecurity/trufflehog@v3.74.0
uses: trufflesecurity/trufflehog@v3.4.4
with:
path: ./
base: ${{ github.event.repository.default_branch }}
head: HEAD
extra_args: --only-verified

View File

@@ -1,16 +0,0 @@
name: "Pull Request Labeler"
on:
pull_request_target:
branches:
- "master"
- "v3"
jobs:
labeler:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v5

View File

@@ -4,90 +4,55 @@ on:
push:
branches:
- "master"
- "v3"
pull_request:
branches:
- "master"
- "v3"
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"]
python-version: ["3.9"]
steps:
- uses: actions/checkout@v4
- name: Test if changes are in not ignored paths
id: are-non-ignored-files-changed
uses: tj-actions/changed-files@v44
with:
files: ./**
files_ignore: |
.github/**
README.md
docs/**
permissions/**
mkdocs.yml
- name: Install poetry
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
run: |
python -m pip install --upgrade pip
pipx install poetry
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
uses: actions/setup-python@v5
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: "poetry"
- name: Install dependencies
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
run: |
poetry install
poetry run pip list
python -m pip install --upgrade pip
pip install pipenv
pipenv install --dev
pipenv run pip list
VERSION=$(curl --silent "https://api.github.com/repos/hadolint/hadolint/releases/latest" | \
grep '"tag_name":' | \
sed -E 's/.*"v([^"]+)".*/\1/' \
) && curl -L -o /tmp/hadolint "https://github.com/hadolint/hadolint/releases/download/v${VERSION}/hadolint-Linux-x86_64" \
&& chmod +x /tmp/hadolint
- name: Poetry check
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
run: |
poetry lock --check
- name: Lint with flake8
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
run: |
poetry run flake8 . --ignore=E266,W503,E203,E501,W605,E128 --exclude contrib
pipenv run flake8 . --ignore=E266,W503,E203,E501,W605,E128 --exclude contrib
- name: Checking format with black
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
run: |
poetry run black --check .
pipenv run black --check .
- name: Lint with pylint
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
run: |
poetry run pylint --disable=W,C,R,E -j 0 -rn -sn prowler/
pipenv run pylint --disable=W,C,R,E -j 0 -rn -sn prowler/
- name: Bandit
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
run: |
poetry run bandit -q -lll -x '*_test.py,./contrib/' -r .
pipenv run bandit -q -lll -x '*_test.py,./contrib/' -r .
- name: Safety
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
run: |
poetry run safety check
pipenv run safety check
- name: Vulture
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
run: |
poetry run vulture --exclude "contrib" --min-confidence 100 .
pipenv run vulture --exclude "contrib" --min-confidence 100 .
- name: Hadolint
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
run: |
/tmp/hadolint Dockerfile --ignore=DL3013
- name: Test with pytest
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
run: |
poetry run pytest -n auto --cov=./prowler --cov-report=xml tests
- name: Upload coverage reports to Codecov
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
uses: codecov/codecov-action@v4
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
pipenv run pytest tests -n auto

View File

@@ -5,94 +5,37 @@ on:
types: [published]
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
PYTHON_VERSION: 3.11
CACHE: "poetry"
# TODO: create a bot user for this kind of tasks, like prowler-bot
GIT_COMMITTER_EMAIL: "sergio@prowler.com"
GITHUB_BRANCH: ${{ github.event.release.tag_name }}
jobs:
release-prowler-job:
runs-on: ubuntu-latest
env:
POETRY_VIRTUALENVS_CREATE: "false"
name: Release Prowler to PyPI
steps:
- name: Get Prowler version
run: |
PROWLER_VERSION="${{ env.RELEASE_TAG }}"
case ${PROWLER_VERSION%%.*} in
3)
echo "Releasing Prowler v3 with tag ${PROWLER_VERSION}"
;;
4)
echo "Releasing Prowler v4 with tag ${PROWLER_VERSION}"
;;
*)
echo "Releasing another Prowler major version, aborting..."
exit 1
;;
esac
- uses: actions/checkout@v4
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v3
with:
ref: ${{ env.GITHUB_BRANCH }}
- name: setup python
uses: actions/setup-python@v2
with:
python-version: 3.9 #install the python needed
- name: Install dependencies
run: |
pipx install poetry
pipx inject poetry poetry-bumpversion
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: ${{ env.CACHE }}
- name: Update Poetry and config version
run: |
poetry version ${{ env.RELEASE_TAG }}
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v6
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.GPG_PASSPHRASE }}
git_user_signingkey: true
git_commit_gpgsign: true
- name: Push updated version to the release tag
run: |
# Configure Git
git config user.name "github-actions"
git config user.email "${{ env.GIT_COMMITTER_EMAIL }}"
# Add the files with the version changed
git add prowler/config/config.py pyproject.toml
git commit -m "chore(release): ${{ env.RELEASE_TAG }}" --no-verify -S
# Replace the tag with the version updated
git tag -fa ${{ env.RELEASE_TAG }} -m "chore(release): ${{ env.RELEASE_TAG }}" --sign
# Push the tag
git push -f origin ${{ env.RELEASE_TAG }}
- name: Build Prowler package
run: |
poetry build
- name: Publish Prowler package to PyPI
run: |
poetry config pypi-token.pypi ${{ secrets.PYPI_API_TOKEN }}
poetry publish
- name: Replicate PyPI package
run: |
rm -rf ./dist && rm -rf ./build && rm -rf prowler.egg-info
pip install toml
python util/replicate_pypi_package.py
poetry build
python -m pip install --upgrade pip
pip install build toml --upgrade
- name: Build package
run: python -m build
- name: Publish prowler-cloud package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_API_TOKEN }}
- name: Replicate PyPi Package
run: |
poetry config pypi-token.pypi ${{ secrets.PYPI_API_TOKEN }}
poetry publish
rm -rf ./dist && rm -rf ./build && rm -rf prowler_cloud.egg-info
python util/replicate_pypi_package.py
python -m build
- name: Publish prowler package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_API_TOKEN }}

View File

@@ -23,12 +23,12 @@ jobs:
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v4
- uses: actions/checkout@v3
with:
ref: ${{ env.GITHUB_BRANCH }}
- name: setup python
uses: actions/setup-python@v5
uses: actions/setup-python@v2
with:
python-version: 3.9 #install the python needed
@@ -38,7 +38,7 @@ jobs:
pip install boto3
- name: Configure AWS Credentials -- DEV
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@v1
with:
aws-region: ${{ env.AWS_REGION_DEV }}
role-to-assume: ${{ secrets.DEV_IAM_ROLE_ARN }}
@@ -50,12 +50,12 @@ jobs:
# Create pull request
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
uses: peter-evans/create-pull-request@v4
with:
token: ${{ secrets.PROWLER_ACCESS_TOKEN }}
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "feat(regions_update): Update regions for AWS services."
branch: "aws-services-regions-updated-${{ github.sha }}"
labels: "status/waiting-for-revision, severity/low, provider/aws, backport-v3"
labels: "status/waiting-for-revision, severity/low"
title: "chore(regions_update): Changes in regions for AWS services."
body: |
### Description

11
.gitignore vendored
View File

@@ -9,9 +9,8 @@
__pycache__
venv/
build/
/dist/
dist/
*.egg-info/
*/__pycache__/*.pyc
# Session
Session.vim
@@ -47,11 +46,3 @@ junit-reports/
# .env
.env*
# Coverage
.coverage*
.coverage
coverage*
# Node
node_modules

View File

@@ -1,7 +1,7 @@
repos:
## GENERAL
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
rev: v4.4.0
hooks:
- id: check-merge-conflict
- id: check-yaml
@@ -13,23 +13,14 @@ repos:
- id: pretty-format-json
args: ["--autofix", --no-sort-keys, --no-ensure-ascii]
## TOML
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
rev: v2.12.0
hooks:
- id: pretty-format-toml
args: [--autofix]
files: pyproject.toml
## BASH
- repo: https://github.com/koalaman/shellcheck-precommit
rev: v0.9.0
hooks:
- id: shellcheck
exclude: contrib
## PYTHON
- repo: https://github.com/myint/autoflake
rev: v2.2.1
rev: v2.0.1
hooks:
- id: autoflake
args:
@@ -40,29 +31,27 @@ repos:
]
- repo: https://github.com/timothycrosley/isort
rev: 5.13.2
rev: 5.12.0
hooks:
- id: isort
args: ["--profile", "black"]
- repo: https://github.com/psf/black
rev: 24.1.1
rev: 23.1.0
hooks:
- id: black
- repo: https://github.com/pycqa/flake8
rev: 7.0.0
rev: 6.0.0
hooks:
- id: flake8
exclude: contrib
args: ["--ignore=E266,W503,E203,E501,W605"]
- repo: https://github.com/python-poetry/poetry
rev: 1.7.0
- repo: https://github.com/haizaar/check-pipfile-lock
rev: v0.0.5
hooks:
- id: poetry-check
- id: poetry-lock
args: ["--no-update"]
- id: check-pipfile-lock
- repo: https://github.com/hadolint/hadolint
rev: v2.12.1-beta
@@ -76,23 +65,17 @@ repos:
name: pylint
entry: bash -c 'pylint --disable=W,C,R,E -j 0 -rn -sn prowler/'
language: system
files: '.*\.py'
- id: trufflehog
name: TruffleHog
description: Detect secrets in your data.
entry: bash -c 'trufflehog --no-update git file://. --only-verified --fail'
# For running trufflehog in docker, use the following entry instead:
# entry: bash -c 'docker run -v "$(pwd):/workdir" -i --rm trufflesecurity/trufflehog:latest git file:///workdir --only-verified --fail'
- id: pytest-check
name: pytest-check
entry: bash -c 'pytest tests -n auto'
language: system
stages: ["commit", "push"]
- id: bandit
name: bandit
description: "Bandit is a tool for finding common security issues in Python code"
entry: bash -c 'bandit -q -lll -x '*_test.py,./contrib/' -r .'
language: system
files: '.*\.py'
- id: safety
name: safety
@@ -105,4 +88,3 @@ repos:
description: "Vulture finds unused code in Python programs."
entry: bash -c 'vulture --exclude "contrib" --min-confidence 100 .'
language: system
files: '.*\.py'

View File

@@ -1,25 +0,0 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
build:
os: "ubuntu-22.04"
tools:
python: "3.11"
jobs:
post_create_environment:
# Install poetry
# https://python-poetry.org/docs/#installing-manually
- python -m pip install poetry
post_install:
# Install dependencies with 'docs' dependency group
# https://python-poetry.org/docs/managing-dependencies/#dependency-groups
# VIRTUAL_ENV needs to be set manually for now.
# See https://github.com/readthedocs/readthedocs.org/pull/11152/
- VIRTUAL_ENV=${READTHEDOCS_VIRTUALENV_PATH} python -m poetry install --only=docs
mkdocs:
configuration: mkdocs.yml

View File

@@ -55,7 +55,7 @@ further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at [support.prowler.com](https://customer.support.prowler.com/servicedesk/customer/portals). All
reported by contacting the project team at community@prowler.cloud. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.

View File

@@ -1,13 +0,0 @@
# Do you want to learn on how to...
- Contribute with your code or fixes to Prowler
- Create a new check for a provider
- Create a new security compliance framework
- Add a custom output format
- Add a new integration
- Contribute with documentation
Want some swag as appreciation for your contribution?
# Prowler Developer Guide
https://docs.prowler.com/projects/prowler-open-source/en/latest/developer-guide/introduction/

View File

@@ -1,10 +1,9 @@
FROM python:3.12-alpine
FROM python:3.9-alpine
LABEL maintainer="https://github.com/prowler-cloud/prowler"
# Update system dependencies
#hadolint ignore=DL3018
RUN apk --no-cache upgrade && apk --no-cache add curl
RUN apk --no-cache upgrade
# Create nonroot user
RUN mkdir -p /home/prowler && \
@@ -15,10 +14,8 @@ USER prowler
# Copy necessary files
WORKDIR /home/prowler
COPY prowler/ /home/prowler/prowler/
COPY dashboard/ /home/prowler/dashboard/
COPY prowler/ /home/prowler/prowler/
COPY pyproject.toml /home/prowler
COPY README.md /home/prowler
# Install dependencies
ENV HOME='/home/prowler'
@@ -27,13 +24,9 @@ ENV PATH="$HOME/.local/bin:$PATH"
RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir .
# Remove deprecated dash dependencies
RUN pip uninstall dash-html-components -y && \
pip uninstall dash-core-components -y
# Remove Prowler directory and build files
USER 0
RUN rm -rf /home/prowler/prowler /home/prowler/pyproject.toml /home/prowler/README.md /home/prowler/build /home/prowler/prowler.egg-info
RUN rm -rf /home/prowler/prowler /home/prowler/pyproject.toml /home/prowler/build /home/prowler/prowler_cloud.egg-info
USER prowler
ENTRYPOINT ["prowler"]

View File

@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright @ 2024 Toni de la Fuente
Copyright 2018 Netflix, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -2,19 +2,12 @@
##@ Testing
test: ## Test with pytest
rm -rf .coverage && \
pytest -n auto -vvv -s --cov=./prowler --cov-report=xml tests
pytest -n auto -vvv -s -x
coverage: ## Show Test Coverage
coverage run --skip-covered -m pytest -v && \
coverage report -m && \
rm -rf .coverage && \
coverage report -m
coverage-html: ## Show Test Coverage
rm -rf ./htmlcov && \
coverage html && \
open htmlcov/index.html
rm -rf .coverage
##@ Linting
format: ## Format Code
@@ -27,15 +20,15 @@ lint: ## Lint Code
@echo "Running black... "
black --check .
@echo "Running pylint..."
pylint --disable=W,C,R,E -j 0 prowler util
pylint --disable=W,C,R,E -j 0 providers lib util config
##@ PyPI
pypi-clean: ## Delete the distribution files
rm -rf ./dist && rm -rf ./build && rm -rf prowler.egg-info
rm -rf ./dist && rm -rf ./build && rm -rf prowler_cloud.egg-info
pypi-build: ## Build package
$(MAKE) pypi-clean && \
poetry build
python3 -m build
pypi-upload: ## Upload package
python3 -m twine upload --repository pypi dist/*

42
Pipfile Normal file
View File

@@ -0,0 +1,42 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
colorama = "0.4.4"
boto3 = "1.26.74"
arnparse = "0.0.2"
botocore = "1.29.75"
pydantic = "1.10.5"
schema = "0.7.5"
shodan = "1.28.0"
detect-secrets = "1.4.0"
alive-progress = "2.4.1"
tabulate = "0.9.0"
azure-identity = "1.12.0"
azure-storage-blob = "12.14.1"
msgraph-core = "0.2.2"
azure-mgmt-subscription = "3.1.1"
azure-mgmt-authorization = "3.0.0"
azure-mgmt-security = "3.0.0"
azure-mgmt-storage = "21.0.0"
[dev-packages]
black = "22.10.0"
pylint = "2.16.2"
flake8 = "5.0.4"
bandit = "1.7.4"
safety = "2.3.1"
vulture = "2.7"
moto = "4.1.3"
docker = "6.0.0"
openapi-spec-validator = "0.5.5"
pytest = "7.2.1"
pytest-xdist = "3.2.0"
coverage = "7.1.0"
sure = "2.0.1"
freezegun = "1.2.1"
[requires]
python_version = "3.9"

1703
Pipfile.lock generated Normal file

File diff suppressed because it is too large Load Diff

264
README.md
View File

@@ -1,31 +1,24 @@
<p align="center">
<img align="center" src="https://github.com/prowler-cloud/prowler/blob/master/docs/img/prowler-logo-black.png?raw=True#gh-light-mode-only" width="350" height="115">
<img align="center" src="https://github.com/prowler-cloud/prowler/blob/master/docs/img/prowler-logo-white.png?raw=True#gh-dark-mode-only" width="350" height="115">
<img align="center" src="https://github.com/prowler-cloud/prowler/blob/62c1ce73bbcdd6b9e5ba03dfcae26dfd165defd9/docs/img/prowler-pro-dark.png?raw=True#gh-dark-mode-only" width="150" height="36">
<img align="center" src="https://github.com/prowler-cloud/prowler/blob/62c1ce73bbcdd6b9e5ba03dfcae26dfd165defd9/docs/img/prowler-pro-light.png?raw=True#gh-light-mode-only" width="15%" height="15%">
</p>
<p align="center">
<b><i>Prowler SaaS </b> and <b>Prowler Open Source</b> are as dynamic and adaptable as the environment theyre meant to protect. Trusted by the leaders in security.
<b><i>See all the things you and your team can do with ProwlerPro at <a href="https://prowler.pro">prowler.pro</a></i></b>
</p>
<p align="center">
<b>Learn more at <a href="https://prowler.com">prowler.com</i></b>
</p>
<p align="center">
<a href="https://join.slack.com/t/prowler-workspace/shared_invite/zt-1hix76xsl-2uq222JIXrC7Q8It~9ZNog"><img width="30" height="30" alt="Prowler community on Slack" src="https://github.com/prowler-cloud/prowler/assets/3985464/3617e470-670c-47c9-9794-ce895ebdb627"></a>
<br>
<a href="https://join.slack.com/t/prowler-workspace/shared_invite/zt-1hix76xsl-2uq222JIXrC7Q8It~9ZNog">Join our Prowler community!</a>
</p>
<hr>
<p align="center">
<img src="https://user-images.githubusercontent.com/3985464/113734260-7ba06900-96fb-11eb-82bc-d4f68a1e2710.png" />
</p>
<p align="center">
<a href="https://join.slack.com/t/prowler-workspace/shared_invite/zt-1hix76xsl-2uq222JIXrC7Q8It~9ZNog"><img alt="Slack Shield" src="https://img.shields.io/badge/slack-prowler-brightgreen.svg?logo=slack"></a>
<a href="https://pypi.org/project/prowler/"><img alt="Python Version" src="https://img.shields.io/pypi/v/prowler.svg"></a>
<a href="https://pypi.python.org/pypi/prowler/"><img alt="Python Version" src="https://img.shields.io/pypi/pyversions/prowler.svg"></a>
<a href="https://pypistats.org/packages/prowler"><img alt="PyPI Prowler Downloads" src="https://img.shields.io/pypi/dw/prowler.svg?label=prowler%20downloads"></a>
<a href="https://pypi.org/project/prowler-cloud/"><img alt="Python Version" src="https://img.shields.io/pypi/v/prowler.svg"></a>
<a href="https://pypi.python.org/pypi/prowler-cloud/"><img alt="Python Version" src="https://img.shields.io/pypi/pyversions/prowler.svg"></a>
<a href="https://pypistats.org/packages/prowler"><img alt="PyPI Prowler Downloads" src="https://img.shields.io/pypi/dw/prowler.svg"></a>
<a href="https://pypistats.org/packages/prowler-cloud"><img alt="PyPI Prowler-Cloud Downloads" src="https://img.shields.io/pypi/dw/prowler-cloud.svg"></a>
<a href="https://hub.docker.com/r/toniblyx/prowler"><img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/toniblyx/prowler"></a>
<a href="https://hub.docker.com/r/toniblyx/prowler"><img alt="Docker" src="https://img.shields.io/docker/cloud/build/toniblyx/prowler"></a>
<a href="https://hub.docker.com/r/toniblyx/prowler"><img alt="Docker" src="https://img.shields.io/docker/image-size/toniblyx/prowler"></a>
<a href="https://gallery.ecr.aws/prowler-cloud/prowler"><img width="120" height=19" alt="AWS ECR Gallery" src="https://user-images.githubusercontent.com/3985464/151531396-b6535a68-c907-44eb-95a1-a09508178616.png"></a>
<a href="https://codecov.io/gh/prowler-cloud/prowler"><img src="https://codecov.io/gh/prowler-cloud/prowler/graph/badge.svg?token=OflBGsdpDl"/></a>
<a href="https://gallery.ecr.aws/o4g1s5r6/prowler"><img width="120" height=19" alt="AWS ECR Gallery" src="https://user-images.githubusercontent.com/3985464/151531396-b6535a68-c907-44eb-95a1-a09508178616.png"></a>
</p>
<p align="center">
<a href="https://github.com/prowler-cloud/prowler"><img alt="Repo size" src="https://img.shields.io/github/repo-size/prowler-cloud/prowler"></a>
@@ -37,99 +30,218 @@
<a href="https://twitter.com/ToniBlyx"><img alt="Twitter" src="https://img.shields.io/twitter/follow/toniblyx?style=social"></a>
<a href="https://twitter.com/prowlercloud"><img alt="Twitter" src="https://img.shields.io/twitter/follow/prowlercloud?style=social"></a>
</p>
<hr>
# Description
**Prowler** is an Open Source security tool to perform AWS, Azure, Google Cloud and Kubernetes security best practices assessments, audits, incident response, continuous monitoring, hardening and forensics readiness, and also remediations! We have Prowler CLI (Command Line Interface) that we call Prowler Open Source and a service on top of it that we call <a href="https://prowler.com">Prowler SaaS</a>.
`Prowler` is an Open Source security tool to perform AWS and Azure security best practices assessments, audits, incident response, continuous monitoring, hardening and forensics readiness.
## Prowler CLI
It contains hundreds of controls covering CIS, PCI-DSS, ISO27001, GDPR, HIPAA, FFIEC, SOC2, AWS FTR, ENS and custom security frameworks.
```console
prowler <provider>
```
![Prowler CLI Execution](docs/img/short-display.png)
## Prowler Dashboard
```console
prowler dashboard
```
![Prowler Dashboard](docs/img/dashboard.png)
It contains hundreds of controls covering CIS, NIST 800, NIST CSF, CISA, RBI, FedRAMP, PCI-DSS, GDPR, HIPAA, FFIEC, SOC2, GXP, AWS Well-Architected Framework Security Pillar, AWS Foundational Technical Review (FTR), ENS (Spanish National Security Scheme) and your custom security frameworks.
| 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 | 304 | 61 -> `prowler aws --list-services` | 28 -> `prowler aws --list-compliance` | 6 -> `prowler aws --list-categories` |
| GCP | 75 | 11 -> `prowler gcp --list-services` | 1 -> `prowler gcp --list-compliance` | 2 -> `prowler gcp --list-categories`|
| Azure | 127 | 16 -> `prowler azure --list-services` | 2 -> `prowler azure --list-compliance` | 2 -> `prowler azure --list-categories` |
| Kubernetes | 83 | 7 -> `prowler kubernetes --list-services` | 1 -> `prowler kubernetes --list-compliance` | 7 -> `prowler kubernetes --list-categories` |
# 💻 Installation
## Looking for Prowler v2 documentation?
For Prowler v2 Documentation, please go to https://github.com/prowler-cloud/prowler/tree/2.12.1.
# ⚙️ Install
## Pip package
Prowler 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:
Prowler is available as a project in [PyPI](https://pypi.org/project/prowler-cloud/), thus can be installed using pip with Python >= 3.9:
```console
pip install prowler
prowler -v
```
More details at [https://docs.prowler.com](https://docs.prowler.com/projects/prowler-open-source/en/latest/)
## Containers
The available versions of Prowler are the following:
- `latest`: in sync with `master` branch (bear in mind that it is not a stable version)
- `v3-latest`: in sync with `v3` branch (bear in mind that it is not a stable version)
- `latest`: in sync with master branch (bear in mind that it is not a stable version)
- `<x.y.z>` (release): you can find the releases [here](https://github.com/prowler-cloud/prowler/releases), those are stable releases.
- `stable`: this tag always point to the latest release.
- `v3-stable`: this tag always point to the latest release for v3.
The container images are available here:
- [DockerHub](https://hub.docker.com/r/toniblyx/prowler/tags)
- [AWS Public ECR](https://gallery.ecr.aws/prowler-cloud/prowler)
- [AWS Public ECR](https://gallery.ecr.aws/o4g1s5r6/prowler)
## From Github
Python >= 3.9, < 3.13 is required with pip and poetry:
Python >= 3.9 is required with pip and pipenv:
```
git clone https://github.com/prowler-cloud/prowler
cd prowler
poetry shell
poetry install
pipenv shell
pipenv install
python prowler.py -v
```
# 📐✏️ High level architecture
You can run Prowler from your workstation, a Kubernetes Job, a Google Compute Engine, an Azure VM, an EC2 instance, Fargate or any other container, CloudShell and many more.
![Architecture](docs/img/architecture.png)
# Deprecations from v3
## General
- `Allowlist` now is called `Mutelist`.
- The `--quiet` option has been deprecated, now use the `--status` flag to select the finding's status you want to get from PASS, FAIL or MANUAL.
- All `INFO` finding's status has changed to `MANUAL`.
- The CSV output format is common for all the providers.
We have deprecated some of our outputs formats:
- The HTML is replaced for the new Prowler Dashboard, run `prowler dashboard`.
- The native JSON is replaced for the JSON [OCSF](https://schema.ocsf.io/) v1.1.0, common for all the providers.
## AWS
- Deprecate the AWS flag --sts-endpoint-region since we use AWS STS regional tokens.
- To send only FAILS to AWS Security Hub, now use either `--send-sh-only-fails` or `--security-hub --status FAIL`.
# 📖 Documentation
Install, Usage, Tutorials and Developer Guide is at https://docs.prowler.com/
The full documentation can now be found at [https://docs.prowler.cloud](https://docs.prowler.cloud)
# 📐✏️ High level architecture
You can run Prowler from your workstation, an EC2 instance, Fargate or any other container, Codebuild, CloudShell and Cloud9.
![Architecture](https://github.com/prowler-cloud/prowler/blob/62c1ce73bbcdd6b9e5ba03dfcae26dfd165defd9/docs/img/architecture.png?raw=True)
# 📝 Requirements
Prowler has been written in Python using the [AWS SDK (Boto3)](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html#) and [Azure SDK](https://azure.github.io/azure-sdk-for-python/).
## AWS
Since Prowler uses AWS Credentials under the hood, you can follow any authentication method as described [here](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html#cli-configure-quickstart-precedence).
Make sure you have properly configured your AWS-CLI with a valid Access Key and Region or declare AWS variables properly (or instance profile/role):
```console
aws configure
```
or
```console
export AWS_ACCESS_KEY_ID="ASXXXXXXX"
export AWS_SECRET_ACCESS_KEY="XXXXXXXXX"
export AWS_SESSION_TOKEN="XXXXXXXXX"
```
Those credentials must be associated to a user or role with proper permissions to do all checks. To make sure, add the following AWS managed policies to the user or role being used:
- arn:aws:iam::aws:policy/SecurityAudit
- arn:aws:iam::aws:policy/job-function/ViewOnlyAccess
> Moreover, some read-only additional permissions are needed for several checks, make sure you attach also the custom policy [prowler-additions-policy.json](https://github.com/prowler-cloud/prowler/blob/master/permissions/prowler-additions-policy.json) to the role you are using.
> If you want Prowler to send findings to [AWS Security Hub](https://aws.amazon.com/security-hub), make sure you also attach the custom policy [prowler-security-hub.json](https://github.com/prowler-cloud/prowler/blob/master/permissions/prowler-security-hub.json).
## Azure
Prowler for Azure supports the following authentication types:
- Service principal authentication by environment variables (Enterprise Application)
- Current az cli credentials stored
- Interactive browser authentication
- Managed identity authentication
### 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_TENANT_ID="XXXXXXXXX"
export AZURE_CLIENT_SECRET="XXXXXXX"
```
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.
### AZ CLI / Browser / Managed Identity authentication
The other three cases do not need additional configuration, `--az-cli-auth` and `--managed-identity-auth` are automated options, `--browser-auth` needs the user to authenticate using the default browser to start the scan.
### Permissions
To use each one, you need to pass the proper flag to the execution. Prowler for Azure handles two types of permission scopes, which are:
- **Azure Active Directory permissions**: Used to retrieve metadata from the identity assumed by Prowler and future AAD checks (not mandatory to have access to execute the tool)
- **Subscription scope permissions**: Required to launch the checks against your resources, mandatory to launch the tool.
#### Azure Active Directory scope
Azure Active Directory (AAD) permissions required by the tool are the following:
- `Directory.Read.All`
- `Policy.Read.All`
#### Subscriptions scope
Regarding the subscription scope, Prowler by default scans all the subscriptions that is able to list, so it is required to add the following RBAC builtin roles per subscription to the entity that is going to be assumed by the tool:
- `Security Reader`
- `Reader`
# 💻 Basic Usage
To run prowler, you will need to specify the provider (e.g aws or azure):
```console
prowler <provider>
```
![Prowler Execution](https://github.com/prowler-cloud/prowler/blob/b91b0103ff38e66a915c8a0ed84905a07e4aae1d/docs/img/short-display.png?raw=True)
> Running the `prowler` command without options will use your environment variable credentials.
By default, prowler will generate a CSV, a JSON and a HTML report, however you can generate JSON-ASFF (only for AWS Security Hub) report with `-M` or `--output-modes`:
```console
prowler <provider> -M csv json json-asff html
```
The html report will be located in the `output` directory as the other files and it will look like:
![Prowler Execution](https://github.com/prowler-cloud/prowler/blob/62c1ce73bbcdd6b9e5ba03dfcae26dfd165defd9/docs/img/html-output.png?raw=True)
You can use `-l`/`--list-checks` or `--list-services` to list all available checks or services within the provider.
```console
prowler <provider> --list-checks
prowler <provider> --list-services
```
For executing specific checks or services you can use options `-c`/`--checks` or `-s`/`--services`:
```console
prowler aws --checks s3_bucket_public_access
prowler aws --services s3 ec2
```
Also, checks and services can be excluded with options `-e`/`--excluded-checks` or `--excluded-services`:
```console
prowler aws --excluded-checks s3_bucket_public_access
prowler aws --excluded-services s3 ec2
```
You can always use `-h`/`--help` to access to the usage information and all the possible options:
```console
prowler -h
```
## Checks Configurations
Several Prowler's checks have user configurable variables that can be modified in a common **configuration file**.
This file can be found in the following path:
```
prowler/config/config.yaml
```
## AWS
Use a custom AWS profile with `-p`/`--profile` and/or AWS regions which you want to audit with `-f`/`--filter-region`:
```console
prowler aws --profile custom-profile -f us-east-1 eu-south-2
```
> By default, `prowler` will scan all AWS regions.
## Azure
With Azure you need to specify which auth method is going to be used:
```console
prowler azure [--sp-env-auth, --az-cli-auth, --browser-auth, --managed-identity-auth]
```
> By default, `prowler` will scan all Azure subscriptions.
# 🎉 New Features
- Python: we got rid of all bash and it is now all in Python.
- Faster: huge performance improvements (same account from 2.5 hours to 4 minutes).
- Developers and community: we have made it easier to contribute with new checks and new compliance frameworks. We also included unit tests.
- Multi-cloud: in addition to AWS, we have added Azure, we plan to include GCP and OCI soon, let us know if you want to contribute!
# 📃 License

View File

@@ -14,7 +14,7 @@ As an **AWS Partner** and we have passed the [AWS Foundation Technical Review (F
If you would like to report a vulnerability or have a security concern regarding Prowler Open Source or ProwlerPro service, please submit the information by contacting to help@prowler.pro.
The information you share with ProwlerPro as part of this process is kept confidential within ProwlerPro. We will only share this information with a third party if the vulnerability you report is found to affect a third-party product, in which case we will share this information with the third-party product's author or manufacturer. Otherwise, we will only share this information as permitted by you.
The information you share with Verica as part of this process is kept confidential within Verica and the Prowler team. We will only share this information with a third party if the vulnerability you report is found to affect a third-party product, in which case we will share this information with the third-party product's author or manufacturer. Otherwise, we will only share this information as permitted by you.
We will review the submitted report, and assign it a tracking number. We will then respond to you, acknowledging receipt of the report, and outline the next steps in the process.

View File

@@ -1,8 +1,17 @@
#!/bin/bash
sudo bash
adduser prowler
su prowler
pip install prowler
cd /tmp
prowler aws
# Install system dependencies
sudo yum -y install openssl-devel bzip2-devel libffi-devel gcc
# Upgrade to Python 3.9
cd /tmp && wget https://www.python.org/ftp/python/3.9.13/Python-3.9.13.tgz
tar zxf Python-3.9.13.tgz
cd Python-3.9.13/ || exit
./configure --enable-optimizations
sudo make altinstall
python3.9 --version
# Install Prowler
cd ~ || exit
python3.9 -m pip install prowler-cloud
prowler -v
# Run Prowler
prowler

View File

@@ -1,24 +1,45 @@
# Build command
# docker build --platform=linux/amd64 --no-cache -t prowler:latest .
ARG PROWLER_VERSION=latest
FROM public.ecr.aws/amazonlinux/amazonlinux:2022
FROM toniblyx/prowler:${PROWLER_VERSION}
ARG PROWLERVER=2.9.0
ARG USERNAME=prowler
ARG USERID=34000
USER 0
# hadolint ignore=DL3018
RUN apk --no-cache add bash aws-cli jq
# Install Dependencies
RUN \
dnf update -y && \
dnf install -y bash file findutils git jq python3 python3-pip \
python3-setuptools python3-wheel shadow-utils tar unzip which && \
dnf remove -y awscli && \
dnf clean all && \
useradd -l -s /bin/sh -U -u ${USERID} ${USERNAME} && \
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \
unzip awscliv2.zip && \
./aws/install && \
pip3 install --no-cache-dir --upgrade pip && \
pip3 install --no-cache-dir "git+https://github.com/ibm/detect-secrets.git@master#egg=detect-secrets" && \
rm -rf aws awscliv2.zip /var/cache/dnf
ARG MULTI_ACCOUNT_SECURITY_HUB_PATH=/home/prowler/multi-account-securityhub
# Place script and env vars
COPY .awsvariables run-prowler-securityhub.sh /
USER prowler
# Installs prowler and change permissions
RUN \
curl -L "https://github.com/prowler-cloud/prowler/archive/refs/tags/${PROWLERVER}.tar.gz" -o "prowler.tar.gz" && \
tar xvzf prowler.tar.gz && \
rm -f prowler.tar.gz && \
mv prowler-${PROWLERVER} prowler && \
chown ${USERNAME}:${USERNAME} /run-prowler-securityhub.sh && \
chmod 500 /run-prowler-securityhub.sh && \
chown ${USERNAME}:${USERNAME} /.awsvariables && \
chmod 400 /.awsvariables && \
chown ${USERNAME}:${USERNAME} -R /prowler && \
chmod +x /prowler/prowler
# Move script and environment variables
RUN mkdir "${MULTI_ACCOUNT_SECURITY_HUB_PATH}"
COPY --chown=prowler:prowler .awsvariables run-prowler-securityhub.sh "${MULTI_ACCOUNT_SECURITY_HUB_PATH}"/
RUN chmod 500 "${MULTI_ACCOUNT_SECURITY_HUB_PATH}"/run-prowler-securityhub.sh & \
chmod 400 "${MULTI_ACCOUNT_SECURITY_HUB_PATH}"/.awsvariables
# Drop to user
USER ${USERNAME}
WORKDIR ${MULTI_ACCOUNT_SECURITY_HUB_PATH}
ENTRYPOINT ["./run-prowler-securityhub.sh"]
# Run script
ENTRYPOINT ["/run-prowler-securityhub.sh"]

View File

@@ -1,17 +1,20 @@
#!/bin/bash
# Run Prowler against All AWS Accounts in an AWS Organization
# Change Directory (rest of the script, assumes you're in the root directory)
cd / || exit
# Show Prowler Version
prowler -v
./prowler/prowler -V
# Source .awsvariables
# shellcheck disable=SC1091
source .awsvariables
# Get Values from Environment Variables
echo "ROLE: ${ROLE}"
echo "PARALLEL_ACCOUNTS: ${PARALLEL_ACCOUNTS}"
echo "REGION: ${REGION}"
echo "ROLE: $ROLE"
echo "PARALLEL_ACCOUNTS: $PARALLEL_ACCOUNTS"
echo "REGION: $REGION"
# Function to unset AWS Profile Variables
unset_aws() {
@@ -21,33 +24,33 @@ unset_aws
# Find THIS Account AWS Number
CALLER_ARN=$(aws sts get-caller-identity --output text --query "Arn")
PARTITION=$(echo "${CALLER_ARN}" | cut -d: -f2)
THISACCOUNT=$(echo "${CALLER_ARN}" | cut -d: -f5)
echo "THISACCOUNT: ${THISACCOUNT}"
echo "PARTITION: ${PARTITION}"
PARTITION=$(echo "$CALLER_ARN" | cut -d: -f2)
THISACCOUNT=$(echo "$CALLER_ARN" | cut -d: -f5)
echo "THISACCOUNT: $THISACCOUNT"
echo "PARTITION: $PARTITION"
# Function to Assume Role to THIS Account & Create Session
this_account_session() {
unset_aws
role_credentials=$(aws sts assume-role --role-arn arn:"${PARTITION}":iam::"${THISACCOUNT}":role/"${ROLE}" --role-session-name ProwlerRun --output json)
AWS_ACCESS_KEY_ID=$(echo "${role_credentials}" | jq -r .Credentials.AccessKeyId)
AWS_SECRET_ACCESS_KEY=$(echo "${role_credentials}" | jq -r .Credentials.SecretAccessKey)
AWS_SESSION_TOKEN=$(echo "${role_credentials}" | jq -r .Credentials.SessionToken)
role_credentials=$(aws sts assume-role --role-arn arn:"$PARTITION":iam::"$THISACCOUNT":role/"$ROLE" --role-session-name ProwlerRun --output json)
AWS_ACCESS_KEY_ID=$(echo "$role_credentials" | jq -r .Credentials.AccessKeyId)
AWS_SECRET_ACCESS_KEY=$(echo "$role_credentials" | jq -r .Credentials.SecretAccessKey)
AWS_SESSION_TOKEN=$(echo "$role_credentials" | jq -r .Credentials.SessionToken)
export AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
}
# Find AWS Master Account
this_account_session
AWSMASTER=$(aws organizations describe-organization --query Organization.MasterAccountId --output text)
echo "AWSMASTER: ${AWSMASTER}"
echo "AWSMASTER: $AWSMASTER"
# Function to Assume Role to Master Account & Create Session
master_account_session() {
unset_aws
role_credentials=$(aws sts assume-role --role-arn arn:"${PARTITION}":iam::"${AWSMASTER}":role/"${ROLE}" --role-session-name ProwlerRun --output json)
AWS_ACCESS_KEY_ID=$(echo "${role_credentials}" | jq -r .Credentials.AccessKeyId)
AWS_SECRET_ACCESS_KEY=$(echo "${role_credentials}" | jq -r .Credentials.SecretAccessKey)
AWS_SESSION_TOKEN=$(echo "${role_credentials}" | jq -r .Credentials.SessionToken)
role_credentials=$(aws sts assume-role --role-arn arn:"$PARTITION":iam::"$AWSMASTER":role/"$ROLE" --role-session-name ProwlerRun --output json)
AWS_ACCESS_KEY_ID=$(echo "$role_credentials" | jq -r .Credentials.AccessKeyId)
AWS_SECRET_ACCESS_KEY=$(echo "$role_credentials" | jq -r .Credentials.SecretAccessKey)
AWS_SESSION_TOKEN=$(echo "$role_credentials" | jq -r .Credentials.SessionToken)
export AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
}
@@ -57,20 +60,20 @@ ACCOUNTS_IN_ORGS=$(aws organizations list-accounts --query Accounts[*].Id --outp
# Run Prowler against Accounts in AWS Organization
echo "AWS Accounts in Organization"
echo "${ACCOUNTS_IN_ORGS}"
for accountId in ${ACCOUNTS_IN_ORGS}; do
echo "$ACCOUNTS_IN_ORGS"
for accountId in $ACCOUNTS_IN_ORGS; do
# shellcheck disable=SC2015
test "$(jobs | wc -l)" -ge "${PARALLEL_ACCOUNTS}" && wait -n || true
test "$(jobs | wc -l)" -ge $PARALLEL_ACCOUNTS && wait -n || true
{
START_TIME=${SECONDS}
START_TIME=$SECONDS
# Unset AWS Profile Variables
unset_aws
# Run Prowler
echo -e "Assessing AWS Account: ${accountId}, using Role: ${ROLE} on $(date)"
echo -e "Assessing AWS Account: $accountId, using Role: $ROLE on $(date)"
# Pipe stdout to /dev/null to reduce unnecessary Cloudwatch logs
prowler aws -R arn:"${PARTITION}":iam::"${accountId}":role/"${ROLE}" -q -S -f "${REGION}" > /dev/null
./prowler/prowler -R "$ROLE" -A "$accountId" -M json-asff -q -S -f "$REGION" > /dev/null
TOTAL_SEC=$((SECONDS - START_TIME))
printf "Completed AWS Account: ${accountId} in %02dh:%02dm:%02ds" $((TOTAL_SEC / 3600)) $((TOTAL_SEC % 3600 / 60)) $((TOTAL_SEC % 60))
printf "Completed AWS Account: $accountId in %02dh:%02dm:%02ds" $((TOTAL_SEC / 3600)) $((TOTAL_SEC % 3600 / 60)) $((TOTAL_SEC % 60))
echo ""
} &
done

View File

@@ -1,6 +1,6 @@
# Organizational Prowler with Serverless
Language: [Korean](README_kr.md)
Langage: [Korean](README_kr.md)
This project is created to apply prowler in a multi-account environment within AWS Organizations.
CloudWatch triggers CodeBuild every fixed time.
@@ -18,12 +18,12 @@ For more information on how to use prowler, see [here](https://github.com/prowle
2. **Master Account**
1. Deploy [ProwlerRole.yaml](templates/ProwlerRole.yaml) stack to CloudFormation in a bid to create resources to master account itself.
(The template will be also deployed for other member accounts as a StackSet)
- ProwlerCodeBuildAccount : Audit Account ID where CodeBuild resides. (preferably Audit/Security account)
- ProwlerCodeBuildAccount : Audit Acccount ID where CodeBuild resides. (preferably Audit/Security account)
- ProwlerCodeBuildRole : Role name to use in CodeBuild service
- ProwlerCrossAccountRole : Role name to assume for Cross account
- ProwlerS3 : The S3 bucket name where reports will be put
1. Create **StackSet** with [ProwlerRole.yaml](templates/ProwlerRole.yaml) to deploy Role into member accounts in AWS Organizations.
- ProwlerCodeBuildAccount : Audit Account ID where CodeBuild resides. (preferably Audit/Security account)
- ProwlerCodeBuildAccount : Audit Acccount ID where CodeBuild resides. (preferably Audit/Security account)
- ProwlerCodeBuildRole : Role name to use in CodeBuild service
- ProwlerCrossAccountRole : Role name to assume for Cross account
- ProwlerS3 : The S3 bucket name where reports will be put
@@ -45,4 +45,4 @@ For more information on how to use prowler, see [here](https://github.com/prowle
- ProwlerReportS3Account : The account where the report S3 bucket resides.
1. If you'd like to change the scheduled time,
1. You can change the cron expression of ScheduleExpression within [ProwlerCodeBuildStack.yaml](templates/ProwlerCodeBuildStack.yaml).
2. Alternatively, you can make changes directly from Events > Rules > ProwlerExecuteRule > Actions > Edit in CloudWatch console.
2. Alternatively, you can make changes directrly from Events > Rules > ProwlerExecuteRule > Actions > Edit in CloudWatch console.

View File

@@ -1,6 +1,6 @@
# Organizational Prowler with Serverless
Language: [English](README.md)
Langage: [English](README.md)
이 문서는 AWS Organization 내의 multi account 환경에서 prowler 를 적용하기 위해 작성된 문서입니다.
일정 시간마다 CloudWatch는 CodeBuild 를 트리거합니다.
@@ -22,7 +22,7 @@ prowler 의 자세한 사용방법은 [이 곳](https://github.com/prowler-cloud
[ProwlerRole.yaml](templates/ProwlerRole.yaml)
- ProwlerCodeBuildAccount : CodeBuild 가 있는 Audit Account ID
- ProwlerCodeBuildAccount : CodeBuild 가 있는 Audit Acccount ID
- ProwlerCodeBuildRole : CodeBuild의 생성될 Role 이름
- ProwlerCrossAccountRole : Cross account 용 Assume할 Role 이름
- ProwlerS3 : report 가 저장될 S3 bucket 명
@@ -30,7 +30,7 @@ prowler 의 자세한 사용방법은 [이 곳](https://github.com/prowler-cloud
[ProwlerRole.yaml](templates/ProwlerRole.yaml)
- ProwlerCodeBuildAccount : CodeBuild 가 있는 Audit Account
- ProwlerCodeBuildAccount : CodeBuild 가 있는 Audit Acccount
- ProwlerCodeBuildRole : CodeBuild에서 사용할 Role 이름
- ProwlerCrossAccountRole : Cross account 용 Assume할 Role 이름
- ProwlerS3 : report 가 저장될 S3 bucket 명

View File

@@ -2,7 +2,7 @@
## Introduction
The following demonstrates how to quickly install the resources necessary to perform a security baseline using Prowler. The speed is based on the prebuilt terraform module that can configure all the resources necessary to run Prowler with the findings being sent to AWS Security Hub.
The following demonstartes how to quickly install the resources necessary to perform a security baseline using Prowler. The speed is based on the prebuilt terraform module that can configure all the resources necessuary to run Prowler with the findings being sent to AWS Security Hub.
## Install
@@ -24,7 +24,7 @@ Installing Prowler with Terraform is simple and can be completed in under 1 minu
![Prowler Install](https://prowler-docs.s3.amazonaws.com/Prowler-Terraform-Install.gif)
- It is likely an error will return related to the SecurityHub subscription. This appears to be Terraform related and you can validate the configuration by navigating to the SecurityHub console. Click Integrations and search for Prowler. Take note of the green check where it says *Accepting findings*
- It is likely an error will return related to the SecurityHub subscription. This appears to be Terraform related and you can validate the configuration by navigating to the SecurityHub console. Click Integreations and search for Prowler. Take note of the green check where it says *Accepting findings*
![Prowler Subscription](https://prowler-docs.s3.amazonaws.com/Validate-Prowler-Subscription.gif)

View File

@@ -92,7 +92,7 @@ To make sure rules are working fine, run `/var/ossec/bin/ossec-logtest` and copy
```
You must see 3 phases goin on.
To check if there is any error you can enable the debug mode of `modulesd` setting the `wazuh_modules.debug=0` variable to 2 in `/var/ossec/etc/internal_options.conf` file. Restart wazuh-manager and errors should appear in the `/var/ossec/logs/ossec.log` file.
To check if there is any error you can enable the debug mode of `modulesd` setting the `wazuh_modules.debug=0` variable to 2 in `/var/ossec/etc/internal_options.conf` file. Restart wazun-manager and errors should appear in the `/var/ossec/logs/ossec.log` file.
## Thanks

View File

@@ -1,2 +0,0 @@
DASHBOARD_PORT = 11666
DASHBOARD_ARGS = {"debug": True, "port": DASHBOARD_PORT, "use_reloader": False}

View File

@@ -1,176 +0,0 @@
# Importing Packages
import sys
import warnings
import click
import dash
import dash_bootstrap_components as dbc
from colorama import Fore, Style
from dash import dcc, html
from dash.dependencies import Input, Output
from dashboard.config import folder_path_overview
from prowler.config.config import orange_color
from prowler.lib.banner import print_banner
warnings.filterwarnings("ignore")
cli = sys.modules["flask.cli"]
print_banner(verbose=False)
print(
f"{Fore.GREEN}Loading all CSV files from the folder {folder_path_overview} ...\n{Style.RESET_ALL}"
)
cli.show_server_banner = lambda *x: click.echo(
f"{Fore.YELLOW}NOTE:{Style.RESET_ALL} If you are a {Fore.GREEN}{Style.BRIGHT}Prowler SaaS{Style.RESET_ALL} customer and you want to use your data from your S3 bucket,\nrun: `{orange_color}aws s3 cp s3://<your-bucket>/output/csv ./output --recursive{Style.RESET_ALL}`\nand then run `prowler dashboard` again to load the new files."
)
# Initialize the app - incorporate css
dashboard = dash.Dash(
__name__,
external_stylesheets=[dbc.themes.DARKLY],
use_pages=True,
suppress_callback_exceptions=True,
title="Prowler Dashboard",
)
# Logo
prowler_logo = html.Img(
src="https://prowler.com/wp-content/uploads/logo-dashboard.png", alt="Prowler Logo"
)
menu_icons = {
"overview": "/assets/images/icons/overview.svg",
"compliance": "/assets/images/icons/compliance.svg",
}
# Function to generate navigation links
def generate_nav_links(current_path):
nav_links = []
for page in dash.page_registry.values():
# Gets the icon URL based on the page name
icon_url = menu_icons.get(page["name"].lower())
is_active = (
" bg-prowler-stone-950 border-r-4 border-solid border-prowler-lime"
if current_path == page["relative_path"]
else ""
)
link_class = f"block hover:bg-prowler-stone-950 hover:border-r-4 hover:border-solid hover:border-prowler-lime{is_active}"
link_content = html.Span(
[
html.Img(src=icon_url, className="w-5"),
html.Span(page["name"], className="font-medium text-base leading-6"),
],
className="flex justify-center lg:justify-normal items-center gap-x-3 py-2 px-3",
)
nav_link = html.Li(
dcc.Link(link_content, href=page["relative_path"], className=link_class)
)
nav_links.append(nav_link)
return nav_links
def generate_help_menu():
help_links = [
{
"title": "Help",
"url": "https://github.com/prowler-cloud/prowler/issues",
"icon": "/assets/images/icons/help.png",
},
{
"title": "Docs",
"url": "https://docs.prowler.com",
"icon": "/assets/images/icons/docs.png",
},
]
link_class = "block hover:bg-prowler-stone-950 hover:border-r-4 hover:border-solid hover:border-prowler-lime"
menu_items = []
for link in help_links:
menu_item = html.Li(
html.A(
html.Span(
[
html.Img(src=link["icon"], className="w-5"),
html.Span(
link["title"], className="font-medium text-base leading-6"
),
],
className="flex items-center gap-x-3 py-2 px-3",
),
href=link["url"],
target="_blank",
className=link_class,
)
)
menu_items.append(menu_item)
return menu_items
# Layout
dashboard.layout = html.Div(
[
dcc.Location(id="url", refresh=False),
html.Link(rel="icon", href="assets/favicon.ico"),
# Placeholder for dynamic navigation bar
html.Div(
[
html.Div(
id="navigation-bar", className="bg-prowler-stone-900 min-w-36 z-10"
),
html.Div(
[
dash.page_container,
],
id="content_select",
className="bg-prowler-white w-full col-span-11 h-screen mx-auto overflow-y-scroll no-scrollbar px-10 py-7",
),
],
className="grid custom-grid 2xl:custom-grid-large h-screen",
),
],
className="h-screen mx-auto",
)
# Callback to update navigation bar
@dashboard.callback(Output("navigation-bar", "children"), [Input("url", "pathname")])
def update_nav_bar(pathname):
return html.Div(
[
html.Div([prowler_logo], className="mb-8 px-3"),
html.H6(
"Dashboards",
className="px-3 text-prowler-stone-500 text-sm opacity-90 font-regular mb-2",
),
html.Nav(
[html.Ul(generate_nav_links(pathname), className="")],
className="flex flex-col gap-y-6",
),
html.Nav(
[
html.A(
[
html.Span(
[
html.Img(src="assets/favicon.ico", className="w-5"),
"Subscribe to prowler SaaS",
],
className="flex items-center gap-x-3",
),
],
href="https://prowler.com/",
target="_blank",
className="block p-3 uppercase text-xs hover:bg-prowler-stone-950 hover:border-r-4 hover:border-solid hover:border-prowler-lime",
),
html.Ul(generate_help_menu(), className=""),
],
className="flex flex-col gap-y-6 mt-auto",
),
],
className="flex flex-col bg-prowler-stone-900 py-7 h-full",
)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="#FFF" aria-hidden="true" class="h-5 w-5" viewBox="0 0 24 24">
<path fill-rule="evenodd" d="M9 1.5H5.625c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0 0 16.5 9h-1.875a1.875 1.875 0 0 1-1.875-1.875V5.25A3.75 3.75 0 0 0 9 1.5zm6.61 10.936a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 14.47a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25z" clip-rule="evenodd"/>
<path d="M12.971 1.816A5.23 5.23 0 0 1 14.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 0 1 3.434 1.279 9.768 9.768 0 0 0-6.963-6.963z"/>
</svg>

Before

Width:  |  Height:  |  Size: 650 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 734 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 441 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 934 B

View File

@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="#FFF" aria-hidden="true" class="h-5 w-5" viewBox="0 0 24 24">
<path fill-rule="evenodd" d="M2.25 13.5a8.25 8.25 0 0 1 8.25-8.25.75.75 0 0 1 .75.75v6.75H18a.75.75 0 0 1 .75.75 8.25 8.25 0 0 1-16.5 0z" clip-rule="evenodd"/>
<path fill-rule="evenodd" d="M12.75 3a.75.75 0 0 1 .75-.75 8.25 8.25 0 0 1 8.25 8.25.75.75 0 0 1-.75.75h-7.5a.75.75 0 0 1-.75-.75V3z" clip-rule="evenodd"/>
</svg>

Before

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,23 +0,0 @@
import warnings
from dashboard.common_methods import get_section_containers_format2
warnings.filterwarnings("ignore")
def get_table(data):
aux = data[
[
"REQUIREMENTS_ID",
"REQUIREMENTS_ATTRIBUTES_SECTION",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
].copy()
return get_section_containers_format2(
aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ID"
)

View File

@@ -1,23 +0,0 @@
import warnings
from dashboard.common_methods import get_section_containers_format1
warnings.filterwarnings("ignore")
def get_table(data):
aux = data[
[
"REQUIREMENTS_ID",
"REQUIREMENTS_ATTRIBUTES_SECTION",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
].copy()
return get_section_containers_format1(
aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ID"
)

View File

@@ -1,23 +0,0 @@
import warnings
from dashboard.common_methods import get_section_containers_format1
warnings.filterwarnings("ignore")
def get_table(data):
aux = data[
[
"REQUIREMENTS_ID",
"REQUIREMENTS_ATTRIBUTES_SECTION",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
].copy()
return get_section_containers_format1(
aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ID"
)

View File

@@ -1,22 +0,0 @@
import warnings
from dashboard.common_methods import get_section_containers_format2
warnings.filterwarnings("ignore")
def get_table(data):
aux = data[
[
"REQUIREMENTS_ATTRIBUTES_NAME",
"REQUIREMENTS_ATTRIBUTES_SECTION",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
]
return get_section_containers_format2(
aux, "REQUIREMENTS_ATTRIBUTES_NAME", "REQUIREMENTS_ATTRIBUTES_SECTION"
)

View File

@@ -1,23 +0,0 @@
import warnings
from dashboard.common_methods import get_section_containers_format2
warnings.filterwarnings("ignore")
def get_table(data):
aux = data[
[
"REQUIREMENTS_ATTRIBUTES_NAME",
"REQUIREMENTS_ATTRIBUTES_SECTION",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
]
return get_section_containers_format2(
aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ATTRIBUTES_NAME"
)

View File

@@ -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"
)

View File

@@ -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"
)

View File

@@ -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"
)

View File

@@ -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"
)

View File

@@ -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"
)

View File

@@ -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"
)

View File

@@ -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"
)

View File

@@ -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"
)

View File

@@ -1,23 +0,0 @@
import warnings
from dashboard.common_methods import get_section_containers_format1
warnings.filterwarnings("ignore")
def get_table(data):
aux = data[
[
"REQUIREMENTS_ID",
"REQUIREMENTS_ATTRIBUTES_SECTION",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
].copy()
return get_section_containers_format1(
aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ID"
)

View File

@@ -1,29 +0,0 @@
import warnings
from dashboard.common_methods import get_section_containers_ens
warnings.filterwarnings("ignore")
def get_table(data):
aux = data[
[
"REQUIREMENTS_ATTRIBUTES_MARCO",
"REQUIREMENTS_ATTRIBUTES_CATEGORIA",
"REQUIREMENTS_ATTRIBUTES_IDGRUPOCONTROL",
"REQUIREMENTS_ATTRIBUTES_TIPO",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
]
return get_section_containers_ens(
aux,
"REQUIREMENTS_ATTRIBUTES_MARCO",
"REQUIREMENTS_ATTRIBUTES_CATEGORIA",
"REQUIREMENTS_ATTRIBUTES_IDGRUPOCONTROL",
"REQUIREMENTS_ATTRIBUTES_TIPO",
)

View File

@@ -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",
]
].copy()
return get_section_containers_format3(
aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ID"
)

View File

@@ -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",
]
].copy()
return get_section_containers_format3(
aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ID"
)

View File

@@ -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",
]
].copy()
return get_section_containers_format3(
aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ID"
)

View File

@@ -1,23 +0,0 @@
import warnings
from dashboard.common_methods import get_section_containers_format1
warnings.filterwarnings("ignore")
def get_table(data):
aux = data[
[
"REQUIREMENTS_ID",
"REQUIREMENTS_ATTRIBUTES_SECTION",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
].copy()
return get_section_containers_format1(
aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ID"
)

View File

@@ -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",
]
].copy()
return get_section_containers_format3(
aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ID"
)

View File

@@ -1,23 +0,0 @@
import warnings
from dashboard.common_methods import get_section_containers_format1
warnings.filterwarnings("ignore")
def get_table(data):
aux = data[
[
"REQUIREMENTS_ID",
"REQUIREMENTS_ATTRIBUTES_SECTION",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
].copy()
return get_section_containers_format1(
aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ID"
)

View File

@@ -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",
]
].copy()
return get_section_containers_format3(
aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ID"
)

View File

@@ -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"
)

View File

@@ -1,23 +0,0 @@
import warnings
from dashboard.common_methods import get_section_containers_format2
warnings.filterwarnings("ignore")
def get_table(data):
aux = data[
[
"REQUIREMENTS_ID",
"REQUIREMENTS_SUBTECHNIQUES",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
].copy()
return get_section_containers_format2(
aux, "REQUIREMENTS_ID", "REQUIREMENTS_SUBTECHNIQUES"
)

View File

@@ -1,23 +0,0 @@
import warnings
from dashboard.common_methods import get_section_containers_format2
warnings.filterwarnings("ignore")
def get_table(data):
aux = data[
[
"REQUIREMENTS_ID",
"REQUIREMENTS_SUBTECHNIQUES",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
].copy()
return get_section_containers_format2(
aux, "REQUIREMENTS_ID", "REQUIREMENTS_SUBTECHNIQUES"
)

View File

@@ -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",
]
].copy()
return get_section_containers_format3(
aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ID"
)

View File

@@ -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",
]
].copy()
return get_section_containers_format3(
aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ID"
)

View File

@@ -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",
]
].copy()
return get_section_containers_format3(
aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ID"
)

View File

@@ -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",
]
].copy()
return get_section_containers_format3(
aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ID"
)

View File

@@ -1,20 +0,0 @@
import warnings
from dashboard.common_methods import get_section_containers_pci
warnings.filterwarnings("ignore")
def get_table(data):
aux = data[
[
"REQUIREMENTS_ID",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
]
return get_section_containers_pci(aux, "REQUIREMENTS_ID")

View File

@@ -1,20 +0,0 @@
import warnings
from dashboard.common_methods import get_section_containers_rbi
warnings.filterwarnings("ignore")
def get_table(data):
aux = data[
[
"REQUIREMENTS_ID",
"REQUIREMENTS_DESCRIPTION",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
]
return get_section_containers_rbi(aux, "REQUIREMENTS_ID")

View File

@@ -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_DESCRIPTION",
"REQUIREMENTS_ATTRIBUTES_SECTION",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
].copy()
return get_section_containers_format3(
aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ID"
)

View File

@@ -1,32 +0,0 @@
import os
# Emojis to be used in the compliance table
pass_emoji = ""
fail_emoji = ""
info_emoji = ""
manual_emoji = "✋🏽"
# Main colors
fail_color = "#e67272"
pass_color = "#54d283"
info_color = "#2684FF"
manual_color = "#636c78"
# Muted colors
muted_fail_color = "#fca903"
muted_pass_color = "#03fccf"
muted_manual_color = "#b33696"
# Severity colors
critical_color = "#951649"
high_color = "#e11d48"
medium_color = "#ee6f15"
low_color = "#f9f5e6"
informational_color = "#3274d9"
# Folder output path
folder_path_overview = os.getcwd() + "/output"
folder_path_compliance = os.getcwd() + "/output/compliance"
# Encoding
encoding_format = "utf-8"

View File

@@ -1,5 +0,0 @@
def init_dashboard_parser(self):
"""Init the Dashboard CLI parser"""
# If we don't set `help="Dashboard"` this won't be rendered
# We don't want the dashboard to inherit from the common providers parser since it's a different component
self.subparsers.add_parser("dashboard")

View File

@@ -1,157 +0,0 @@
from typing import List
from dash import html
def create_provider_card(
provider: str, provider_logo: str, account_type: str, filtered_data
) -> List[html.Div]:
"""
Card to display the provider's name and icon.
Args:
provider (str): Name of the provider.
provider_icon (str): Icon of the provider.
Returns:
html.Div: Card to display the provider's name and icon.
"""
accounts = len(
filtered_data[filtered_data["PROVIDER"] == provider]["ACCOUNT_UID"].unique()
)
checks_executed = len(
filtered_data[filtered_data["PROVIDER"] == provider]["CHECK_ID"].unique()
)
fails = len(
filtered_data[
(filtered_data["PROVIDER"] == provider)
& (filtered_data["STATUS"] == "FAIL")
]
)
passes = len(
filtered_data[
(filtered_data["PROVIDER"] == provider)
& (filtered_data["STATUS"] == "PASS")
]
)
# Take the values in the MUTED colum that are true for the provider
if "MUTED" in filtered_data.columns:
muted = len(
filtered_data[
(filtered_data["PROVIDER"] == provider)
& (filtered_data["MUTED"] == "True")
]
)
else:
muted = 0
return [
html.Div(
[
html.Div(
[
html.Div(
[
html.Div(
[
html.Div([provider_logo], className="w-8"),
],
className="p-2 shadow-box-up rounded-full",
),
html.H5(
f"{provider.upper()} {account_type}",
className="text-base font-semibold leading-snug tracking-normal text-gray-900",
),
],
className="flex justify-between items-center mb-3",
),
html.Div(
[
html.Div(
[
html.Span(
account_type,
className="text-prowler-stone-900 inline-block text-3xs font-bold uppercase transition-all rounded-lg text-prowler-stone-900 shadow-box-up px-4 py-1 text-center col-span-6 flex justify-center items-center",
),
html.Div(
accounts,
className="inline-block text-xs text-prowler-stone-900 font-bold shadow-box-down px-4 py-1 rounded-lg text-center col-span-5 col-end-13",
),
],
className="grid grid-cols-12",
),
html.Div(
[
html.Span(
"Checks",
className="text-prowler-stone-900 inline-block text-3xs font-bold uppercase transition-all rounded-lg text-prowler-stone-900 shadow-box-up px-4 py-1 text-center col-span-6 flex justify-center items-center",
),
html.Div(
checks_executed,
className="inline-block text-xs text-prowler-stone-900 font-bold shadow-box-down px-4 py-1 rounded-lg text-center col-span-5 col-end-13",
),
],
className="grid grid-cols-12",
),
html.Div(
[
html.Span(
"FAILED",
className="text-prowler-stone-900 inline-block text-3xs font-bold uppercase transition-all rounded-lg text-prowler-stone-900 shadow-box-up px-4 py-1 text-center col-span-6 flex justify-center items-center",
),
html.Div(
[
html.Div(
fails,
className="m-[2px] px-4 py-1 rounded-lg bg-gradient-failed",
),
],
className="inline-block text-xs font-bold shadow-box-down rounded-lg text-center col-span-5 col-end-13",
),
],
className="grid grid-cols-12",
),
html.Div(
[
html.Span(
"PASSED",
className="text-prowler-stone-900 inline-block text-3xs font-bold uppercase transition-all rounded-lg text-prowler-stone-900 shadow-box-up px-4 py-1 text-center col-span-6 flex justify-center items-center",
),
html.Div(
[
html.Div(
passes,
className="m-[2px] px-4 py-1 rounded-lg bg-gradient-passed",
),
],
className="inline-block text-xs font-bold shadow-box-down rounded-lg text-center col-span-5 col-end-13",
),
],
className="grid grid-cols-12",
),
html.Div(
[
html.Span(
"MUTED",
className="text-prowler-stone-900 inline-block text-3xs font-bold uppercase transition-all rounded-lg text-prowler-stone-900 shadow-box-up px-4 py-1 text-center col-span-6 flex justify-center items-center",
),
html.Div(
[
html.Div(
muted,
className="m-[2px] px-4 py-1 rounded-lg bg-gradient-muted",
),
],
className="inline-block text-xs font-bold shadow-box-down rounded-lg text-center col-span-5 col-end-13",
),
],
className="grid grid-cols-12",
),
],
className="grid gap-x-8 gap-y-4",
),
],
className="px-4 py-3",
),
],
className="relative flex flex-col bg-white shadow-provider rounded-xl w-full transition ease-in-out delay-100 hover:-translate-y-1 hover:scale-110 hover:z-50 hover:cursor-pointer",
)
]

View File

@@ -1,289 +0,0 @@
from dash import dcc, html
def create_date_dropdown(assesment_times: list) -> html.Div:
"""
Dropdown to select the date of the last available scan for each account.
Args:
assesment_times (list): List of dates of the last available scan for each account.
Returns:
html.Div: Dropdown to select the date of the last available scan for each account.
"""
return html.Div(
[
html.Div(
[
html.Label(
"Assessment date (last available scan) ",
className="text-prowler-stone-900 font-bold text-sm",
),
html.Img(
id="info-file-over",
src="/assets/images/icons/help-black.png",
className="w-5",
title="The date of the last available scan for each account is displayed here. If you have not run prowler yet, the date will be empty.",
),
],
style={"display": "inline-flex"},
),
dcc.Dropdown(
id="report-date-filter",
options=[
{"label": account, "value": account} for account in assesment_times
],
value=assesment_times[0],
clearable=False,
multi=False,
style={"color": "#000000", "width": "100%"},
),
],
)
def create_date_dropdown_compliance(assesment_times: list) -> html.Div:
"""
Dropdown to select the date of the last available scan for each account.
Args:
assesment_times (list): List of dates of the last available scan for each account.
Returns:
html.Div: Dropdown to select the date of the last available scan for each account.
"""
return html.Div(
[
html.Label(
"Assesment Date:", className="text-prowler-stone-900 font-bold text-sm"
),
dcc.Dropdown(
id="date-filter-analytics",
options=[
{"label": account, "value": account} for account in assesment_times
],
value=assesment_times[0],
clearable=False,
multi=False,
style={"color": "#000000", "width": "100%"},
),
],
)
def create_region_dropdown(regions: list) -> html.Div:
"""
Dropdown to select the region of the account.
Args:
regions (list): List of regions of the account.
Returns:
html.Div: Dropdown to select the region of the account.
"""
return html.Div(
[
html.Label(
"Region / Location / Namespace :",
className="text-prowler-stone-900 font-bold text-sm",
),
dcc.Dropdown(
id="region-filter",
options=[{"label": region, "value": region} for region in regions],
value=["All"], # Initial selection is ALL
clearable=False,
multi=True,
style={"color": "#000000", "width": "100%"},
),
],
)
def create_region_dropdown_compliance(regions: list) -> html.Div:
"""
Dropdown to select the region of the account.
Args:
regions (list): List of regions of the account.
Returns:
html.Div: Dropdown to select the region of the account.
"""
return html.Div(
[
html.Label(
"Region / Location / Namespace :",
className="text-prowler-stone-900 font-bold text-sm",
),
dcc.Dropdown(
id="region-filter-compliance",
options=[{"label": region, "value": region} for region in regions],
value=["All"], # Initial selection is ALL
clearable=False,
multi=True,
style={"color": "#000000", "width": "100%"},
),
],
)
def create_account_dropdown(accounts: list) -> html.Div:
"""
Dropdown to select the account.
Args:
accounts (list): List of accounts.
Returns:
html.Div: Dropdown to select the account.
"""
return html.Div(
[
html.Label(
"Account / Subscription / Project / Cluster :",
className="text-prowler-stone-900 font-bold text-sm",
),
dcc.Dropdown(
id="cloud-account-filter",
options=[{"label": account, "value": account} for account in accounts],
value=["All"], # Initial selection is ALL
clearable=False,
multi=True,
style={"color": "#000000", "width": "100%"},
),
],
)
def create_account_dropdown_compliance(accounts: list) -> html.Div:
"""
Dropdown to select the account.
Args:
accounts (list): List of accounts.
Returns:
html.Div: Dropdown to select the account.
"""
return html.Div(
[
html.Label(
"Account / Subscription / Project / Cluster :",
className="text-prowler-stone-900 font-bold text-sm",
),
dcc.Dropdown(
id="cloud-account-filter-compliance",
options=[{"label": account, "value": account} for account in accounts],
value=["All"], # Initial selection is ALL
clearable=False,
multi=True,
style={"color": "#000000", "width": "100%"},
),
],
)
def create_compliance_dropdown(compliance: list) -> html.Div:
"""
Dropdown to select the compliance.
Args:
compliance (list): List of compliance.
Returns:
html.Div: Dropdown to select the compliance.
"""
return html.Div(
[
html.Label(
"Compliance:", className="text-prowler-stone-900 font-bold text-sm"
),
dcc.Dropdown(
id="report-compliance-filter",
options=[{"label": i, "value": i} for i in compliance],
value=compliance[0],
clearable=False,
style={"color": "#000000"},
),
],
)
def create_severity_dropdown(severity: list) -> html.Div:
"""
Dropdown to select the severity.
Args:
severity (list): List of severity.
Returns:
html.Div: Dropdown to select the severity.
"""
return html.Div(
[
html.Label(
"Severity:", className="text-prowler-stone-900 font-bold text-sm"
),
dcc.Dropdown(
id="severity-filter",
options=[{"label": i, "value": i} for i in severity],
value=["All"],
clearable=False,
multi=True,
style={"color": "#000000"},
),
],
)
def create_service_dropdown(services: list) -> html.Div:
"""
Dropdown to select the service.
Args:
services (list): List of services.
Returns:
html.Div: Dropdown to select the service.
"""
return html.Div(
[
html.Label(
"Service:", className="text-prowler-stone-900 font-bold text-sm"
),
dcc.Dropdown(
id="service-filter",
options=[{"label": i, "value": i} for i in services],
value=["All"],
clearable=False,
multi=True,
style={"color": "#000000"},
),
],
)
def create_status_dropdown(status: list) -> html.Div:
"""
Dropdown to select the status.
Args:
status (list): List of status.
Returns:
html.Div: Dropdown to select the status.
"""
return html.Div(
[
html.Label("Status:", className="text-prowler-stone-900 font-bold text-sm"),
dcc.Dropdown(
id="status-filter",
options=[{"label": i, "value": i} for i in status],
value=["All"],
clearable=False,
multi=True,
style={"color": "#000000"},
),
],
)
def create_table_row_dropdown(table_rows: list) -> html.Div:
"""
Dropdown to select the number of rows in the table.
Args:
table_rows (list): List of number of rows.
Returns:
html.Div: Dropdown to select the number of rows in the table.
"""
return html.Div(
[
dcc.Dropdown(
id="table-rows",
options=[{"label": i, "value": i} for i in table_rows],
value=table_rows[0],
clearable=False,
style={"color": "#000000", "margin-right": "10px"},
),
],
)

View File

@@ -1,174 +0,0 @@
from dash import dcc, html
def create_layout_overview(
account_dropdown: html.Div,
date_dropdown: html.Div,
region_dropdown: html.Div,
download_button_csv: html.Button,
download_button_xlsx: html.Button,
severity_dropdown: html.Div,
service_dropdown: html.Div,
table_row_dropdown: html.Div,
status_dropdown: html.Div,
) -> html.Div:
"""
Create the layout of the dashboard.
Args:
account_dropdown (html.Div): Dropdown to select the account.
date_dropdown (html.Div): Dropdown to select the date of the last available scan for each account.
region_dropdown (html.Div): Dropdown to select the region of the account.
Returns:
html.Div: Layout of the dashboard.
"""
return html.Div(
[
dcc.Location(id="url", refresh=False),
html.Div(
[
html.H1(
"Scan Overview",
className="text-prowler-stone-900 text-2xxl font-bold",
),
html.Div(className="d-flex flex-wrap", id="subscribe_card"),
],
className="flex justify-between border-b border-prowler-500 pb-3",
),
html.Div(
[
html.Div([date_dropdown], className=""),
html.Div([account_dropdown], className=""),
html.Div([region_dropdown], className=""),
],
className="grid gap-x-4 gap-y-4 sm:grid-cols-2 lg:grid-cols-3 lg:gap-y-0",
),
html.Div(
[
html.Div([severity_dropdown], className=""),
html.Div([service_dropdown], className=""),
html.Div([status_dropdown], className=""),
],
className="grid gap-x-4 gap-y-4 sm:grid-cols-2 lg:grid-cols-3 lg:gap-y-0",
),
html.Div(
[
html.Div(className="flex", id="aws_card", n_clicks=0),
html.Div(className="flex", id="azure_card", n_clicks=0),
html.Div(className="flex", id="gcp_card", n_clicks=0),
html.Div(className="flex", id="k8s_card", n_clicks=0),
],
className="grid gap-x-4 gap-y-4 sm:grid-cols-2 lg:grid-cols-4 lg:gap-y-0",
),
html.H4(
"Count of Findings by severity",
className="text-prowler-stone-900 text-lg font-bold",
),
html.Div(
[
html.Div(
className="flex flex-col col-span-12 sm:col-span-6 lg:col-span-3 gap-y-4",
id="status_graph",
),
html.Div(
className="flex flex-col col-span-12 sm:col-span-6 lg:col-span-3 gap-y-4",
id="two_pie_chart",
),
html.Div(
className="flex flex-col col-span-12 sm:col-span-6 lg:col-span-6 col-end-13 gap-y-4",
id="line_plot",
),
],
className="grid gap-x-4 gap-y-4 grid-cols-12 lg:gap-y-0",
),
html.Div(
[
html.H4(
"Top Findings by Severity",
className="text-prowler-stone-900 text-lg font-bold",
),
html.Div(
[
(
html.Label(
"Table Rows:",
className="text-prowler-stone-900 font-bold text-sm",
style={"margin-right": "10px"},
)
),
table_row_dropdown,
download_button_csv,
download_button_xlsx,
],
className="flex justify-between items-center",
),
dcc.Download(id="download-data"),
],
className="flex justify-between items-center",
),
html.Div(id="table", className="grid"),
],
className="grid gap-x-8 gap-y-8 2xl:container mx-auto",
)
def create_layout_compliance(
account_dropdown: html.Div,
date_dropdown: html.Div,
region_dropdown: html.Div,
compliance_dropdown: html.Div,
) -> html.Div:
return html.Div(
[
dcc.Location(id="url", refresh=False),
html.Div(
[
html.H1(
"Compliance",
className="text-prowler-stone-900 text-2xxl font-bold",
),
html.A(
[
html.Img(src="assets/favicon.ico", className="w-5 mr-3"),
html.Span("Subscribe to prowler SaaS"),
],
href="https://prowler.pro/",
target="_blank",
className="text-prowler-stone-900 inline-flex px-4 py-2 text-xs font-bold uppercase transition-all rounded-lg text-gray-900 hover:bg-prowler-stone-900/10 border-solid border-1 hover:border-prowler-stone-900/10 hover:border-solid hover:border-1 border-prowler-stone-900/10",
),
],
className="flex justify-between border-b border-prowler-500 pb-3",
),
html.Div(
[
html.Div([date_dropdown], className=""),
html.Div([account_dropdown], className=""),
html.Div([region_dropdown], className=""),
html.Div([compliance_dropdown], className=""),
],
className="grid gap-x-4 gap-y-4 sm:grid-cols-2 lg:grid-cols-4 lg:gap-y-0",
),
html.Div(
[
html.Div(
className="flex flex-col col-span-12 md:col-span-4 gap-y-4",
id="overall_status_result_graph",
),
html.Div(
className="flex flex-col col-span-12 md:col-span-7 md:col-end-13 gap-y-4",
id="security_level_graph",
),
html.Div(
className="flex flex-col col-span-12 md:col-span-2 gap-y-4",
id="",
),
],
className="grid gap-x-4 gap-y-4 grid-cols-12 lg:gap-y-0",
),
html.H4(
"Details compliance:",
className="text-prowler-stone-900 text-lg font-bold",
),
html.Div(className="flex flex-wrap", id="output"),
],
className="grid gap-x-8 gap-y-8 2xl:container mx-auto",
)

View File

@@ -1,600 +0,0 @@
# Standard library imports
import csv
import glob
import importlib
import os
import re
import warnings
# Third-party imports
import dash
import pandas as pd
import plotly.express as px
from dash import callback, dcc, html
from dash.dependencies import Input, Output
# Config import
from dashboard.config import (
encoding_format,
fail_color,
folder_path_compliance,
info_color,
manual_color,
pass_color,
)
from dashboard.lib.dropdowns import (
create_account_dropdown_compliance,
create_compliance_dropdown,
create_date_dropdown_compliance,
create_region_dropdown_compliance,
)
from dashboard.lib.layouts import create_layout_compliance
# Suppress warnings
warnings.filterwarnings("ignore")
# Global variables
# TODO: Create a flag to let the user put a custom path
csv_files = []
for file in glob.glob(os.path.join(folder_path_compliance, "*.csv")):
with open(file, "r", newline="", encoding=encoding_format) as csvfile:
reader = csv.reader(csvfile)
num_rows = sum(1 for row in reader)
if num_rows > 1:
csv_files.append(file)
def load_csv_files(csv_files):
# Load CSV files into a single pandas DataFrame.
dfs = []
results = []
for file in csv_files:
df = pd.read_csv(file, sep=";", on_bad_lines="skip")
if "CHECKID" in df.columns:
dfs.append(df)
result = file
result = result.split("/")[-1]
result = re.sub(r"^.*?_", "", result)
result = result.replace(".csv", "")
result = result.upper()
if "AWS" in result:
if "AWS_" in result:
result = result.replace("_AWS", "")
else:
result = result.replace("_AWS", " - AWS")
if "GCP" in result:
result = result.replace("_GCP", " - GCP")
if "AZURE" in result:
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)
results = list(unique_results)
# Check if there is any CIS report in the list and divide it in level 1 and level 2
new_results = []
old_results = results.copy()
for compliance_name in results:
if "CIS_" in compliance_name:
old_results.remove(compliance_name)
new_results.append(compliance_name + " - Level_1")
new_results.append(compliance_name + " - Level_2")
results = old_results + new_results
results.sort()
# Handle the case where there are no CSV files
try:
data = pd.concat(dfs, ignore_index=True)
except ValueError:
data = None
return data, results
data, results = load_csv_files(csv_files)
if data is None:
dash.register_page(__name__)
layout = html.Div(
[
html.Div(
[
html.H5(
"No data found, check if the CSV files are in the correct folder.",
className="card-title",
style={"text-align": "left"},
)
],
style={
"width": "99%",
"margin-right": "0.8%",
"margin-bottom": "10px",
},
)
]
)
else:
data["ASSESSMENTDATE"] = pd.to_datetime(data["ASSESSMENTDATE"])
data["ASSESSMENT_TIME"] = data["ASSESSMENTDATE"].dt.strftime("%Y-%m-%d %H:%M:%S")
data_values = data["ASSESSMENT_TIME"].unique()
data_values.sort()
data_values = data_values[::-1]
aux = []
for value in data_values:
if value.split(" ")[0] not in [aux[i].split(" ")[0] for i in range(len(aux))]:
aux.append(value)
data_values = aux
data = data[data["ASSESSMENT_TIME"].isin(data_values)]
data["ASSESSMENT_TIME"] = data["ASSESSMENT_TIME"].apply(lambda x: x.split(" ")[0])
# Select Compliance - Dropdown
compliance_dropdown = create_compliance_dropdown(results)
# Select Account - Dropdown
select_account_dropdown_list = ["All"]
# Append to the list the unique values of the columns ACCOUNTID, PROJECTID and SUBSCRIPTIONID if they exist
if "ACCOUNTID" in data.columns:
select_account_dropdown_list = select_account_dropdown_list + list(
data["ACCOUNTID"].unique()
)
if "PROJECTID" in data.columns:
select_account_dropdown_list = select_account_dropdown_list + list(
data["PROJECTID"].unique()
)
if "SUBSCRIPTIONID" in data.columns:
select_account_dropdown_list = select_account_dropdown_list + list(
data["SUBSCRIPTIONID"].unique()
)
if "SUBSCRIPTION" in data.columns:
select_account_dropdown_list = select_account_dropdown_list + list(
data["SUBSCRIPTION"].unique()
)
list_items = []
for item in select_account_dropdown_list:
if item.__class__.__name__ == "str" and "nan" not in item:
list_items.append(item)
account_dropdown = create_account_dropdown_compliance(list_items)
# Select Region - Dropdown
select_region_dropdown_list = ["All"]
# Append to the list the unique values of the column REGION or LOCATION if it exists
if "REGION" in data.columns:
# Handle the case where the column REGION is empty
data["REGION"] = data["REGION"].fillna("-")
select_region_dropdown_list = select_region_dropdown_list + list(
data["REGION"].unique()
)
if "LOCATION" in data.columns:
# Handle the case where the column LOCATION is empty
data["LOCATION"] = data["LOCATION"].fillna("-")
select_region_dropdown_list = select_region_dropdown_list + list(
data["LOCATION"].unique()
)
# Clear the list from None and NaN values
list_items = []
for item in select_region_dropdown_list:
if item.__class__.__name__ == "str":
list_items.append(item)
region_dropdown = create_region_dropdown_compliance(list_items)
# Select Date - Dropdown
date_dropdown = create_date_dropdown_compliance(
list(data["ASSESSMENT_TIME"].unique())
)
dash.register_page(__name__)
layout = create_layout_compliance(
account_dropdown, date_dropdown, region_dropdown, compliance_dropdown
)
@callback(
[
Output("output", "children"),
Output("overall_status_result_graph", "children"),
Output("security_level_graph", "children"),
Output("cloud-account-filter-compliance", "value"),
Output("cloud-account-filter-compliance", "options"),
Output("region-filter-compliance", "value"),
Output("region-filter-compliance", "options"),
Output("date-filter-analytics", "value"),
Output("date-filter-analytics", "options"),
],
Input("report-compliance-filter", "value"),
Input("cloud-account-filter-compliance", "value"),
Input("region-filter-compliance", "value"),
Input("date-filter-analytics", "value"),
)
def display_data(
analytics_input, account_filter, region_filter_analytics, date_filter_analytics
):
current_compliance = analytics_input
analytics_input = analytics_input.replace(" - ", "_")
analytics_input = analytics_input.lower()
# Check if the compliance selected is the level 1 or level 2 of the CIS
is_level_1 = "level_1" in analytics_input
analytics_input = analytics_input.replace("_level_1", "").replace("_level_2", "")
# Filter the data based on the compliance selected
files = [file for file in csv_files if analytics_input in file]
def load_csv_files(files):
"""Load CSV files into a single pandas DataFrame."""
dfs = []
for file in files:
df = pd.read_csv(file, sep=";", on_bad_lines="skip")
dfs.append(df.astype(str))
return pd.concat(dfs, ignore_index=True)
data = load_csv_files(files)
# Rename the column LOCATION to REGION for GCP or Azure
if "gcp" in analytics_input or "azure" in analytics_input:
data = data.rename(columns={"LOCATION": "REGION"})
# Add the column ACCOUNTID to the data if the provider is kubernetes
if "kubernetes" in analytics_input:
data.rename(columns={"CONTEXT": "ACCOUNTID"}, inplace=True)
data.rename(columns={"NAMESPACE": "REGION"}, inplace=True)
if "REQUIREMENTS_ATTRIBUTES_PROFILE" in data.columns:
data["REQUIREMENTS_ATTRIBUTES_PROFILE"] = data[
"REQUIREMENTS_ATTRIBUTES_PROFILE"
].apply(lambda x: x.split(" - ")[0])
# Filter the chosen level of the CIS
if is_level_1:
data = data[data["REQUIREMENTS_ATTRIBUTES_PROFILE"] == "Level 1"]
# Rename the column PROJECTID to ACCOUNTID for GCP
if data.columns.str.contains("PROJECTID").any():
data.rename(columns={"PROJECTID": "ACCOUNTID"}, inplace=True)
# Rename the column SUBSCRIPTIONID to ACCOUNTID for Azure
if data.columns.str.contains("SUBSCRIPTIONID").any():
data.rename(columns={"SUBSCRIPTIONID": "ACCOUNTID"}, inplace=True)
data["REGION"] = "-"
# Handle v3 azure cis compliance
if data.columns.str.contains("SUBSCRIPTION").any():
data.rename(columns={"SUBSCRIPTION": "ACCOUNTID"}, inplace=True)
data["REGION"] = "-"
# Filter ACCOUNT
if account_filter == ["All"]:
updated_cloud_account_values = data["ACCOUNTID"].unique()
elif "All" in account_filter and len(account_filter) > 1:
# Remove 'All' from the list
account_filter.remove("All")
updated_cloud_account_values = account_filter
elif len(account_filter) == 0:
updated_cloud_account_values = data["ACCOUNTID"].unique()
account_filter = ["All"]
else:
updated_cloud_account_values = account_filter
data = data[data["ACCOUNTID"].isin(updated_cloud_account_values)]
account_filter_options = list(data["ACCOUNTID"].unique())
account_filter_options = account_filter_options + ["All"]
for item in account_filter_options:
if "nan" in item or item.__class__.__name__ != "str" or item is None:
account_filter_options.remove(item)
# Filter REGION
if region_filter_analytics == ["All"]:
updated_region_account_values = data["REGION"].unique()
elif "All" in region_filter_analytics and len(region_filter_analytics) > 1:
# Remove 'All' from the list
region_filter_analytics.remove("All")
updated_region_account_values = region_filter_analytics
elif len(region_filter_analytics) == 0:
updated_region_account_values = data["REGION"].unique()
region_filter_analytics = ["All"]
else:
updated_region_account_values = region_filter_analytics
data = data[data["REGION"].isin(updated_region_account_values)]
region_filter_options = list(data["REGION"].unique())
region_filter_options = region_filter_options + ["All"]
for item in region_filter_options:
if item == "nan" or item.__class__.__name__ != "str":
region_filter_options.remove(item)
data["ASSESSMENTDATE"] = pd.to_datetime(data["ASSESSMENTDATE"], errors="coerce")
data["ASSESSMENTDATE"] = data["ASSESSMENTDATE"].dt.strftime("%Y-%m-%d %H:%M:%S")
# Choosing the date that is the most recent
data_values = data["ASSESSMENTDATE"].unique()
data_values.sort()
data_values = data_values[::-1]
aux = []
data_values = [str(i) for i in data_values]
for value in data_values:
if value.split(" ")[0] not in [aux[i].split(" ")[0] for i in range(len(aux))]:
aux.append(value)
data_values = [str(i) for i in aux]
data = data[data["ASSESSMENTDATE"].isin(data_values)]
data["ASSESSMENTDATE"] = data["ASSESSMENTDATE"].apply(lambda x: x.split(" ")[0])
options_date = data["ASSESSMENTDATE"].unique()
options_date.sort()
options_date = options_date[::-1]
# Filter DATE
if date_filter_analytics in options_date:
data = data[data["ASSESSMENTDATE"] == date_filter_analytics]
else:
date_filter_analytics = options_date[0]
data = data[data["ASSESSMENTDATE"] == date_filter_analytics]
if data.empty:
fig = px.pie()
pie_1 = dcc.Graph(
figure=fig,
config={"displayModeBar": False},
style={"height": "250px", "width": "250px", "right": "0px"},
)
return [
html.Div(
[
html.H5(
"No data found for this compliance",
className="card-title",
style={"text-align": "left"},
)
],
style={
"width": "99%",
"margin-right": "0.8%",
"margin-bottom": "10px",
},
)
]
else:
# Check cases where the compliance start with AWS_
if "aws_" in analytics_input:
analytics_input = analytics_input + "_aws"
try:
current = analytics_input.replace(".", "_")
compliance_module = importlib.import_module(
f"dashboard.compliance.{current}"
)
data.drop_duplicates(keep="first", inplace=True)
table = compliance_module.get_table(data)
except ModuleNotFoundError:
table = html.Div(
[
html.H5(
"No data found for this compliance",
className="card-title",
style={"text-align": "left", "color": "black"},
)
],
style={
"width": "99%",
"margin-right": "0.8%",
"margin-bottom": "10px",
},
)
df = data.copy()
df = df.groupby(["STATUS"]).size().reset_index(name="counts")
df = df.sort_values(by=["counts"], ascending=False)
# Pie 1
pie_1 = get_pie(df)
# Get the pie2 depending on the compliance
df = data.copy()
current_filter = ""
if "pci" in analytics_input:
pie_2 = get_bar_graph(df, "REQUIREMENTS_ID")
current_filter = "req_id"
elif (
"REQUIREMENTS_ATTRIBUTES_SECTION" in df.columns
and not df["REQUIREMENTS_ATTRIBUTES_SECTION"].isnull().values.any()
):
pie_2 = get_bar_graph(df, "REQUIREMENTS_ATTRIBUTES_SECTION")
current_filter = "sections"
elif (
"REQUIREMENTS_ATTRIBUTES_CATEGORIA" in df.columns
and not df["REQUIREMENTS_ATTRIBUTES_CATEGORIA"].isnull().values.any()
):
pie_2 = get_bar_graph(df, "REQUIREMENTS_ATTRIBUTES_CATEGORIA")
current_filter = "categorias"
elif (
"REQUIREMENTS_ATTRIBUTES_CATEGORY" in df.columns
and not df["REQUIREMENTS_ATTRIBUTES_CATEGORY"].isnull().values.any()
):
pie_2 = get_bar_graph(df, "REQUIREMENTS_ATTRIBUTES_CATEGORY")
current_filter = "categories"
elif (
"REQUIREMENTS_ATTRIBUTES_SERVICE" in df.columns
and not df["REQUIREMENTS_ATTRIBUTES_SERVICE"].isnull().values.any()
):
pie_2 = get_bar_graph(df, "REQUIREMENTS_ATTRIBUTES_SERVICE")
current_filter = "services"
elif (
"REQUIREMENTS_ID" in df.columns
and not df["REQUIREMENTS_ID"].isnull().values.any()
):
pie_2 = get_bar_graph(df, "REQUIREMENTS_ID")
current_filter = "techniques"
else:
fig = px.pie()
fig.update_layout(
margin=dict(l=0, r=0, t=0, b=0),
autosize=True,
showlegend=False,
paper_bgcolor="#303030",
)
pie_2 = dcc.Graph(
figure=fig,
config={"displayModeBar": False},
style={"height": "250px", "width": "250px", "right": "0px"},
)
current_filter = "none"
# Analytics table
if not analytics_input:
analytics_input = ""
table_output = get_table(current_compliance, table)
overall_status_result_graph = get_graph(pie_1, "Overall Status Result")
security_level_graph = get_graph(
pie_2, f"Top 5 failed {current_filter} by findings"
)
return (
table_output,
overall_status_result_graph,
security_level_graph,
account_filter,
account_filter_options,
region_filter_analytics,
region_filter_options,
date_filter_analytics,
options_date,
)
def get_graph(pie, title):
return [
html.Span(
title,
className="text-center text-prowler-stone-900 uppercase text-xs font-bold",
),
html.Div(
[pie],
className="",
style={
"display": "flex",
"justify-content": "center",
"align-items": "center",
"margin-top": "7%",
},
),
]
def get_bar_graph(df, column_name):
df = df[df["STATUS"] == "FAIL"]
df = df.groupby([column_name, "STATUS"]).size().reset_index(name="counts")
df = df.sort_values(by=["counts"], ascending=True)
# take the top 5
df = df.tail(5)
colums = df[column_name].unique()
# Cut the text if it is too long
for i in range(len(colums)):
if len(colums[i]) > 15:
colums[i] = colums[i][:15] + "..."
fig = px.bar(
df,
x="counts",
y=colums,
color="STATUS",
color_discrete_map={"FAIL": fail_color},
orientation="h",
)
fig.update_layout(
margin=dict(l=0, r=0, t=0, b=0),
autosize=True,
showlegend=False,
xaxis_title=None,
yaxis_title=None,
font=dict(size=14, color="#292524"),
hoverlabel=dict(font_size=12),
paper_bgcolor="#FFF",
)
return dcc.Graph(
figure=fig,
config={"displayModeBar": False},
style={"height": "20rem", "width": "40rem"},
)
def get_pie(df):
# Define custom colors
color_mapping = {
"FAIL": fail_color,
"PASS": pass_color,
"INFO": info_color,
"WARN": "#260000",
"MANUAL": manual_color,
}
# Use the color_discrete_map parameter to map categories to custom colors
fig = px.pie(
df,
names="STATUS",
values="counts",
hole=0.7,
color="STATUS",
color_discrete_map=color_mapping,
)
fig.update_traces(
hovertemplate=None,
textposition="outside",
textinfo="percent+label",
rotation=50,
)
fig.update_layout(
margin=dict(l=0, r=0, t=0, b=0),
autosize=True,
showlegend=False,
font=dict(size=14, color="#292524"),
hoverlabel=dict(font_size=12),
paper_bgcolor="#FFF",
)
pie = dcc.Graph(
figure=fig,
config={"displayModeBar": False},
style={"height": "20rem", "width": "20rem"},
)
return pie
def get_table(current_compliance, table):
return [
html.Div(
[
html.H5(
f"{current_compliance}",
className="text-prowler-stone-900 text-md font-bold uppercase mb-4",
),
table,
],
className="relative flex flex-col bg-white shadow-provider rounded-xl px-4 py-3 flex-wrap w-full",
),
]

File diff suppressed because it is too large Load Diff

View File

@@ -1,112 +0,0 @@
/*
/*
/*
/*
/* Use this file to add custom styles using Tailwind's utility classes. */
@tailwind base;
@tailwind components;
@tailwind utilities;
#_dash-app-content {
@apply bg-prowler-stone-500;
}
@layer components {
.custom-grid {
grid-template-columns: minmax(0, 16fr) repeat(11, minmax(0, 11fr));
}
.custom-grid-large {
grid-template-columns: minmax(0, 10fr) repeat(11, minmax(0, 11fr));
}
/* Styles for the accordion in the compliance page */
#_dash-app-content .accordion .accordion-header .accordion-button {
@apply text-prowler-stone-900 inline-block px-4 text-xs font-bold uppercase transition-all rounded-lg bg-prowler-stone-300 hover:bg-prowler-stone-900/10;
}
#_dash-app-content .accordion .accordion-item {
@apply text-prowler-stone-900 bg-prowler-white rounded-lg;
}
#_dash-app-content .accordion .accordion-button:not(.collapsed) {
@apply text-prowler-stone-900 bg-prowler-stone-500;
}
#_dash-app-content .accordion .dash-table-container {
@apply grid;
}
#_dash-app-content .accordion table {
@apply rounded-lg;
}
/* Styles for thead */
#_dash-app-content .accordion th {
@apply text-prowler-white text-left bg-prowler-stone-900 text-xs py-1 font-bold;
}
/* Styles for td */
#_dash-app-content .accordion td {
@apply text-prowler-stone-900 text-left bg-prowler-white text-xs py-1 font-light;
}
/* Styles for table cells */
#_dash-app-content .accordion table tbody thead,
#_dash-app-content .accordion table tbody tr {
@apply w-full;
}
/* Check ID */
#_dash-app-content .accordion table th:nth-child(1) {
@apply w-[60%];
}
/* Status */
#_dash-app-content .accordion table th:nth-child(2) {
@apply w-[10%] text-center;
}
#_dash-app-content .accordion table td:nth-child(2) {
@apply text-center;
}
/* Region */
#_dash-app-content .accordion table th:nth-child(3) {
@apply w-[10%];
}
/* Account ID */
#_dash-app-content .accordion table th:nth-child(4) {
@apply w-[10%];
}
/* Resource ID */
#_dash-app-content .accordion table th:nth-child(5) {
@apply w-[10%];
}
#_dash-app-content .compliance-data-layout,
#_dash-app-content .accordion-body,
#_dash-app-content .compliance-data-layout .accordion.accordion-flush {
@apply grid gap-y-4;
}
#_dash-app-content .accordion-inner--child,
#_dash-app-content .accordion-inner {
@apply relative;
}
#_dash-app-content .info-bar {
@apply absolute left-1/2 transform -translate-x-1/2 top-2 h-8 z-50;
}
#_dash-app-content .info-bar-child {
@apply absolute right-6 top-2 w-auto h-8 z-50;
}
@layer utilities {
/* Hide scrollbar for Chrome, Safari and Opera */
.no-scrollbar::-webkit-scrollbar {
display: none;
}
/* Hide scrollbar for IE, Edge and Firefox */
.no-scrollbar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
}

View File

@@ -1,90 +0,0 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./assets/**/*.{py,html,js}",
"./components/**/*.{py,html,js}",
"./pages/**/*.{py,html,js}",
"./utils/**/*.{py,html,js}",
"./app.py",
],
theme: {
extend: {
colors: {
prowler: {
stone: {
950: "#1C1917",
900: "#292524",
500: "#E7E5E4",
300: "#F5F5F4",
},
gray: {
900: "#9bAACF",
700: "#BEC8E4",
500: "#C8D0E7",
300: "#E4EBF5",
},
status: {
passed: "#1FB53F",
failed: "#A3231F",
},
lime: "#84CC16",
white: "#FFFFFF",
error: "#B91C1C",
},
},
fontSize: {
'3xs': '0.625rem', // 10px
'2xs': '0.6875rem', // 11px
xs: '0.75rem', // 12px
sm: '0.875rem', // 14px
base: '1rem', // 16px
lg: '1.125rem', // 18px
xl: '1.25rem', // 20px
'2xl': '1.375rem', // 22px
'2xxl': '1.5rem', // 24px
'3xl': '1.75rem', // 28px
'4xl': '2rem', // 32px
'5xl': '2.25rem', // 36px
'6xl': '2.75rem', // 44px
'7xl': '3.5rem' // 56px
},
fontWeight: {
light: 300,
regular: 400,
medium: 500,
bold: 700,
heavy: 800
},
lineHeight: {
14: "0.875rem", // 14px
22: "1.375rem", // 22px
26: "1.625rem", // 26px
28: "1.75rem", // 28px
30: "1.875rem", // 30px
32: "2rem", // 32px
34: "2.125rem", // 34px
36: "2.25rem", // 36px
40: "2.5rem", // 40px
44: "2.75rem", // 44px
48: "3rem", // 48px
56: "3.5rem", // 56px
68: "4.25rem", // 68px
},
boxShadow: {
"provider":
".3rem .3rem .6rem #c8d0e7, -.2rem -.2rem .5rem #FFF",
"box-up":
"0.3rem 0.3rem 0.6rem #c8d0e7, -0.2rem -0.2rem 0.5rem #FFF",
"box-down":
"inset .2rem .2rem .5rem #c8d0e7, inset -.2rem -.2rem .5rem #FFF",
},
backgroundImage: {
"gradient-passed":
"linear-gradient(127.43deg, #F1F5F8 -177.68%, #4ADE80 87.35%)",
"gradient-failed":
"linear-gradient(127.43deg, #F1F5F8 -177.68%, #EF4444 87.35%)",
},
},
},
plugins: [],
};

View File

@@ -1,325 +0,0 @@
# Create a new Check for a Provider
Here you can find how to create new checks for Prowler.
**To create a check is required to have a Prowler provider service already created, so if the service is not present or the attribute you want to audit is not retrieved by the service, please refer to the [Service](./services.md) documentation.**
## Introduction
The checks are the fundamental piece of Prowler. A check is a simply piece of code that ensures if something is configured against cybersecurity best practices. Then the check generates a finding with the result and includes the check's metadata to give the user more contextual information about the result, the risk and how to remediate it.
To create a new check for a supported Prowler provider, you will need to create a folder with the check name inside the specific service for the selected provider.
We are going to use the `ec2_ami_public` check from the `AWS` provider as an example. So the folder name will be `prowler/providers/aws/services/ec2/ec2_ami_public` (following the format `prowler/providers/<provider>/services/<service>/<check_name>`), with the name of check following the pattern: `service_subservice_resource_action`.
???+ note
A subservice is an specific component of a service that is gonna be audited. Sometimes it could be the shortened name of the class attribute that is gonna be accessed in the check.
Inside that folder, we need to create three files:
- An empty `__init__.py`: to make Python treat this check folder as a package.
- A `check_name.py` with the above format containing the check's logic. Refer to the [check](./checks.md#check)
- A `check_name.metadata.json` containing the check's metadata. Refer to the [check metadata](./checks.md#check-metadata)
## Check
The Prowler's check structure is very simple and following it there is nothing more to do to include a check in a provider's service because the load is done dynamically based on the paths.
The following is the code for the `ec2_ami_public` check:
```python title="Check Class"
# At the top of the file we need to import the following:
# - Check class which is in charge of the following:
# - Retrieve the check metadata and expose the `metadata()`
# to return a JSON representation of the metadata,
# read more at Check Metadata Model down below.
# - Enforce that each check requires to have the `execute()` function
from prowler.lib.check.models import Check, Check_Report_AWS
# Then you have to import the provider service client
# read more at the Service documentation.
from prowler.providers.aws.services.ec2.ec2_client import ec2_client
# For each check we need to create a python class called the same as the
# file which inherits from the Check class.
class ec2_ami_public(Check):
"""ec2_ami_public verifies if an EC2 AMI is publicly shared"""
# Then, within the check's class we need to create the "execute(self)"
# function, which is enforce by the "Check" class to implement
# the Check's interface and let Prowler to run this check.
def execute(self):
# Inside the execute(self) function we need to create
# the list of findings initialised to an empty list []
findings = []
# Then, using the service client we need to iterate by the resource we
# want to check, in this case EC2 AMIs stored in the
# "ec2_client.images" object.
for image in ec2_client.images:
# Once iterating for the images, we have to intialise
# the Check_Report_AWS class passing the check's metadata
# using the "metadata" function explained above.
report = Check_Report_AWS(self.metadata())
# For each Prowler check we MUST fill the following
# Check_Report_AWS fields:
# - region
# - resource_id
# - resource_arn
# - resource_tags
# - status
# - status_extended
report.region = image.region
report.resource_id = image.id
report.resource_arn = image.arn
# The resource_tags should be filled if the resource has the ability
# of having tags, please check the service first.
report.resource_tags = image.tags
# Then we need to create the business logic for the check
# which always should be simple because the Prowler service
# must do the heavy lifting and the check should be in charge
# of parsing the data provided
report.status = "PASS"
report.status_extended = f"EC2 AMI {image.id} is not public."
# In this example each "image" object has a boolean attribute
# called "public" to set if the AMI is publicly shared
if image.public:
report.status = "FAIL"
report.status_extended = (
f"EC2 AMI {image.id} is currently public."
)
# Then at the same level as the "report"
# object we need to append it to the findings list.
findings.append(report)
# Last thing to do is to return the findings list to Prowler
return findings
```
### Check Status
All the checks MUST fill the `report.status` and `report.status_extended` with the following criteria:
- Status -- `report.status`
- `PASS` --> If the check is passing against the configured value.
- `FAIL` --> If the check is failing against the configured value.
- `MANUAL` --> This value cannot be used unless a manual operation is required in order to determine if the `report.status` is whether `PASS` or `FAIL`.
- Status Extended -- `report.status_extended`
- MUST end in a dot `.`
- MUST include the service audited with the resource and a brief explanation of the result generated, e.g.: `EC2 AMI ami-0123456789 is not public.`
### Check Region
All the checks MUST fill the `report.region` with the following criteria:
- If the audited resource is regional use the `region` (the name changes depending on the provider: `location` in Azure and GCP and `namespace` in K8s) attribute within the resource object.
- If the audited resource is global use the `service_client.region` within the service client object.
### Resource ID, Name and ARN
All the checks MUST fill the `report.resource_id` and `report.resource_arn` with the following criteria:
- AWS
- Resource ID -- `report.resource_id`
- AWS Account --> Account Number `123456789012`
- AWS Resource --> Resource ID / Name
- Root resource --> `<root_account>`
- Resource ARN -- `report.resource_arn`
- AWS Account --> Root ARN `arn:aws:iam::123456789012:root`
- AWS Resource --> Resource ARN
- Root resource --> Resource Type ARN `f"arn:{service_client.audited_partition}:<service_name>:{service_client.region}:{service_client.audited_account}:<resource_type>"`
- GCP
- Resource ID -- `report.resource_id`
- GCP Resource --> Resource ID
- Resource Name -- `report.resource_name`
- GCP Resource --> Resource Name
- Azure
- Resource ID -- `report.resource_id`
- Azure Resource --> Resource ID
- Resource Name -- `report.resource_name`
- Azure Resource --> Resource Name
### Python Model
The following is the Python model for the check's class.
As per April 11th 2024 the `Check_Metadata_Model` can be found [here](https://github.com/prowler-cloud/prowler/blob/master/prowler/lib/check/models.py#L36-L82).
```python
class Check(ABC, Check_Metadata_Model):
"""Prowler Check"""
def __init__(self, **data):
"""Check's init function. Calls the CheckMetadataModel init."""
# Parse the Check's metadata file
metadata_file = (
os.path.abspath(sys.modules[self.__module__].__file__)[:-3]
+ ".metadata.json"
)
# Store it to validate them with Pydantic
data = Check_Metadata_Model.parse_file(metadata_file).dict()
# Calls parents init function
super().__init__(**data)
def metadata(self) -> dict:
"""Return the JSON representation of the check's metadata"""
return self.json()
@abstractmethod
def execute(self):
"""Execute the check's logic"""
```
### Using the audit config
Prowler has a [configuration file](../tutorials/configuration_file.md) which is used to pass certain configuration values to the checks, like the following:
```python title="ec2_securitygroup_with_many_ingress_egress_rules.py"
class ec2_securitygroup_with_many_ingress_egress_rules(Check):
def execute(self):
findings = []
# max_security_group_rules, default: 50
max_security_group_rules = ec2_client.audit_config.get(
"max_security_group_rules", 50
)
for security_group in ec2_client.security_groups:
```
```yaml title="config.yaml"
# AWS Configuration
aws:
# AWS EC2 Configuration
# aws.ec2_securitygroup_with_many_ingress_egress_rules
# The default value is 50 rules
max_security_group_rules: 50
```
As you can see in the above code, within the service client, in this case the `ec2_client`, there is an object called `audit_config` which is a Python dictionary containing the values read from the configuration file.
In order to use it, you have to check first if the value is present in the configuration file. If the value is not present, you can create it in the `config.yaml` file and then, read it from the check.
???+ note
It is mandatory to always use the `dictionary.get(value, default)` syntax to set a default value in the case the configuration value is not present.
## Check Metadata
Each Prowler check has metadata associated which is stored at the same level of the check's folder in a file called A `check_name.metadata.json` containing the check's metadata.
???+ note
We are going to include comments in this example metadata JSON but they cannot be included because the JSON format does not allow comments.
```json
{
# Provider holds the Prowler provider which the checks belongs to
"Provider": "aws",
# CheckID holds check name
"CheckID": "ec2_ami_public",
# CheckTitle holds the title of the check
"CheckTitle": "Ensure there are no EC2 AMIs set as Public.",
# CheckType holds Software and Configuration Checks, check more here
# https://docs.aws.amazon.com/securityhub/latest/userguide/asff-required-attributes.html#Types
"CheckType": [
"Infrastructure Security"
],
# ServiceName holds the provider service name
"ServiceName": "ec2",
# SubServiceName holds the service's subservice or resource used by the check
"SubServiceName": "ami",
# ResourceIdTemplate holds the unique ID for the resource used by the check
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
# Severity holds the check's severity, always in lowercase (critical, high, medium, low or informational)
"Severity": "critical",
# ResourceType only for AWS, holds the type from here
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html
"ResourceType": "Other",
# Description holds the title of the check, for now is the same as CheckTitle
"Description": "Ensure there are no EC2 AMIs set as Public.",
# Risk holds the check risk if the result is FAIL
"Risk": "When your AMIs are publicly accessible, they are available in the Community AMIs where everyone with an AWS account can use them to launch EC2 instances. Your AMIs could contain snapshots of your applications (including their data), therefore exposing your snapshots in this manner is not advised.",
# RelatedUrl holds an URL with more information about the check purpose
"RelatedUrl": "",
# Remediation holds the information to help the practitioner to fix the issue in the case of the check raise a FAIL
"Remediation": {
# Code holds different methods to remediate the FAIL finding
"Code": {
# CLI holds the command in the provider native CLI to remediate it
"CLI": "https://docs.prowler.com/checks/public_8#cli-command",
# NativeIaC holds the native IaC code to remediate it, use "https://docs.bridgecrew.io/docs"
"NativeIaC": "",
# Other holds the other commands, scripts or code to remediate it, use "https://www.trendmicro.com/cloudoneconformity"
"Other": "https://docs.prowler.com/checks/public_8#aws-console",
# Terraform holds the Terraform code to remediate it, use "https://docs.bridgecrew.io/docs"
"Terraform": ""
},
# Recommendation holds the recommendation for this check with a description and a related URL
"Recommendation": {
"Text": "We recommend your EC2 AMIs are not publicly accessible, or generally available in the Community AMIs.",
"Url": "https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/cancel-sharing-an-AMI.html"
}
},
# Categories holds the category or categories where the check can be included, if applied
"Categories": [
"internet-exposed"
],
# DependsOn is not actively used for the moment but it will hold other
# checks wich this check is dependant to
"DependsOn": [],
# RelatedTo is not actively used for the moment but it will hold other
# checks wich this check is related to
"RelatedTo": [],
# Notes holds additional information not covered in this file
"Notes": ""
}
```
### Remediation Code
For the Remediation Code we use the following knowledge base to fill it:
- Official documentation for the provider
- https://docs.bridgecrew.io
- https://www.trendmicro.com/cloudoneconformity
- https://github.com/cloudmatos/matos/tree/master/remediations
### RelatedURL and Recommendation
The RelatedURL field must be filled with an URL from the provider's official documentation like https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/sharingamis-intro.html
Also, if not present you can use the Risk and Recommendation texts from the TrendMicro [CloudConformity](https://www.trendmicro.com/cloudoneconformity) guide.
### Python Model
The following is the Python model for the check's metadata model. We use the Pydantic's [BaseModel](https://docs.pydantic.dev/latest/api/base_model/#pydantic.BaseModel) as the parent class.
As per August 5th 2023 the `Check_Metadata_Model` can be found [here](https://github.com/prowler-cloud/prowler/blob/master/prowler/lib/check/models.py#L34-L56).
```python
class Check_Metadata_Model(BaseModel):
"""Check Metadata Model"""
Provider: str
CheckID: str
CheckTitle: str
CheckType: list[str]
ServiceName: str
SubServiceName: str
ResourceIdTemplate: str
Severity: str
ResourceType: str
Description: str
Risk: str
RelatedUrl: str
Remediation: Remediation
Categories: list[str]
DependsOn: list[str]
RelatedTo: list[str]
Notes: str
# We set the compliance to None to
# store the compliance later if supplied
Compliance: list = None
```

View File

@@ -1,45 +0,0 @@
# Debugging
Debugging in Prowler make things easier!
If you are developing Prowler, it's possible that you will encounter some situations where you have to inspect the code in depth to fix some unexpected issues during the execution. To do that, if you are using VSCode you can run the code using the integrated debugger. Please, refer to this [documentation](https://code.visualstudio.com/docs/editor/debugging) for guidance about the debugger in VSCode.
The following file is an example of the [debugging configuration](https://code.visualstudio.com/docs/editor/debugging#_launch-configurations) file that you can add to [Virtual Studio Code](https://code.visualstudio.com/).
This file should inside the *.vscode* folder and its name has to be *launch.json*:
```json
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: Current File",
"type": "python",
"request": "launch",
"program": "prowler.py",
"args": [
"aws",
"-f",
"eu-west-1",
"--service",
"cloudwatch",
"--log-level",
"ERROR",
"-p",
"dev",
],
"console": "integratedTerminal",
"justMyCode": false
},
{
"name": "Python: Debug Tests",
"type": "python",
"request": "launch",
"program": "${file}",
"purpose": [
"debug-test"
],
"console": "integratedTerminal",
"justMyCode": false
}
]
}
```

View File

@@ -1,8 +0,0 @@
## Contribute with documentation
We use `mkdocs` to build this Prowler documentation site so you can easily contribute back with new docs or improving them. To install all necessary dependencies use `poetry install --with docs`.
1. Install `mkdocs` with your favorite package manager.
2. Inside the `prowler` repository folder run `mkdocs serve` and point your browser to `http://localhost:8000` and you will see live changes to your local copy of this documentation site.
3. Make all needed changes to docs or add new documents. To do so just edit existing md files inside `prowler/docs` and if you are adding a new section or file please make sure you add it to `mkdocs.yaml` file in the root folder of the Prowler repo.
4. Once you are done with changes, please send a pull request to us for review and merge. Thank you in advance!

View File

@@ -1,3 +0,0 @@
# Integration Tests
Coming soon ...

View File

@@ -1,3 +0,0 @@
# Create a new integration
Coming soon ...

View File

@@ -1,61 +0,0 @@
# Developer Guide
You can extend Prowler Open Source in many different ways, in most cases you will want to create your own checks and compliance security frameworks, here is where you can learn about how to get started with it. We also include how to create custom outputs, integrations and more.
## Get the code and install all dependencies
First of all, you need a version of Python 3.9 or higher and also pip installed to be able to install all dependencies required. Once that is satisfied go a head and clone the repo:
```
git clone https://github.com/prowler-cloud/prowler
cd prowler
```
For isolation and avoid conflicts with other environments, we recommend usage of `poetry`:
```
pip install poetry
```
Then install all dependencies including the ones for developers:
```
poetry install --with dev
poetry shell
```
## Contributing with your code or fixes to Prowler
This repo has git pre-commit hooks managed via the [pre-commit](https://pre-commit.com/) tool. [Install](https://pre-commit.com/#install) it how ever you like, then in the root of this repo run:
```shell
pre-commit install
```
You should get an output like the following:
```shell
pre-commit installed at .git/hooks/pre-commit
```
Before we merge any of your pull requests we pass checks to the code, we use the following tools and automation to make sure the code is secure and dependencies up-to-dated:
???+ note
These should have been already installed if you ran `poetry install --with dev`
- [`bandit`](https://pypi.org/project/bandit/) for code security review.
- [`safety`](https://pypi.org/project/safety/) and [`dependabot`](https://github.com/features/security) for dependencies.
- [`hadolint`](https://github.com/hadolint/hadolint) and [`dockle`](https://github.com/goodwithtech/dockle) for our containers security.
- [`Snyk`](https://docs.snyk.io/integrations/snyk-container-integrations/container-security-with-docker-hub-integration) in Docker Hub.
- [`clair`](https://github.com/quay/clair) in Amazon ECR.
- [`vulture`](https://pypi.org/project/vulture/), [`flake8`](https://pypi.org/project/flake8/), [`black`](https://pypi.org/project/black/) and [`pylint`](https://pypi.org/project/pylint/) for formatting and best practices.
You can see all dependencies in file `pyproject.toml`.
## Pull Request Checklist
If you create or review a PR in https://github.com/prowler-cloud/prowler please follow this checklist:
- [ ] Make sure you've read the Prowler Developer Guide at https://docs.prowler.cloud/en/latest/developer-guide/introduction/
- [ ] Are we following the style guide, hence installed all the linters and formatters? Please check https://docs.prowler.cloud/en/latest/developer-guide/introduction/#contributing-with-your-code-or-fixes-to-prowler
- [ ] Are we increasing/decreasing the test coverage? Please, review if we need to include/modify tests for the new code.
- [ ] Are we modifying outputs? Please review it carefully.
- [ ] Do we need to modify the Prowler documentation to reflect the changes introduced?
- [ ] Are we introducing possible breaking changes? Are we modifying a core feature?
## Want some swag as appreciation for your contribution?
If you are like us and you love swag, we are happy to thank you for your contribution with some laptop stickers or whatever other swag we may have at that time. Please, tell us more details and your pull request link in our [Slack workspace here](https://join.slack.com/t/prowler-workspace/shared_invite/zt-1hix76xsl-2uq222JIXrC7Q8It~9ZNog). You can also reach out to Toni de la Fuente on Twitter [here](https://twitter.com/ToniBlyx), his DMs are open.

View File

@@ -1,3 +0,0 @@
# Create a custom output format
Coming soon ...

View File

@@ -1,281 +0,0 @@
# Create a new Provider for Prowler
Here you can find how to create a new Provider in Prowler to give support for making all security checks needed and make your cloud safer!
## Introduction
Providers are the foundation on which Prowler is built, a simple definition for a cloud provider could be "third-party company that offers a platform where any IT resource you need is available at any time upon request". The most well-known cloud providers are Amazon Web Services, Azure from Microsoft and Google Cloud which are already supported by Prowler.
To create a new provider that is not supported now by Prowler and add your security checks you must create a new folder to store all the related files within it (services, checks, etc.). It must be store in route `prowler/providers/<new_provider_name>/`.
Inside that folder, you MUST create the following files and folders:
- A `lib` folder: to store all extra functions.
- A `services` folder: to store all [services](./services.md) to audit.
- An empty `__init__.py`: to make Python treat this service folder as a package.
- A `<new_provider_name>_provider.py`, containing all the provider's logic necessary to get authenticated in the provider, configurations and extra data useful for final report.
- A `models.py`, containing all the models necessary for the new provider.
## Provider
The structure for Prowler's providers is set up in such a way that they can be utilized through a generic service specific to each provider. This is achieved by passing the required parameters to the constructor, which in turn initializes all the necessary session values.
### Base Class
All the providers in Prowler inherits from the same [base class](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/common/provider.py). It is an [abstract base class](https://docs.python.org/3/library/abc.html) that defines the interface for all provider classes. The code of the class is the next:
```python title="Provider Base Class"
from abc import ABC, abstractmethod
from typing import Any
class Provider(ABC):
"""
The Provider class is an abstract base class that defines the interface for all provider classes in the auditing system.
Attributes:
type (property): The type of the provider.
identity (property): The identity of the provider for auditing.
session (property): The session of the provider for auditing.
audit_config (property): The audit configuration of the provider.
output_options (property): The output configuration of the provider for auditing.
Methods:
print_credentials(): Displays the provider's credentials used for auditing in the command-line interface.
setup_session(): Sets up the session for the provider.
get_output_mapping(): Returns the output mapping between the provider and the generic model.
validate_arguments(): Validates the arguments for the provider.
get_checks_to_execute_by_audit_resources(): Returns a set of checks based on the input resources to scan.
Note:
This is an abstract base class and should not be instantiated directly. Each provider should implement its own
version of the Provider class by inheriting from this base class and implementing the required methods and properties.
"""
@property
@abstractmethod
def type(self) -> str:
"""
type method stores the provider's type.
This method needs to be created in each provider.
"""
raise NotImplementedError()
@property
@abstractmethod
def identity(self) -> str:
"""
identity method stores the provider's identity to audit.
This method needs to be created in each provider.
"""
raise NotImplementedError()
@abstractmethod
def setup_session(self) -> Any:
"""
setup_session sets up the session for the provider.
This method needs to be created in each provider.
"""
raise NotImplementedError()
@property
@abstractmethod
def session(self) -> str:
"""
session method stores the provider's session to audit.
This method needs to be created in each provider.
"""
raise NotImplementedError()
@property
@abstractmethod
def audit_config(self) -> str:
"""
audit_config method stores the provider's audit configuration.
This method needs to be created in each provider.
"""
raise NotImplementedError()
@abstractmethod
def print_credentials(self) -> None:
"""
print_credentials is used to display in the CLI the provider's credentials used to audit.
This method needs to be created in each provider.
"""
raise NotImplementedError()
@property
@abstractmethod
def output_options(self) -> str:
"""
output_options method returns the provider's audit output configuration.
This method needs to be created in each provider.
"""
raise NotImplementedError()
@output_options.setter
@abstractmethod
def output_options(self, value: str) -> Any:
"""
output_options.setter sets the provider's audit output configuration.
This method needs to be created in each provider.
"""
raise NotImplementedError()
@abstractmethod
def get_output_mapping(self) -> dict:
"""
get_output_mapping returns the output mapping between the provider and the generic model.
This method needs to be created in each provider.
"""
raise NotImplementedError()
def validate_arguments(self) -> None:
"""
validate_arguments validates the arguments for the provider.
This method can be overridden in each provider if needed.
"""
raise NotImplementedError()
def get_checks_to_execute_by_audit_resources(self) -> set:
"""
get_checks_to_execute_by_audit_resources returns a set of checks based on the input resources to scan.
This is a fallback that returns None if the service has not implemented this function.
"""
return set()
@property
@abstractmethod
def mutelist(self):
"""
mutelist method returns the provider's mutelist.
This method needs to be created in each provider.
"""
raise NotImplementedError()
@mutelist.setter
@abstractmethod
def mutelist(self, path: str):
"""
mutelist.setter sets the provider's mutelist.
This method needs to be created in each provider.
"""
raise NotImplementedError()
```
### Provider Class
Due to the complexity and differences of each provider use the rest of the providers as a template for the implementation.
- [AWS](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/aws/aws_provider.py)
- [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)
To facilitate understanding here is a pseudocode of how the most basic provider could be with examples.
```python title="Provider Example Class"
# Library imports to authenticate in the Provider
from prowler.config.config import load_and_validate_config_file
from prowler.lib.logger import logger
from prowler.lib.mutelist.mutelist import parse_mutelist_file
from prowler.lib.utils.utils import print_boxes
from prowler.providers.common.models import Audit_Metadata
from prowler.providers.common.provider import Provider
from prowler.providers.<new_provider_name>.models import (
# All providers models needed
ProvierSessionModel,
ProvierIdentityModel,
ProvierOutputOptionsModel
)
class NewProvider(Provider):
# All properties from the class, some of this are properties in the base class
_type: str = "<provider_name>"
_session: <ProvierSessionModel>
_identity: <ProvierIdentityModel>
_audit_config: dict
_output_options: ProvierOutputOptionsModel
_mutelist: dict
audit_metadata: Audit_Metadata
def __init__(self, arguments):
"""
Initializes the NewProvider instance.
Args:
arguments (dict): A dictionary containing configuration arguments.
"""
logger.info("Setting <NewProviderName> provider ...")
# First get from arguments the necesary from the cloud acount (subscriptions or projects or whatever the provider use for storing services)
# Set the session with the method enforced by parent class
self._session = self.setup_session(credentials_file)
# Set the Identity class normaly the provider class give by Python provider library
self._identity = <ProvierIdentityModel>()
# Set the provider configuration
self._audit_config = load_and_validate_config_file(
self._type, arguments.config_file
)
# All enforced properties by the parent class
@property
def identity(self):
return self._identity
@property
def session(self):
return self._session
@property
def type(self):
return self._type
@property
def audit_config(self):
return self._audit_config
@property
def output_options(self):
return self._output_options
def setup_session(self, <all_needed_for_auth>):
"""
Sets up the Provider session.
Args:
<all_needed_for_auth> Can include all necessary arguments to setup the session
Returns:
Credentials necesary to communicate with the provider.
"""
pass
"""
This method is enforced by parent class and is used to print all relevant
information during the prowler execution as a header of execution.
Normally the Account ID, User name or stuff like this is displayed in colors using the colorama module (Fore).
"""
def print_credentials(self):
pass
```

View File

@@ -1,41 +0,0 @@
# Create a new security compliance framework
## Introduction
If you want to create or contribute with your own security frameworks or add public ones to Prowler you need to make sure the checks are available if not you have to create your own. Then create a compliance file per provider like in `prowler/compliance/<provider>/` and name it as `<framework>_<version>_<provider>.json` then follow the following format to create yours.
## Compliance Framework
Each file version of a framework will have the following structure at high level with the case that each framework needs to be generally identified, one requirement can be also called one control but one requirement can be linked to multiple prowler checks.:
- `Framework`: string. Distinguish name of the framework, like CIS
- `Provider`: string. Provider where the framework applies, such as AWS, Azure, OCI,...
- `Version`: string. Version of the framework itself, like 1.4 for CIS.
- `Requirements`: array of objects. Include all requirements or controls with the mapping to Prowler.
- `Requirements_Id`: string. Unique identifier per each requirement in the specific framework
- `Requirements_Description`: string. Description as in the framework.
- `Requirements_Attributes`: array of objects. Includes all needed attributes per each requirement, like levels, sections, etc. Whatever helps to create a dedicated report with the result of the findings. Attributes would be taken as closely as possible from the framework's own terminology directly.
- `Requirements_Checks`: array. Prowler checks that are needed to prove this requirement. It can be one or multiple checks. In case of no automation possible this can be empty.
```
{
"Framework": "<framework>-<provider>",
"Version": "<version>",
"Requirements": [
{
"Id": "<unique-id>",
"Description": "Requirement full description",
"Checks": [
"Here is the prowler check or checks that is going to be executed"
],
"Attributes": [
{
<Add here your custom attributes.>
}
]
},
...
]
}
```
Finally, to have a proper output file for your reports, your framework data model has to be created in `prowler/lib/outputs/models.py` and also the CLI table output in `prowler/lib/outputs/compliance.py`. Also, you need to add a new conditional in `prowler/lib/outputs/file_descriptors.py` if you create a new CSV model.

View File

@@ -1,240 +0,0 @@
# Create a new Provider Service
Here you can find how to create a new service, or to complement an existing one, for a Prowler Provider.
## Introduction
In Prowler, a service is basically a solution that is offered by a cloud provider i.e. [ec2](https://aws.amazon.com/ec2/). Essentially it is a class that stores all the necessary stuff that we will need later in the checks to audit some aspects of our Cloud account.
To create a new service, you will need to create a folder inside the specific provider, i.e. `prowler/providers/<provider>/services/<new_service_name>/`.
Inside that folder, you MUST create the following files:
- An empty `__init__.py`: to make Python treat this service folder as a package.
- A `<new_service_name>_service.py`, containing all the service's logic and API calls.
- A `<new_service_name>_client_.py`, containing the initialization of the service's class we have just created so the checks's checks can use it.
## Service
The Prowler's service structure is the following and the way to initialise it is just by importing the service client in a check.
### Service Base Class
All the Prowler provider's services inherits from a base class depending on the provider used.
- [AWS Service Base Class](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/aws/lib/service/service.py)
- [GCP Service Base Class](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/azure/lib/service/service.py)
- [Azure Service Base Class](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/gcp/lib/service/service.py)
- [Kubernetes Service Base Class](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/kubernetes/lib/service/service.py)
Each class is used to initialize the credentials and the API's clients to be used in the service. If some threading is used it must be coded there.
### Service Class
Due to the complexity and differences of each provider API we are going to use an example service to guide you in how can it be created.
The following is the `<new_service_name>_service.py` file:
```python title="Service Class"
from datetime import datetime
from typing import Optional
# The following is just for the AWS provider
from botocore.client import ClientError
# To use the Pydantic's BaseModel
from pydantic import BaseModel
# Prowler logging library
from prowler.lib.logger import logger
# Prowler resource filter, only for the AWS provider
from prowler.lib.scan_filters.scan_filters import is_resource_filtered
# Provider parent class
from prowler.providers.<provider>.lib.service.service import ServiceParentClass
# Create a class for the Service
################## <Service>
class <Service>(ServiceParentClass):
def __init__(self, provider):
# Call Service Parent Class __init__
# We use the __class__.__name__ to get it automatically
# from the Service Class name but you can pass a custom
# string if the provider's API service name is different
super().__init__(__class__.__name__, provider)
# Create an empty dictionary of items to be gathered,
# using the unique ID as the dictionary key
# e.g., instances
self.<items> = {}
# If you can parallelize by regions or locations
# you can use the __threading_call__ function
# available in the Service Parent Class
self.__threading_call__(self.__describe_<items>__)
# Optionally you can create another function to retrieve
# more data about each item without parallel
self.__describe_<item>__()
def __describe_<items>__(self, regional_client):
"""Get ALL <Service> <Items>"""
logger.info("<Service> - Describing <Items>...")
# We MUST include a try/except block in each function
try:
# Call to the provider API to retrieve the data we want
describe_<items>_paginator = regional_client.get_paginator("describe_<items>")
# Paginator to get every item
for page in describe_<items>_paginator.paginate():
# Another try/except within the loop for to continue looping
# if something unexpected happens
try:
for <item> in page["<Items>"]:
# For the AWS provider we MUST include the following lines to retrieve
# or not data for the resource passed as argument using the --resource-arn
if not self.audit_resources or (
is_resource_filtered(<item>["<item_arn>"], self.audit_resources)
):
# Then we have to include the retrieved resource in the object
# previously created
self.<items>[<item_unique_id>] =
<Item>(
arn=stack["<item_arn>"],
name=stack["<item_name>"],
tags=stack.get("Tags", []),
region=regional_client.region,
)
except Exception as error:
logger.error(
f"{<provider_specific_field>} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
# In the except part we have to use the following code to log the errors
except Exception as error:
# Depending on each provider we can use the following fields in the logger:
# - AWS: regional_client.region or self.region
# - GCP: project_id and location
# - Azure: subscription
logger.error(
f"{<provider_specific_field>} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def __describe_<item>__(self):
"""Get Details for a <Service> <Item>"""
logger.info("<Service> - Describing <Item> to get specific details...")
# We MUST include a try/except block in each function
try:
# Loop over the items retrieved in the previous function
for <item> in self.<items>:
# When we perform calls to the Provider API within a for loop we have
# to include another try/except block because in the cloud there are
# ephemeral resources that can be deleted at the time we are checking them
try:
<item>_details = self.regional_clients[<item>.region].describe_<item>(
<Attribute>=<item>.name
)
# For example, check if item is Public. Here is important if we are
# getting values from a dictionary we have to use the "dict.get()"
# function with a default value in the case this value is not present
<item>.public = <item>_details.get("Public", False)
# In this except block, for example for the AWS Provider we can use
# the botocore.ClientError exception and check for a specific error code
# to raise a WARNING instead of an ERROR if some resource is not present.
except ClientError as error:
if error.response["Error"]["Code"] == "InvalidInstanceID.NotFound":
logger.warning(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
else:
logger.error(
f"{<provider_specific_field>} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
continue
# In the except part we have to use the following code to log the errors
except Exception as error:
# Depending on each provider we can use the following fields in the logger:
# - AWS: regional_client.region or self.region
# - GCP: project_id and location
# - Azure: subscription
logger.error(
f"{<item>.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
```
???+note
To avoid fake findings, when Prowler can't retrieve the items, because an Access Denied or similar error, we set that items value as `None`.
#### Service Models
Service models are classes that are used in the service to design all that we need to store in each class object extrated from API calls. We use the Pydantic's [BaseModel](https://docs.pydantic.dev/latest/api/base_model/#pydantic.BaseModel) to take advantage of the data validation.
```python title="Service Model"
# In each service class we have to create some classes using
# the Pydantic's Basemodel for the resources we want to audit.
class <Item>(BaseModel):
"""<Item> holds a <Service> <Item>"""
arn: str
"""<Items>[].arn"""
name: str
"""<Items>[].name"""
region: str
"""<Items>[].region"""
public: bool
"""<Items>[].public"""
# We can create Optional attributes set to None by default
tags: Optional[list]
"""<Items>[].tags"""
```
#### Service Objects
In the service each group of resources should be created as a Python [dictionary](https://docs.python.org/3/tutorial/datastructures.html#dictionaries). This is because we are performing lookups all the time and the Python dictionary lookup has [O(1) complexity](https://en.wikipedia.org/wiki/Big_O_notation#Orders_of_common_functions).
We MUST set as the dictionary key a unique ID, like the resource Unique ID or ARN.
Example:
```python
self.vpcs = {}
self.vpcs["vpc-01234567890abcdef"] = VPC_Object_Class()
```
### Service Client
Each Prowler service requires a service client to use the service in the checks.
The following is the `<new_service_name>_client.py` containing the initialization of the service's class we have just created so the service's checks can use them:
```python
from prowler.providers.common.common import get_global_provider
from prowler.providers.<provider>.services.<new_service_name>.<new_service_name>_service import <Service>
<new_service_name>_client = <Service>(get_global_provider())
```
## Permissions
It is really important to check if the current Prowler's permissions for each provider are enough to implement a new service. If we need to include more please refer to the following documentaion and update it:
- 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

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