mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 04:51:51 +00:00
Merge branch 'master' into PRWLR-4669-Roles-Page-API-UI
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
name: "Custom CodeQL Config for API"
|
||||
name: "API - CodeQL Config"
|
||||
paths:
|
||||
- 'api/'
|
||||
- "api/"
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
name: "Custom CodeQL Config"
|
||||
paths-ignore:
|
||||
- 'api/'
|
||||
- 'ui/'
|
||||
@@ -0,0 +1,4 @@
|
||||
name: "SDK - CodeQL Config"
|
||||
paths-ignore:
|
||||
- "api/"
|
||||
- "ui/"
|
||||
@@ -1,3 +1,3 @@
|
||||
name: "Custom CodeQL Config for UI"
|
||||
name: "UI - CodeQL Config"
|
||||
paths:
|
||||
- "ui/"
|
||||
|
||||
+39
-2
@@ -5,6 +5,7 @@
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
# v5
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
@@ -14,6 +15,7 @@ updates:
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "pip"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
@@ -24,20 +26,55 @@ updates:
|
||||
- "dependencies"
|
||||
- "github_actions"
|
||||
|
||||
- package-ecosystem: "pip"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 10
|
||||
target-branch: master
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "npm"
|
||||
|
||||
# v4.6
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
open-pull-requests-limit: 10
|
||||
target-branch: v4.6
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "pip"
|
||||
- "v4"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
open-pull-requests-limit: 10
|
||||
target-branch: v4.6
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "github_actions"
|
||||
- "v4"
|
||||
|
||||
# v3
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
open-pull-requests-limit: 10
|
||||
target-branch: v3
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "pip"
|
||||
- "v3"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
interval: "monthly"
|
||||
open-pull-requests-limit: 10
|
||||
target-branch: v3
|
||||
labels:
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
name: API - Build and Push containers
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
paths:
|
||||
- "api/**"
|
||||
- ".github/workflows/api-build-lint-push-containers.yml"
|
||||
|
||||
# Uncomment the code below to test this action on PRs
|
||||
# pull_request:
|
||||
# branches:
|
||||
# - "master"
|
||||
# paths:
|
||||
# - "api/**"
|
||||
# - ".github/workflows/api-build-lint-push-containers.yml"
|
||||
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
env:
|
||||
# Tags
|
||||
LATEST_TAG: latest
|
||||
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
||||
|
||||
WORKING_DIRECTORY: ./api
|
||||
|
||||
# Container Registries
|
||||
PROWLERCLOUD_DOCKERHUB_REPOSITORY: prowlercloud
|
||||
PROWLERCLOUD_DOCKERHUB_IMAGE: prowler-api
|
||||
|
||||
jobs:
|
||||
# Build Prowler OSS container
|
||||
container-build-push:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ env.WORKING_DIRECTORY }}
|
||||
|
||||
steps:
|
||||
- name: Repository check
|
||||
working-directory: /tmp
|
||||
run: |
|
||||
[[ ${{ github.repository }} != "prowler-cloud/prowler" ]] && echo "This action only runs for prowler-cloud/prowler"; exit 0
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push container image (latest)
|
||||
# Comment the following line for testing
|
||||
if: github.event_name == 'push'
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ${{ env.WORKING_DIRECTORY }}
|
||||
# Set push: false for testing
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ env.LATEST_TAG }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Build and push container image (release)
|
||||
if: github.event_name == 'release'
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ${{ env.WORKING_DIRECTORY }}
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ env.RELEASE_TAG }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
@@ -9,11 +9,11 @@
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "API - CodeQL"
|
||||
name: API - CodeQL
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
branches:
|
||||
- "master"
|
||||
- "v3"
|
||||
- "v4.*"
|
||||
@@ -21,7 +21,7 @@ on:
|
||||
paths:
|
||||
- "api/**"
|
||||
pull_request:
|
||||
branches:
|
||||
branches:
|
||||
- "master"
|
||||
- "v3"
|
||||
- "v4.*"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: "API - Pull Request"
|
||||
name: API - Pull Request
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -69,6 +69,7 @@ jobs:
|
||||
|
||||
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@v45
|
||||
@@ -80,18 +81,21 @@ jobs:
|
||||
api/permissions/**
|
||||
api/README.md
|
||||
api/mkdocs.yml
|
||||
|
||||
- name: Install poetry
|
||||
working-directory: ./api
|
||||
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pipx install poetry
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: "poetry"
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: ./api
|
||||
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
|
||||
@@ -109,48 +113,59 @@ jobs:
|
||||
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
|
||||
run: |
|
||||
poetry lock --check
|
||||
|
||||
- name: Lint with ruff
|
||||
working-directory: ./api
|
||||
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
|
||||
run: |
|
||||
poetry run ruff check . --exclude contrib
|
||||
|
||||
- name: Check Format with ruff
|
||||
working-directory: ./api
|
||||
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
|
||||
run: |
|
||||
poetry run ruff format --check . --exclude contrib
|
||||
|
||||
- name: Lint with pylint
|
||||
working-directory: ./api
|
||||
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
|
||||
run: |
|
||||
poetry run pylint --disable=W,C,R,E -j 0 -rn -sn src/
|
||||
|
||||
- name: Bandit
|
||||
working-directory: ./api
|
||||
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
|
||||
run: |
|
||||
poetry run bandit -q -lll -x '*_test.py,./contrib/' -r .
|
||||
|
||||
- name: Safety
|
||||
working-directory: ./api
|
||||
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
|
||||
run: |
|
||||
poetry run safety check --ignore 70612,66963
|
||||
|
||||
- name: Vulture
|
||||
working-directory: ./api
|
||||
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
|
||||
run: |
|
||||
poetry run vulture --exclude "contrib,tests,conftest.py" --min-confidence 100 .
|
||||
|
||||
- name: Hadolint
|
||||
working-directory: ./api
|
||||
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
|
||||
run: |
|
||||
/tmp/hadolint Dockerfile --ignore=DL3013
|
||||
|
||||
- name: Test with pytest
|
||||
working-directory: ./api
|
||||
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
|
||||
run: |
|
||||
poetry run pytest --cov=./src/backend --cov-report=xml src/backend
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
|
||||
uses: codecov/codecov-action@v5
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
flags: api
|
||||
|
||||
@@ -1,42 +1,47 @@
|
||||
name: Automatic Backport
|
||||
name: Prowler - Automatic Backport
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
branches: ['master']
|
||||
types: ['labeled', 'closed']
|
||||
|
||||
env:
|
||||
# The prefix of the label that triggers the backport must not contain the branch name
|
||||
# so, for example, if the branch is 'master', the label should be 'backport-to-<branch>'
|
||||
BACKPORT_LABEL_PREFIX: backport-to-
|
||||
BACKPORT_LABEL_IGNORE: was-backported
|
||||
|
||||
jobs:
|
||||
backport:
|
||||
name: Backport PR
|
||||
if: github.event.pull_request.merged == true && !(contains(github.event.pull_request.labels.*.name, 'backport'))
|
||||
if: github.event.pull_request.merged == true && !(contains(github.event.pull_request.labels.*.name, 'backport')) && !(contains(github.event.pull_request.labels.*.name, 'was-backported'))
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
pull-requests: write
|
||||
contents: write
|
||||
steps:
|
||||
# Workaround not to fail the workflow if the PR does not need a backport
|
||||
# https://github.com/sorenlouv/backport-github-action/issues/127#issuecomment-2258561266
|
||||
- name: Check for backport labels
|
||||
id: check_labels
|
||||
run: |-
|
||||
labels='${{ toJSON(github.event.pull_request.labels.*.name) }}'
|
||||
echo "$labels"
|
||||
matched=$(echo "${labels}" | jq '. | map(select(startswith("backport-to-"))) | length')
|
||||
echo "matched=$matched"
|
||||
echo "matched=$matched" >> $GITHUB_OUTPUT
|
||||
- name: Check labels
|
||||
id: preview_label_check
|
||||
uses: docker://agilepathway/pull-request-label-checker:v1.6.55
|
||||
with:
|
||||
allow_failure: true
|
||||
prefix_mode: true
|
||||
any_of: ${{ env.BACKPORT_LABEL_PREFIX }}
|
||||
none_of: ${{ env.BACKPORT_LABEL_IGNORE }}
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Backport Action
|
||||
if: fromJSON(steps.check_labels.outputs.matched) > 0
|
||||
if: steps.preview_label_check.outputs.label_check == 'success'
|
||||
uses: sorenlouv/backport-github-action@v9.5.1
|
||||
with:
|
||||
github_token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||
auto_backport_label_prefix: backport-to-
|
||||
auto_backport_label_prefix: ${{ env.BACKPORT_LABEL_PREFIX }}
|
||||
|
||||
- name: Info log
|
||||
if: ${{ success() && fromJSON(steps.check_labels.outputs.matched) > 0 }}
|
||||
if: ${{ success() && steps.preview_label_check.outputs.label_check == 'success' }}
|
||||
run: cat ~/.backport/backport.info.log
|
||||
|
||||
- name: Debug log
|
||||
if: ${{ failure() && fromJSON(steps.check_labels.outputs.matched) > 0 }}
|
||||
if: ${{ failure() && steps.preview_label_check.outputs.label_check == 'success' }}
|
||||
run: cat ~/.backport/backport.debug.log
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Pull Request Documentation Link
|
||||
name: Prowler - Pull Request Documentation Link
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Find secrets
|
||||
name: Prowler - Find secrets
|
||||
|
||||
on: pull_request
|
||||
|
||||
@@ -11,9 +11,9 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: TruffleHog OSS
|
||||
uses: trufflesecurity/trufflehog@v3.84.1
|
||||
uses: trufflesecurity/trufflehog@v3.86.1
|
||||
with:
|
||||
path: ./
|
||||
base: ${{ github.event.repository.default_branch }}
|
||||
head: HEAD
|
||||
extra_args: --only-verified
|
||||
extra_args: --only-verified
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: "Pull Request Labeler"
|
||||
name: Prowler - PR Labeler
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
|
||||
+7
-3
@@ -1,9 +1,13 @@
|
||||
name: Build and Push containers
|
||||
name: SDK - Build and Push containers
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
# For `v3-latest`
|
||||
- "v3"
|
||||
# For `v4-latest`
|
||||
- "v4.6"
|
||||
# For `latest`
|
||||
- "master"
|
||||
paths-ignore:
|
||||
- ".github/**"
|
||||
@@ -85,8 +89,8 @@ jobs:
|
||||
echo "STABLE_TAG=v3-stable" >> "${GITHUB_ENV}"
|
||||
;;
|
||||
|
||||
|
||||
4)
|
||||
|
||||
4)
|
||||
echo "LATEST_TAG=v4-latest" >> "${GITHUB_ENV}"
|
||||
echo "STABLE_TAG=v4-stable" >> "${GITHUB_ENV}"
|
||||
;;
|
||||
@@ -9,11 +9,11 @@
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
name: SDK - CodeQL
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
branches:
|
||||
- "master"
|
||||
- "v3"
|
||||
- "v4.*"
|
||||
@@ -21,7 +21,7 @@ on:
|
||||
- 'ui/**'
|
||||
- 'api/**'
|
||||
pull_request:
|
||||
branches:
|
||||
branches:
|
||||
- "master"
|
||||
- "v3"
|
||||
- "v4.*"
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
config-file: ./.github/codeql/codeql-config.yml
|
||||
config-file: ./.github/codeql/sdk-codeql-config.yml
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
@@ -1,4 +1,4 @@
|
||||
name: "Pull Request"
|
||||
name: SDK - Pull Request
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -22,6 +22,7 @@ jobs:
|
||||
|
||||
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@v45
|
||||
@@ -36,17 +37,20 @@ jobs:
|
||||
README.md
|
||||
mkdocs.yml
|
||||
.backportrc.json
|
||||
|
||||
- name: Install poetry
|
||||
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pipx install poetry
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: "poetry"
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
|
||||
run: |
|
||||
@@ -57,44 +61,56 @@ jobs:
|
||||
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,ui,api
|
||||
|
||||
- name: Checking format with black
|
||||
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
|
||||
run: |
|
||||
poetry run black --exclude api ui --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/
|
||||
|
||||
- name: Bandit
|
||||
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
|
||||
run: |
|
||||
poetry run bandit -q -lll -x '*_test.py,./contrib/,./api/,./ui' -r .
|
||||
|
||||
- name: Safety
|
||||
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
|
||||
run: |
|
||||
poetry run safety check --ignore 70612 -r pyproject.toml
|
||||
|
||||
- name: Vulture
|
||||
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
|
||||
run: |
|
||||
poetry run vulture --exclude "contrib,api,ui" --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@v5
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
flags: prowler
|
||||
@@ -1,4 +1,4 @@
|
||||
name: PyPI release
|
||||
name: SDK - PyPI release
|
||||
|
||||
on:
|
||||
release:
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: Refresh regions of AWS services
|
||||
name: SDK - Refresh AWS services' regions
|
||||
|
||||
on:
|
||||
schedule:
|
||||
@@ -0,0 +1,81 @@
|
||||
name: UI - Build and Push containers
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
paths:
|
||||
- "ui/**"
|
||||
- ".github/workflows/ui-build-lint-push-containers.yml"
|
||||
|
||||
# Uncomment the below code to test this action on PRs
|
||||
# pull_request:
|
||||
# branches:
|
||||
# - "master"
|
||||
# paths:
|
||||
# - "ui/**"
|
||||
# - ".github/workflows/ui-build-lint-push-containers.yml"
|
||||
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
env:
|
||||
# Tags
|
||||
LATEST_TAG: latest
|
||||
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
||||
|
||||
WORKING_DIRECTORY: ./ui
|
||||
|
||||
# Container Registries
|
||||
PROWLERCLOUD_DOCKERHUB_REPOSITORY: prowlercloud
|
||||
PROWLERCLOUD_DOCKERHUB_IMAGE: prowler-ui
|
||||
|
||||
jobs:
|
||||
# Build Prowler OSS container
|
||||
container-build-push:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ env.WORKING_DIRECTORY }}
|
||||
|
||||
steps:
|
||||
- name: Repository check
|
||||
working-directory: /tmp
|
||||
run: |
|
||||
[[ ${{ github.repository }} != "prowler-cloud/prowler" ]] && echo "This action only runs for prowler-cloud/prowler"; exit 0
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push container image (latest)
|
||||
# Comment the following line for testing
|
||||
if: github.event_name == 'push'
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ${{ env.WORKING_DIRECTORY }}
|
||||
# Set push: false for testing
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ env.LATEST_TAG }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Build and push container image (release)
|
||||
if: github.event_name == 'release'
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ${{ env.WORKING_DIRECTORY }}
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ env.RELEASE_TAG }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
@@ -9,7 +9,7 @@
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "UI - CodeQL"
|
||||
name: UI - CodeQL
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: "UI - Pull Request"
|
||||
name: UI - Pull Request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -31,4 +31,4 @@ jobs:
|
||||
run: npm run healthcheck
|
||||
- name: Build the application
|
||||
working-directory: ./ui
|
||||
run: npm run build
|
||||
run: npm run build
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM python:3.12-alpine
|
||||
FROM python:3.12.8-alpine3.20
|
||||
|
||||
LABEL maintainer="https://github.com/prowler-cloud/prowler"
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<img align="center" src="https://github.com/prowler-cloud/prowler/blob/master/docs/img/prowler-logo-white.png#gh-dark-mode-only" width="50%" height="50%">
|
||||
</p>
|
||||
<p align="center">
|
||||
<b><i>Prowler SaaS </b> and <b>Prowler Open Source</b> are as dynamic and adaptable as the environment they’re meant to protect. Trusted by the leaders in security.
|
||||
<b><i>Prowler Open Source</b> is as dynamic and adaptable as the environment they’re meant to protect. Trusted by the leaders in security.
|
||||
</p>
|
||||
<p align="center">
|
||||
<b>Learn more at <a href="https://prowler.com">prowler.com</i></b>
|
||||
@@ -29,7 +29,7 @@
|
||||
<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>
|
||||
<a href="https://github.com/prowler-cloud/prowler/issues"><img alt="Issues" src="https://img.shields.io/github/issues/prowler-cloud/prowler"></a>
|
||||
<a href="https://github.com/prowler-cloud/prowler/releases"><img alt="Version" src="https://img.shields.io/github/v/release/prowler-cloud/prowler?include_prereleases"></a>
|
||||
<a href="https://github.com/prowler-cloud/prowler/releases"><img alt="Version" src="https://img.shields.io/github/v/release/prowler-cloud/prowler"></a>
|
||||
<a href="https://github.com/prowler-cloud/prowler/releases"><img alt="Version" src="https://img.shields.io/github/release-date/prowler-cloud/prowler"></a>
|
||||
<a href="https://github.com/prowler-cloud/prowler"><img alt="Contributors" src="https://img.shields.io/github/contributors-anon/prowler-cloud/prowler"></a>
|
||||
<a href="https://github.com/prowler-cloud/prowler"><img alt="License" src="https://img.shields.io/github/license/prowler-cloud/prowler"></a>
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
# 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, 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 Cloud</a>.
|
||||
|
||||
## Prowler App
|
||||
|
||||
@@ -139,6 +139,19 @@ cd src/backend
|
||||
python -m celery -A config.celery worker -l info -E
|
||||
```
|
||||
|
||||
**Commands to run the API Scheduler**
|
||||
|
||||
``` console
|
||||
git clone https://github.com/prowler-cloud/prowler
|
||||
cd prowler/api
|
||||
poetry install
|
||||
poetry shell
|
||||
set -a
|
||||
source .env
|
||||
cd src/backend
|
||||
python -m celery -A config.celery beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler
|
||||
```
|
||||
|
||||
**Commands to run the UI**
|
||||
|
||||
``` console
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
component_management:
|
||||
individual_components:
|
||||
- component_id: "prowler"
|
||||
paths:
|
||||
- "prowler/**"
|
||||
- component_id: "api"
|
||||
paths:
|
||||
- "api/**"
|
||||
|
||||
comment:
|
||||
layout: "header, diff, flags, components"
|
||||
@@ -37,7 +37,7 @@ services:
|
||||
- 3000:3000
|
||||
|
||||
postgres:
|
||||
image: postgres:16.3-alpine
|
||||
image: postgres:16.3-alpine3.20
|
||||
hostname: "postgres-db"
|
||||
volumes:
|
||||
- ./_data/postgres:/var/lib/postgresql/data
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ services:
|
||||
- ${UI_PORT:-3000}:${UI_PORT:-3000}
|
||||
|
||||
postgres:
|
||||
image: postgres:16.3-alpine
|
||||
image: postgres:16.3-alpine3.20
|
||||
hostname: "postgres-db"
|
||||
volumes:
|
||||
- ./_data/postgres:/var/lib/postgresql/data
|
||||
|
||||
@@ -51,14 +51,14 @@ For the AWS provider we have ways to test a Prowler check based on the following
|
||||
We use and contribute to the [Moto](https://github.com/getmoto/moto) library which allows us to easily mock out tests based on AWS infrastructure. **It's awesome!**
|
||||
|
||||
- AWS API calls covered by [Moto](https://github.com/getmoto/moto):
|
||||
- Service tests with `@mock_<service>`
|
||||
- Checks tests with `@mock_<service>`
|
||||
- Service tests with `@mock_aws`
|
||||
- Checks tests with `@mock_aws`
|
||||
- AWS API calls not covered by Moto:
|
||||
- Service test with `mock_make_api_call`
|
||||
- Checks tests with [MagicMock](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.MagicMock)
|
||||
- AWS API calls partially covered by Moto:
|
||||
- Service test with `@mock_<service>` and `mock_make_api_call`
|
||||
- Checks tests with `@mock_<service>` and `mock_make_api_call`
|
||||
- Service test with `@mock_aws` and `mock_make_api_call`
|
||||
- Checks tests with `@mock_aws` and `mock_make_api_call`
|
||||
|
||||
In the following section we are going to explain all of the above scenarios with examples. The main difference between those scenarios comes from if the [Moto](https://github.com/getmoto/moto) library covers the AWS API calls made by the service. You can check the covered API calls [here](https://github.com/getmoto/moto/blob/master/IMPLEMENTATION_COVERAGE.md).
|
||||
|
||||
@@ -70,7 +70,7 @@ This section is going to be divided based on the API coverage of the [Moto](http
|
||||
|
||||
#### API calls covered
|
||||
|
||||
If the [Moto](https://github.com/getmoto/moto) library covers the API calls we want to test, we can use the `@mock_<service>` decorator. This will mocked out all the API calls made to AWS keeping the state within the code decorated, in this case the test function.
|
||||
If the [Moto](https://github.com/getmoto/moto) library covers the API calls we want to test, we can use the `@mock_aws` decorator. This will mocked out all the API calls made to AWS keeping the state within the code decorated, in this case the test function.
|
||||
|
||||
```python
|
||||
# We need to import the unittest.mock to allow us to patch some objects
|
||||
@@ -80,8 +80,8 @@ from unittest import mock
|
||||
# Boto3 client and session to call the AWS APIs
|
||||
from boto3 import client, session
|
||||
|
||||
# Moto decorator for the IAM service we want to mock
|
||||
from moto import mock_iam
|
||||
# Moto decorator
|
||||
from moto import mock_aws
|
||||
|
||||
# Constants used
|
||||
AWS_ACCOUNT_NUMBER = "123456789012"
|
||||
@@ -91,10 +91,8 @@ AWS_REGION = "us-east-1"
|
||||
# We always name the test classes like Test_<check_name>
|
||||
class Test_iam_password_policy_uppercase:
|
||||
|
||||
# We include the Moto decorator for the service we want to use
|
||||
# You can include more than one if two or more services are
|
||||
# involved in test
|
||||
@mock_iam
|
||||
# We include the Moto decorator
|
||||
@mock_aws
|
||||
# We name the tests with test_<service>_<check_name>_<test_action>
|
||||
def test_iam_password_policy_no_uppercase_flag(self):
|
||||
# First, we have to create an IAM client
|
||||
@@ -238,7 +236,7 @@ To do so, you need to mock the `botocore.client.BaseClient._make_api_call` funct
|
||||
import boto3
|
||||
import botocore
|
||||
from unittest.mock import patch
|
||||
from moto import mock_iam
|
||||
from moto import mock_aws
|
||||
|
||||
# Original botocore _make_api_call function
|
||||
orig = botocore.client.BaseClient._make_api_call
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 274 KiB |
+20
-7
@@ -1,4 +1,4 @@
|
||||
**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, 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 Cloud</a>.
|
||||
|
||||
## Prowler App
|
||||
|
||||
@@ -29,7 +29,7 @@ It contains hundreds of controls covering CIS, NIST 800, NIST CSF, CISA, RBI, Fe
|
||||
|
||||
Prowler App can be installed in different ways, depending on your environment:
|
||||
|
||||
> See how to use Prowler App in the [Prowler App](tutorials/prowler-app.md) section.
|
||||
> See how to use Prowler App in the [Prowler App Tutorial](tutorials/prowler-app.md) section.
|
||||
|
||||
=== "Docker Compose"
|
||||
|
||||
@@ -65,6 +65,9 @@ Prowler App can be installed in different ways, depending on your environment:
|
||||
* `npm` installed: [npm installation](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).
|
||||
* `Docker Compose` installed: https://docs.docker.com/compose/install/.
|
||||
|
||||
???+ warning
|
||||
Make sure to have `api/.env` and `ui/.env.local` files with the required environment variables. You can find the required environment variables in the [`api/.env.template`](https://github.com/prowler-cloud/prowler/blob/master/api/.env.example) and [`ui/.env.template`](https://github.com/prowler-cloud/prowler/blob/master/ui/.env.template) files.
|
||||
|
||||
_Commands to run the API_:
|
||||
|
||||
``` bash
|
||||
@@ -95,6 +98,19 @@ Prowler App can be installed in different ways, depending on your environment:
|
||||
python -m celery -A config.celery worker -l info -E
|
||||
```
|
||||
|
||||
_Commands to run the API Scheduler_:
|
||||
|
||||
``` bash
|
||||
git clone https://github.com/prowler-cloud/prowler \
|
||||
cd prowler/api \
|
||||
poetry install \
|
||||
poetry shell \
|
||||
set -a \
|
||||
source .env \
|
||||
cd src/backend \
|
||||
python -m celery -A config.celery beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler
|
||||
```
|
||||
|
||||
_Commands to run the UI_:
|
||||
|
||||
``` bash
|
||||
@@ -107,9 +123,6 @@ Prowler App can be installed in different ways, depending on your environment:
|
||||
|
||||
> Enjoy Prowler App at http://localhost:3000 by signing up with your email and password.
|
||||
|
||||
???+ warning
|
||||
Make sure to have `api/.env` and `ui/.env.local` files with the required environment variables. You can find the required environment variables in the [`api/.env.template`](https://github.com/prowler-cloud/prowler/blob/master/api/.env.example) and [`ui/.env.template`](https://github.com/prowler-cloud/prowler/blob/master/ui/.env.template) files.
|
||||
|
||||
???+ warning
|
||||
Google and GitHub authentication is only available in [Prowler Cloud](https://prowler.com).
|
||||
|
||||
@@ -373,8 +386,8 @@ After successfully adding and testing your credentials, Prowler will start scann
|
||||
#### **View Results**
|
||||
While the scan is running, start exploring the findings in these sections:
|
||||
|
||||
- **Overview**: High-level summary of the scans. <img src="../../img/overview.png" alt="Overview" width="700"/>
|
||||
- **Compliance**: Insights into compliance status. <img src="../../img/compliance.png" alt="Compliance" width="700"/>
|
||||
- **Overview**: High-level summary of the scans. <img src="img/overview.png" alt="Overview" width="700"/>
|
||||
- **Compliance**: Insights into compliance status. <img src="img/compliance.png" alt="Compliance" width="700"/>
|
||||
|
||||
> See more details about the Prowler App usage in the [Prowler App](tutorials/prowler-app.md) section.
|
||||
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ As an **AWS Partner** and we have passed the [AWS Foundation Technical Review (F
|
||||
|
||||
## Reporting Vulnerabilities
|
||||
|
||||
If you would like to report a vulnerability or have a security concern regarding Prowler Open Source or Prowler SaaS service, please submit the information by contacting to us via [**support.prowler.com**](http://support.prowler.com).
|
||||
If you would like to report a vulnerability or have a security concern regarding Prowler Open Source or Prowler Cloud service, please submit the information by contacting to us via [**support.prowler.com**](http://support.prowler.com).
|
||||
|
||||
The information you share with the Prowler team as part of this process is kept confidential within Prowler. 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.
|
||||
|
||||
|
||||
@@ -22,32 +22,31 @@ In order to see which compliance frameworks are cover by Prowler, you can use op
|
||||
```sh
|
||||
prowler <provider> --list-compliance
|
||||
```
|
||||
Currently, the available frameworks are:
|
||||
|
||||
### AWS
|
||||
|
||||
- `aws_account_security_onboarding_aws`
|
||||
- `aws_audit_manager_control_tower_guardrails_aws`
|
||||
- `aws_foundational_security_best_practices_aws`
|
||||
- `aws_foundational_technical_review_aws`
|
||||
- `aws_well_architected_framework_reliability_pillar_aws`
|
||||
- `aws_well_architected_framework_security_pillar_aws`
|
||||
- `cis_1.4_aws`
|
||||
- `cis_1.5_aws`
|
||||
- `cis_2.0_aws`
|
||||
- `cis_2.0_gcp`
|
||||
- `cis_2.0_azure`
|
||||
- `cis_2.1_azure`
|
||||
- `cis_3.0_aws`
|
||||
- `cis_1.8_kubernetes`
|
||||
- `cisa_aws`
|
||||
- `ens_rd2022_aws`
|
||||
- `fedramp_low_revision_4_aws`
|
||||
- `fedramp_moderate_revision_4_aws`
|
||||
- `ffiec_aws`
|
||||
- `aws_foundational_technical_review_aws`
|
||||
- `gdpr_aws`
|
||||
- `gxp_21_cfr_part_11_aws`
|
||||
- `gxp_eu_annex_11_aws`
|
||||
- `hipaa_aws`
|
||||
- `iso27001_2013_aws`
|
||||
- `kisa_isms_p_2023_aws`
|
||||
- `kisa_isms_p_2023_korean_aws`
|
||||
- `mitre_attack_aws`
|
||||
- `nist_800_171_revision_2_aws`
|
||||
- `nist_800_53_revision_4_aws`
|
||||
@@ -57,6 +56,23 @@ Currently, the available frameworks are:
|
||||
- `rbi_cyber_security_framework_aws`
|
||||
- `soc2_aws`
|
||||
|
||||
### Azure
|
||||
|
||||
- `cis_2.0_azure`
|
||||
- `cis_2.1_azure`
|
||||
- `ens_rd2022_azure`
|
||||
- `mitre_attack_azure`
|
||||
|
||||
### GCP
|
||||
|
||||
- `cis_2.0_gcp`
|
||||
- `ens_rd2022_gcp`
|
||||
- `mitre_attack_gcp`
|
||||
|
||||
### Kubernetes
|
||||
|
||||
- `cis_1.8_kubernetes`
|
||||
|
||||
## List Requirements of Compliance Frameworks
|
||||
For each compliance framework, you can use option `--list-compliance-requirements` to list its requirements:
|
||||
```sh
|
||||
|
||||
@@ -75,6 +75,7 @@ The following list includes all the Azure checks with configurable variables tha
|
||||
| `app_ensure_php_version_is_latest` | `php_latest_version` | String |
|
||||
| `app_ensure_python_version_is_latest` | `python_latest_version` | String |
|
||||
| `app_ensure_java_version_is_latest` | `java_latest_version` | String |
|
||||
| `sqlserver_recommended_minimal_tls_version` | `recommended_minimal_tls_versions` | List of Strings |
|
||||
|
||||
|
||||
## GCP
|
||||
@@ -447,6 +448,14 @@ azure:
|
||||
# azure.app_ensure_java_version_is_latest
|
||||
java_latest_version: "17"
|
||||
|
||||
# Azure SQL Server
|
||||
# azure.sqlserver_minimal_tls_version
|
||||
recommended_minimal_tls_versions:
|
||||
[
|
||||
"1.2",
|
||||
"1.3"
|
||||
]
|
||||
|
||||
# GCP Configuration
|
||||
gcp:
|
||||
# GCP Compute Configuration
|
||||
|
||||
@@ -5,6 +5,9 @@ The **Prowler App** is a user-friendly interface for the Prowler CLI, providing
|
||||
After [installing](../index.md#prowler-app-installation) the **Prowler App**, access it at [http://localhost:3000](http://localhost:3000).
|
||||
You can also access to the auto-generated **Prowler API** documentation at [http://localhost:8080/api/v1/docs](http://localhost:8080/api/v1/docs) to see all the available endpoints, parameters and responses.
|
||||
|
||||
???+ note
|
||||
If you are a [Prowler Cloud](https://cloud.prowler.com/sign-in) user you can see API docs at [https://api.prowler.com/api/v1/docs](https://api.prowler.com/api/v1/docs)
|
||||
|
||||
## **Step 1: Sign Up**
|
||||
To get started, sign up using your email and password:
|
||||
|
||||
|
||||
+1
-2
@@ -34,7 +34,6 @@ theme:
|
||||
icon: material/weather-sunny
|
||||
name: Switch to light mode
|
||||
|
||||
|
||||
plugins:
|
||||
- search
|
||||
- git-revision-date-localized:
|
||||
@@ -112,7 +111,7 @@ nav:
|
||||
- Contact Us: contact.md
|
||||
- Troubleshooting: troubleshooting.md
|
||||
- About: about.md
|
||||
- Prowler SaaS: https://prowler.com
|
||||
- Prowler Cloud: https://prowler.com
|
||||
|
||||
# Customization
|
||||
extra:
|
||||
|
||||
Generated
+102
-97
@@ -775,17 +775,17 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "boto3"
|
||||
version = "1.35.71"
|
||||
version = "1.35.78"
|
||||
description = "The AWS SDK for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "boto3-1.35.71-py3-none-any.whl", hash = "sha256:e2969a246bb3208122b3c349c49cc6604c6fc3fc2b2f65d99d3e8ccd745b0c16"},
|
||||
{file = "boto3-1.35.71.tar.gz", hash = "sha256:3ed7172b3d4fceb6218bb0ec3668c4d40c03690939c2fca4f22bb875d741a07f"},
|
||||
{file = "boto3-1.35.78-py3-none-any.whl", hash = "sha256:5ef7166fe5060637b92af8dc152cd7acecf96b3fc9c5456706a886cadb534391"},
|
||||
{file = "boto3-1.35.78.tar.gz", hash = "sha256:fc8001519c8842e766ad3793bde3fbd0bb39e821a582fc12cf67876b8f3cf7f1"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
botocore = ">=1.35.71,<1.36.0"
|
||||
botocore = ">=1.35.78,<1.36.0"
|
||||
jmespath = ">=0.7.1,<2.0.0"
|
||||
s3transfer = ">=0.10.0,<0.11.0"
|
||||
|
||||
@@ -794,13 +794,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
|
||||
|
||||
[[package]]
|
||||
name = "botocore"
|
||||
version = "1.35.71"
|
||||
version = "1.35.79"
|
||||
description = "Low-level, data-driven core of boto 3."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "botocore-1.35.71-py3-none-any.whl", hash = "sha256:fc46e7ab1df3cef66dfba1633f4da77c75e07365b36f03bd64a3793634be8fc1"},
|
||||
{file = "botocore-1.35.71.tar.gz", hash = "sha256:f9fa058e0393660c3fe53c1e044751beb64b586def0bd2212448a7c328b0cbba"},
|
||||
{file = "botocore-1.35.79-py3-none-any.whl", hash = "sha256:e6b10bb9a357e3f5ca2e60f6dd15a85d311b9a476eb21b3c0c2a3b364a2897c8"},
|
||||
{file = "botocore-1.35.79.tar.gz", hash = "sha256:245bfdda1b1508539ddd1819c67a8a2cc81780adf0715d3de418d64c4247f346"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1099,73 +1099,73 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.6.8"
|
||||
version = "7.6.9"
|
||||
description = "Code coverage measurement for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "coverage-7.6.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b39e6011cd06822eb964d038d5dff5da5d98652b81f5ecd439277b32361a3a50"},
|
||||
{file = "coverage-7.6.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:63c19702db10ad79151a059d2d6336fe0c470f2e18d0d4d1a57f7f9713875dcf"},
|
||||
{file = "coverage-7.6.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3985b9be361d8fb6b2d1adc9924d01dec575a1d7453a14cccd73225cb79243ee"},
|
||||
{file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:644ec81edec0f4ad17d51c838a7d01e42811054543b76d4ba2c5d6af741ce2a6"},
|
||||
{file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f188a2402f8359cf0c4b1fe89eea40dc13b52e7b4fd4812450da9fcd210181d"},
|
||||
{file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e19122296822deafce89a0c5e8685704c067ae65d45e79718c92df7b3ec3d331"},
|
||||
{file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:13618bed0c38acc418896005732e565b317aa9e98d855a0e9f211a7ffc2d6638"},
|
||||
{file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:193e3bffca48ad74b8c764fb4492dd875038a2f9925530cb094db92bb5e47bed"},
|
||||
{file = "coverage-7.6.8-cp310-cp310-win32.whl", hash = "sha256:3988665ee376abce49613701336544041f2117de7b7fbfe91b93d8ff8b151c8e"},
|
||||
{file = "coverage-7.6.8-cp310-cp310-win_amd64.whl", hash = "sha256:f56f49b2553d7dd85fd86e029515a221e5c1f8cb3d9c38b470bc38bde7b8445a"},
|
||||
{file = "coverage-7.6.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:86cffe9c6dfcfe22e28027069725c7f57f4b868a3f86e81d1c62462764dc46d4"},
|
||||
{file = "coverage-7.6.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d82ab6816c3277dc962cfcdc85b1efa0e5f50fb2c449432deaf2398a2928ab94"},
|
||||
{file = "coverage-7.6.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13690e923a3932e4fad4c0ebfb9cb5988e03d9dcb4c5150b5fcbf58fd8bddfc4"},
|
||||
{file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4be32da0c3827ac9132bb488d331cb32e8d9638dd41a0557c5569d57cf22c9c1"},
|
||||
{file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44e6c85bbdc809383b509d732b06419fb4544dca29ebe18480379633623baafb"},
|
||||
{file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:768939f7c4353c0fac2f7c37897e10b1414b571fd85dd9fc49e6a87e37a2e0d8"},
|
||||
{file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e44961e36cb13c495806d4cac67640ac2866cb99044e210895b506c26ee63d3a"},
|
||||
{file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ea8bb1ab9558374c0ab591783808511d135a833c3ca64a18ec927f20c4030f0"},
|
||||
{file = "coverage-7.6.8-cp311-cp311-win32.whl", hash = "sha256:629a1ba2115dce8bf75a5cce9f2486ae483cb89c0145795603d6554bdc83e801"},
|
||||
{file = "coverage-7.6.8-cp311-cp311-win_amd64.whl", hash = "sha256:fb9fc32399dca861584d96eccd6c980b69bbcd7c228d06fb74fe53e007aa8ef9"},
|
||||
{file = "coverage-7.6.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e683e6ecc587643f8cde8f5da6768e9d165cd31edf39ee90ed7034f9ca0eefee"},
|
||||
{file = "coverage-7.6.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1defe91d41ce1bd44b40fabf071e6a01a5aa14de4a31b986aa9dfd1b3e3e414a"},
|
||||
{file = "coverage-7.6.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7ad66e8e50225ebf4236368cc43c37f59d5e6728f15f6e258c8639fa0dd8e6d"},
|
||||
{file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fe47da3e4fda5f1abb5709c156eca207eacf8007304ce3019eb001e7a7204cb"},
|
||||
{file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:202a2d645c5a46b84992f55b0a3affe4f0ba6b4c611abec32ee88358db4bb649"},
|
||||
{file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4674f0daa1823c295845b6a740d98a840d7a1c11df00d1fd62614545c1583787"},
|
||||
{file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:74610105ebd6f33d7c10f8907afed696e79c59e3043c5f20eaa3a46fddf33b4c"},
|
||||
{file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37cda8712145917105e07aab96388ae76e787270ec04bcb9d5cc786d7cbb8443"},
|
||||
{file = "coverage-7.6.8-cp312-cp312-win32.whl", hash = "sha256:9e89d5c8509fbd6c03d0dd1972925b22f50db0792ce06324ba069f10787429ad"},
|
||||
{file = "coverage-7.6.8-cp312-cp312-win_amd64.whl", hash = "sha256:379c111d3558272a2cae3d8e57e6b6e6f4fe652905692d54bad5ea0ca37c5ad4"},
|
||||
{file = "coverage-7.6.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb"},
|
||||
{file = "coverage-7.6.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63"},
|
||||
{file = "coverage-7.6.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365"},
|
||||
{file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002"},
|
||||
{file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3"},
|
||||
{file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022"},
|
||||
{file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e"},
|
||||
{file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b"},
|
||||
{file = "coverage-7.6.8-cp313-cp313-win32.whl", hash = "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146"},
|
||||
{file = "coverage-7.6.8-cp313-cp313-win_amd64.whl", hash = "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28"},
|
||||
{file = "coverage-7.6.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d"},
|
||||
{file = "coverage-7.6.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451"},
|
||||
{file = "coverage-7.6.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764"},
|
||||
{file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf"},
|
||||
{file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5"},
|
||||
{file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4"},
|
||||
{file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83"},
|
||||
{file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b"},
|
||||
{file = "coverage-7.6.8-cp313-cp313t-win32.whl", hash = "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71"},
|
||||
{file = "coverage-7.6.8-cp313-cp313t-win_amd64.whl", hash = "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc"},
|
||||
{file = "coverage-7.6.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ac47fa29d8d41059ea3df65bd3ade92f97ee4910ed638e87075b8e8ce69599e"},
|
||||
{file = "coverage-7.6.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:24eda3a24a38157eee639ca9afe45eefa8d2420d49468819ac5f88b10de84f4c"},
|
||||
{file = "coverage-7.6.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4c81ed2820b9023a9a90717020315e63b17b18c274a332e3b6437d7ff70abe0"},
|
||||
{file = "coverage-7.6.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd55f8fc8fa494958772a2a7302b0354ab16e0b9272b3c3d83cdb5bec5bd1779"},
|
||||
{file = "coverage-7.6.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f39e2f3530ed1626c66e7493be7a8423b023ca852aacdc91fb30162c350d2a92"},
|
||||
{file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:716a78a342679cd1177bc8c2fe957e0ab91405bd43a17094324845200b2fddf4"},
|
||||
{file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:177f01eeaa3aee4a5ffb0d1439c5952b53d5010f86e9d2667963e632e30082cc"},
|
||||
{file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:912e95017ff51dc3d7b6e2be158dedc889d9a5cc3382445589ce554f1a34c0ea"},
|
||||
{file = "coverage-7.6.8-cp39-cp39-win32.whl", hash = "sha256:4db3ed6a907b555e57cc2e6f14dc3a4c2458cdad8919e40b5357ab9b6db6c43e"},
|
||||
{file = "coverage-7.6.8-cp39-cp39-win_amd64.whl", hash = "sha256:428ac484592f780e8cd7b6b14eb568f7c85460c92e2a37cb0c0e5186e1a0d076"},
|
||||
{file = "coverage-7.6.8-pp39.pp310-none-any.whl", hash = "sha256:5c52a036535d12590c32c49209e79cabaad9f9ad8aa4cbd875b68c4d67a9cbce"},
|
||||
{file = "coverage-7.6.8.tar.gz", hash = "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85d9636f72e8991a1706b2b55b06c27545448baf9f6dbf51c4004609aacd7dcb"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:608a7fd78c67bee8936378299a6cb9f5149bb80238c7a566fc3e6717a4e68710"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96d636c77af18b5cb664ddf12dab9b15a0cfe9c0bde715da38698c8cea748bfa"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75cded8a3cff93da9edc31446872d2997e327921d8eed86641efafd350e1df1"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7b15f589593110ae767ce997775d645b47e5cbbf54fd322f8ebea6277466cec"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:44349150f6811b44b25574839b39ae35291f6496eb795b7366fef3bd3cf112d3"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d891c136b5b310d0e702e186d70cd16d1119ea8927347045124cb286b29297e5"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:db1dab894cc139f67822a92910466531de5ea6034ddfd2b11c0d4c6257168073"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-win32.whl", hash = "sha256:41ff7b0da5af71a51b53f501a3bac65fb0ec311ebed1632e58fc6107f03b9198"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-win_amd64.whl", hash = "sha256:35371f8438028fdccfaf3570b31d98e8d9eda8bb1d6ab9473f5a390969e98717"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:932fc826442132dde42ee52cf66d941f581c685a6313feebed358411238f60f9"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:085161be5f3b30fd9b3e7b9a8c301f935c8313dcf928a07b116324abea2c1c2c"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccc660a77e1c2bf24ddbce969af9447a9474790160cfb23de6be4fa88e3951c7"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c69e42c892c018cd3c8d90da61d845f50a8243062b19d228189b0224150018a9"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0824a28ec542a0be22f60c6ac36d679e0e262e5353203bea81d44ee81fe9c6d4"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4401ae5fc52ad8d26d2a5d8a7428b0f0c72431683f8e63e42e70606374c311a1"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98caba4476a6c8d59ec1eb00c7dd862ba9beca34085642d46ed503cc2d440d4b"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ee5defd1733fd6ec08b168bd4f5387d5b322f45ca9e0e6c817ea6c4cd36313e3"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-win32.whl", hash = "sha256:f2d1ec60d6d256bdf298cb86b78dd715980828f50c46701abc3b0a2b3f8a0dc0"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-win_amd64.whl", hash = "sha256:0d59fd927b1f04de57a2ba0137166d31c1a6dd9e764ad4af552912d70428c92b"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:99e266ae0b5d15f1ca8d278a668df6f51cc4b854513daab5cae695ed7b721cf8"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9901d36492009a0a9b94b20e52ebfc8453bf49bb2b27bca2c9706f8b4f5a554a"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abd3e72dd5b97e3af4246cdada7738ef0e608168de952b837b8dd7e90341f015"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff74026a461eb0660366fb01c650c1d00f833a086b336bdad7ab00cc952072b3"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65dad5a248823a4996724a88eb51d4b31587aa7aa428562dbe459c684e5787ae"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:22be16571504c9ccea919fcedb459d5ab20d41172056206eb2994e2ff06118a4"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f957943bc718b87144ecaee70762bc2bc3f1a7a53c7b861103546d3a403f0a6"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ae1387db4aecb1f485fb70a6c0148c6cdaebb6038f1d40089b1fc84a5db556f"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-win32.whl", hash = "sha256:1a330812d9cc7ac2182586f6d41b4d0fadf9be9049f350e0efb275c8ee8eb692"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-win_amd64.whl", hash = "sha256:b12c6b18269ca471eedd41c1b6a1065b2f7827508edb9a7ed5555e9a56dcfc97"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:899b8cd4781c400454f2f64f7776a5d87bbd7b3e7f7bda0cb18f857bb1334664"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:61f70dc68bd36810972e55bbbe83674ea073dd1dcc121040a08cdf3416c5349c"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a289d23d4c46f1a82d5db4abeb40b9b5be91731ee19a379d15790e53031c014"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e216d8044a356fc0337c7a2a0536d6de07888d7bcda76febcb8adc50bdbbd00"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c026eb44f744acaa2bda7493dad903aa5bf5fc4f2554293a798d5606710055d"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e77363e8425325384f9d49272c54045bbed2f478e9dd698dbc65dbc37860eb0a"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:777abfab476cf83b5177b84d7486497e034eb9eaea0d746ce0c1268c71652077"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:447af20e25fdbe16f26e84eb714ba21d98868705cb138252d28bc400381f6ffb"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-win32.whl", hash = "sha256:d872ec5aeb086cbea771c573600d47944eea2dcba8be5f3ee649bfe3cb8dc9ba"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-win_amd64.whl", hash = "sha256:fd1213c86e48dfdc5a0cc676551db467495a95a662d2396ecd58e719191446e1"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ba9e7484d286cd5a43744e5f47b0b3fb457865baf07bafc6bee91896364e1419"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e5ea1cf0872ee455c03e5674b5bca5e3e68e159379c1af0903e89f5eba9ccc3a"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d10e07aa2b91835d6abec555ec8b2733347956991901eea6ffac295f83a30e4"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13a9e2d3ee855db3dd6ea1ba5203316a1b1fd8eaeffc37c5b54987e61e4194ae"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c38bf15a40ccf5619fa2fe8f26106c7e8e080d7760aeccb3722664c8656b030"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d5275455b3e4627c8e7154feaf7ee0743c2e7af82f6e3b561967b1cca755a0be"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8f8770dfc6e2c6a2d4569f411015c8d751c980d17a14b0530da2d7f27ffdd88e"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8d2dfa71665a29b153a9681edb1c8d9c1ea50dfc2375fb4dac99ea7e21a0bcd9"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-win32.whl", hash = "sha256:5e6b86b5847a016d0fbd31ffe1001b63355ed309651851295315031ea7eb5a9b"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-win_amd64.whl", hash = "sha256:97ddc94d46088304772d21b060041c97fc16bdda13c6c7f9d8fcd8d5ae0d8611"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:adb697c0bd35100dc690de83154627fbab1f4f3c0386df266dded865fc50a902"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:be57b6d56e49c2739cdf776839a92330e933dd5e5d929966fbbd380c77f060be"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1592791f8204ae9166de22ba7e6705fa4ebd02936c09436a1bb85aabca3e599"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e12ae8cc979cf83d258acb5e1f1cf2f3f83524d1564a49d20b8bec14b637f08"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb5555cff66c4d3d6213a296b360f9e1a8e323e74e0426b6c10ed7f4d021e464"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b9389a429e0e5142e69d5bf4a435dd688c14478a19bb901735cdf75e57b13845"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:592ac539812e9b46046620341498caf09ca21023c41c893e1eb9dbda00a70cbf"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a27801adef24cc30871da98a105f77995e13a25a505a0161911f6aafbd66e678"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-win32.whl", hash = "sha256:8e3c3e38930cfb729cb8137d7f055e5a473ddaf1217966aa6238c88bd9fd50e6"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-win_amd64.whl", hash = "sha256:e28bf44afa2b187cc9f41749138a64435bf340adfcacb5b2290c070ce99839d4"},
|
||||
{file = "coverage-7.6.9-pp39.pp310-none-any.whl", hash = "sha256:f3ca78518bc6bc92828cd11867b121891d75cae4ea9e908d72030609b996db1b"},
|
||||
{file = "coverage-7.6.9.tar.gz", hash = "sha256:4a8d8977b0c6ef5aeadcb644da9e69ae0dcfe66ec7f368c89c72e058bd71164d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1719,13 +1719,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"]
|
||||
|
||||
[[package]]
|
||||
name = "google-api-python-client"
|
||||
version = "2.154.0"
|
||||
version = "2.155.0"
|
||||
description = "Google API Client Library for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "google_api_python_client-2.154.0-py2.py3-none-any.whl", hash = "sha256:a521bbbb2ec0ba9d6f307cdd64ed6e21eeac372d1bd7493a4ab5022941f784ad"},
|
||||
{file = "google_api_python_client-2.154.0.tar.gz", hash = "sha256:1b420062e03bfcaa1c79e2e00a612d29a6a934151ceb3d272fe150a656dc8f17"},
|
||||
{file = "google_api_python_client-2.155.0-py2.py3-none-any.whl", hash = "sha256:83fe9b5aa4160899079d7c93a37be306546a17e6686e2549bcc9584f1a229747"},
|
||||
{file = "google_api_python_client-2.155.0.tar.gz", hash = "sha256:25529f89f0d13abcf3c05c089c423fb2858ac16e0b3727543393468d0d7af67c"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2403,13 +2403,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "microsoft-kiota-abstractions"
|
||||
version = "1.6.2"
|
||||
version = "1.6.6"
|
||||
description = "Core abstractions for kiota generated libraries in Python"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.8"
|
||||
files = [
|
||||
{file = "microsoft_kiota_abstractions-1.6.2-py3-none-any.whl", hash = "sha256:8c2c777748e80f17dba3809b5d149585d9918198f0f94125e87432f7165ba80e"},
|
||||
{file = "microsoft_kiota_abstractions-1.6.2.tar.gz", hash = "sha256:dec30f0fb427a051003e94b5c6fcf266f4702ecbd9d6961e3966124b9cbe41bf"},
|
||||
{file = "microsoft_kiota_abstractions-1.6.6-py3-none-any.whl", hash = "sha256:29071715baf0d604c381c5d17be47f35e6e63a441dcfb5e9141963406b469d50"},
|
||||
{file = "microsoft_kiota_abstractions-1.6.6.tar.gz", hash = "sha256:2554495b00c9c25b43f6964a71b65c89a277bd6b50f4d0028a7febcec6c4fd67"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2583,13 +2583,13 @@ dev = ["click", "codecov", "mkdocs-gen-files", "mkdocs-git-authors-plugin", "mkd
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-material"
|
||||
version = "9.5.46"
|
||||
version = "9.5.48"
|
||||
description = "Documentation that simply works"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "mkdocs_material-9.5.46-py3-none-any.whl", hash = "sha256:98f0a2039c62e551a68aad0791a8d41324ff90c03a6e6cea381a384b84908b83"},
|
||||
{file = "mkdocs_material-9.5.46.tar.gz", hash = "sha256:ae2043f4238e572f9a40e0b577f50400d6fc31e2fef8ea141800aebf3bd273d7"},
|
||||
{file = "mkdocs_material-9.5.48-py3-none-any.whl", hash = "sha256:b695c998f4b939ce748adbc0d3bff73fa886a670ece948cf27818fa115dc16f8"},
|
||||
{file = "mkdocs_material-9.5.48.tar.gz", hash = "sha256:a582531e8b34f4c7ed38c29d5c44763053832cf2a32f7409567e0c74749a47db"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2769,13 +2769,13 @@ dev = ["bumpver", "isort", "mypy", "pylint", "pytest", "yapf"]
|
||||
|
||||
[[package]]
|
||||
name = "msgraph-sdk"
|
||||
version = "1.12.0"
|
||||
version = "1.14.0"
|
||||
description = "The Microsoft Graph Python SDK"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "msgraph_sdk-1.12.0-py3-none-any.whl", hash = "sha256:ac298b546b240391b0e407379d039db32862a56d6fe15cf7c5f7e77631fc6771"},
|
||||
{file = "msgraph_sdk-1.12.0.tar.gz", hash = "sha256:fbb5a8a9f6eed89b496f207eb35b6b4cfc7fefa75608aeef07477a3b2276d4fa"},
|
||||
{file = "msgraph_sdk-1.14.0-py3-none-any.whl", hash = "sha256:1a2f327dc8fbe5a5e6d0d84cf71d605e7b118b3066b1e16f011ccd8fd927bb03"},
|
||||
{file = "msgraph_sdk-1.14.0.tar.gz", hash = "sha256:5bbda80941c5d1794682753b8b291bd2ebed719a43d6de949fd0cd613b6dfbbd"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -3796,17 +3796,17 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "pylint"
|
||||
version = "3.3.1"
|
||||
version = "3.3.2"
|
||||
description = "python code static checker"
|
||||
optional = false
|
||||
python-versions = ">=3.9.0"
|
||||
files = [
|
||||
{file = "pylint-3.3.1-py3-none-any.whl", hash = "sha256:2f846a466dd023513240bc140ad2dd73bfc080a5d85a710afdb728c420a5a2b9"},
|
||||
{file = "pylint-3.3.1.tar.gz", hash = "sha256:9f3dcc87b1203e612b78d91a896407787e708b3f189b5fa0b307712d49ff0c6e"},
|
||||
{file = "pylint-3.3.2-py3-none-any.whl", hash = "sha256:77f068c287d49b8683cd7c6e624243c74f92890f767f106ffa1ddf3c0a54cb7a"},
|
||||
{file = "pylint-3.3.2.tar.gz", hash = "sha256:9ec054ec992cd05ad30a6df1676229739a73f8feeabf3912c995d17601052b01"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
astroid = ">=3.3.4,<=3.4.0-dev0"
|
||||
astroid = ">=3.3.5,<=3.4.0-dev0"
|
||||
colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
|
||||
dill = [
|
||||
{version = ">=0.2", markers = "python_version < \"3.11\""},
|
||||
@@ -3858,13 +3858,13 @@ diagrams = ["jinja2", "railroad-diagrams"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "8.3.3"
|
||||
version = "8.3.4"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"},
|
||||
{file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"},
|
||||
{file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"},
|
||||
{file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -4458,6 +4458,7 @@ files = [
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6"},
|
||||
@@ -4466,6 +4467,7 @@ files = [
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632"},
|
||||
@@ -4474,6 +4476,7 @@ files = [
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a"},
|
||||
@@ -4482,6 +4485,7 @@ files = [
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987"},
|
||||
@@ -4490,6 +4494,7 @@ files = [
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2c59aa6170b990d8d2719323e628aaf36f3bfbc1c26279c0eeeb24d05d2d11c7"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win32.whl", hash = "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12"},
|
||||
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b"},
|
||||
{file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"},
|
||||
@@ -4638,17 +4643,17 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "slack-sdk"
|
||||
version = "3.33.4"
|
||||
version = "3.33.5"
|
||||
description = "The Slack API Platform SDK for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "slack_sdk-3.33.4-py2.py3-none-any.whl", hash = "sha256:9f30cb3c9c07b441c49d53fc27f9f1837ad1592a7e9d4ca431f53cdad8826cc6"},
|
||||
{file = "slack_sdk-3.33.4.tar.gz", hash = "sha256:5e109847f6b6a22d227609226ba4ed936109dc00675bddeb7e0bee502d3ee7e0"},
|
||||
{file = "slack_sdk-3.33.5-py2.py3-none-any.whl", hash = "sha256:b8cccadfa3d4005a5e6529f52000d25c583f46173fda8e9136fdd2bc58923ff6"},
|
||||
{file = "slack_sdk-3.33.5.tar.gz", hash = "sha256:a5e74c00c99dc844ad93e501ab764a20d86fa8184bbc9432af217496f632c4ee"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
optional = ["SQLAlchemy (>=1.4,<3)", "aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "websocket-client (>=1,<2)", "websockets (>=9.1,<14)"]
|
||||
optional = ["SQLAlchemy (>=1.4,<3)", "aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "websocket-client (>=1,<2)", "websockets (>=9.1,<15)"]
|
||||
|
||||
[[package]]
|
||||
name = "smmap"
|
||||
@@ -4888,13 +4893,13 @@ zstd = ["zstandard (>=0.18.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "vulture"
|
||||
version = "2.13"
|
||||
version = "2.14"
|
||||
description = "Find dead code"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "vulture-2.13-py2.py3-none-any.whl", hash = "sha256:34793ba60488e7cccbecdef3a7fe151656372ef94fdac9fe004c52a4000a6d44"},
|
||||
{file = "vulture-2.13.tar.gz", hash = "sha256:78248bf58f5eaffcc2ade306141ead73f437339950f80045dce7f8b078e5a1aa"},
|
||||
{file = "vulture-2.14-py2.py3-none-any.whl", hash = "sha256:d9a90dba89607489548a49d557f8bac8112bd25d3cbc8aeef23e860811bd5ed9"},
|
||||
{file = "vulture-2.14.tar.gz", hash = "sha256:cb8277902a1138deeab796ec5bef7076a6e0248ca3607a3f3dee0b6d9e9b8415"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -5194,4 +5199,4 @@ type = ["pytest-mypy"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.9,<3.13"
|
||||
content-hash = "1acc901866ecfc2c0f3576b9e442d7a3b6e6522cac3d4d1b9301ed4232755cba"
|
||||
content-hash = "e00da6013a01923ac8e79017e7fdb221e09a3dcf581ad8d74e39550be64cc2f3"
|
||||
|
||||
@@ -12,7 +12,7 @@ from prowler.lib.logger import logger
|
||||
|
||||
timestamp = datetime.today()
|
||||
timestamp_utc = datetime.now(timezone.utc).replace(tzinfo=timezone.utc)
|
||||
prowler_version = "5.0.0"
|
||||
prowler_version = "5.1.0"
|
||||
html_logo_url = "https://github.com/prowler-cloud/prowler/"
|
||||
square_logo_img = "https://prowler.com/wp-content/uploads/logo-html.png"
|
||||
aws_logo = "https://user-images.githubusercontent.com/38561120/235953920-3e3fba08-0795-41dc-b480-9bea57db9f2e.png"
|
||||
|
||||
@@ -388,6 +388,14 @@ azure:
|
||||
# azure.app_ensure_java_version_is_latest
|
||||
java_latest_version: "17"
|
||||
|
||||
# Azure SQL Server
|
||||
# azure.sqlserver_minimal_tls_version
|
||||
recommended_minimal_tls_versions:
|
||||
[
|
||||
"1.2",
|
||||
"1.3",
|
||||
]
|
||||
|
||||
# GCP Configuration
|
||||
gcp:
|
||||
# GCP Compute Configuration
|
||||
|
||||
@@ -94,11 +94,12 @@ def get_cis_table(
|
||||
print(
|
||||
f"\nCompliance Status of {Fore.YELLOW}{compliance_framework.upper()}{Style.RESET_ALL} Framework:"
|
||||
)
|
||||
total_findings_count = len(fail_count) + len(pass_count) + len(muted_count)
|
||||
overview_table = [
|
||||
[
|
||||
f"{Fore.RED}{round(len(fail_count) / len(findings) * 100, 2)}% ({len(fail_count)}) FAIL{Style.RESET_ALL}",
|
||||
f"{Fore.GREEN}{round(len(pass_count) / len(findings) * 100, 2)}% ({len(pass_count)}) PASS{Style.RESET_ALL}",
|
||||
f"{orange_color}{round(len(muted_count) / len(findings) * 100, 2)}% ({len(muted_count)}) MUTED{Style.RESET_ALL}",
|
||||
f"{Fore.RED}{round(len(fail_count) / total_findings_count * 100, 2)}% ({len(fail_count)}) FAIL{Style.RESET_ALL}",
|
||||
f"{Fore.GREEN}{round(len(pass_count) / total_findings_count * 100, 2)}% ({len(pass_count)}) PASS{Style.RESET_ALL}",
|
||||
f"{orange_color}{round(len(muted_count) / total_findings_count * 100, 2)}% ({len(muted_count)}) MUTED{Style.RESET_ALL}",
|
||||
]
|
||||
]
|
||||
print(tabulate(overview_table, tablefmt="rounded_grid"))
|
||||
|
||||
@@ -95,11 +95,12 @@ def get_ens_table(
|
||||
print(
|
||||
f"\nEstado de Cumplimiento de {Fore.YELLOW}{compliance_framework.upper()}{Style.RESET_ALL}:"
|
||||
)
|
||||
total_findings_count = len(fail_count) + len(pass_count) + len(muted_count)
|
||||
overview_table = [
|
||||
[
|
||||
f"{Fore.RED}{round(len(fail_count) / len(findings) * 100, 2)}% ({len(fail_count)}) NO CUMPLE{Style.RESET_ALL}",
|
||||
f"{Fore.GREEN}{round(len(pass_count) / len(findings) * 100, 2)}% ({len(pass_count)}) CUMPLE{Style.RESET_ALL}",
|
||||
f"{orange_color}{round(len(muted_count) / len(findings) * 100, 2)}% ({len(muted_count)}) MUTED{Style.RESET_ALL}",
|
||||
f"{Fore.RED}{round(len(fail_count) / total_findings_count * 100, 2)}% ({len(fail_count)}) NO CUMPLE{Style.RESET_ALL}",
|
||||
f"{Fore.GREEN}{round(len(pass_count) / total_findings_count * 100, 2)}% ({len(pass_count)}) CUMPLE{Style.RESET_ALL}",
|
||||
f"{orange_color}{round(len(muted_count) / total_findings_count * 100, 2)}% ({len(muted_count)}) MUTED{Style.RESET_ALL}",
|
||||
]
|
||||
]
|
||||
print(tabulate(overview_table, tablefmt="rounded_grid"))
|
||||
|
||||
@@ -39,11 +39,12 @@ def get_generic_compliance_table(
|
||||
print(
|
||||
f"\nCompliance Status of {Fore.YELLOW}{compliance_framework.upper()}{Style.RESET_ALL} Framework:"
|
||||
)
|
||||
total_findings_count = len(fail_count) + len(pass_count) + len(muted_count)
|
||||
overview_table = [
|
||||
[
|
||||
f"{Fore.RED}{round(len(fail_count) / len(findings) * 100, 2)}% ({len(fail_count)}) FAIL{Style.RESET_ALL}",
|
||||
f"{Fore.GREEN}{round(len(pass_count) / len(findings) * 100, 2)}% ({len(pass_count)}) PASS{Style.RESET_ALL}",
|
||||
f"{orange_color}{round(len(muted_count) / len(findings) * 100, 2)}% ({len(muted_count)}) MUTED{Style.RESET_ALL}",
|
||||
f"{Fore.RED}{round(len(fail_count) / total_findings_count * 100, 2)}% ({len(fail_count)}) FAIL{Style.RESET_ALL}",
|
||||
f"{Fore.GREEN}{round(len(pass_count) / total_findings_count * 100, 2)}% ({len(pass_count)}) PASS{Style.RESET_ALL}",
|
||||
f"{orange_color}{round(len(muted_count) / total_findings_count * 100, 2)}% ({len(muted_count)}) MUTED{Style.RESET_ALL}",
|
||||
]
|
||||
]
|
||||
print(tabulate(overview_table, tablefmt="rounded_grid"))
|
||||
|
||||
@@ -61,11 +61,12 @@ def get_kisa_ismsp_table(
|
||||
print(
|
||||
f"\nCompliance Status of {Fore.YELLOW}{compliance_framework.upper()}{Style.RESET_ALL} Framework:"
|
||||
)
|
||||
total_findings_count = len(fail_count) + len(pass_count) + len(muted_count)
|
||||
overview_table = [
|
||||
[
|
||||
f"{Fore.RED}{round(len(fail_count) / len(findings) * 100, 2)}% ({len(fail_count)}) FAIL{Style.RESET_ALL}",
|
||||
f"{Fore.GREEN}{round(len(pass_count) / len(findings) * 100, 2)}% ({len(pass_count)}) PASS{Style.RESET_ALL}",
|
||||
f"{orange_color}{round(len(muted_count) / len(findings) * 100, 2)}% ({len(muted_count)}) MUTED{Style.RESET_ALL}",
|
||||
f"{Fore.RED}{round(len(fail_count) / total_findings_count * 100, 2)}% ({len(fail_count)}) FAIL{Style.RESET_ALL}",
|
||||
f"{Fore.GREEN}{round(len(pass_count) / total_findings_count * 100, 2)}% ({len(pass_count)}) PASS{Style.RESET_ALL}",
|
||||
f"{orange_color}{round(len(muted_count) / total_findings_count * 100, 2)}% ({len(muted_count)}) MUTED{Style.RESET_ALL}",
|
||||
]
|
||||
]
|
||||
print(tabulate(overview_table, tablefmt="rounded_grid"))
|
||||
|
||||
@@ -69,11 +69,12 @@ def get_mitre_attack_table(
|
||||
print(
|
||||
f"\nCompliance Status of {Fore.YELLOW}{compliance_framework.upper()}{Style.RESET_ALL} Framework:"
|
||||
)
|
||||
total_findings_count = len(fail_count) + len(pass_count) + len(muted_count)
|
||||
overview_table = [
|
||||
[
|
||||
f"{Fore.RED}{round(len(fail_count) / len(findings) * 100, 2)}% ({len(fail_count)}) FAIL{Style.RESET_ALL}",
|
||||
f"{Fore.GREEN}{round(len(pass_count) / len(findings) * 100, 2)}% ({len(pass_count)}) PASS{Style.RESET_ALL}",
|
||||
f"{orange_color}{round(len(muted_count) / len(findings) * 100, 2)}% ({len(muted_count)}) MUTED{Style.RESET_ALL}",
|
||||
f"{Fore.RED}{round(len(fail_count) / total_findings_count * 100, 2)}% ({len(fail_count)}) FAIL{Style.RESET_ALL}",
|
||||
f"{Fore.GREEN}{round(len(pass_count) / total_findings_count * 100, 2)}% ({len(pass_count)}) PASS{Style.RESET_ALL}",
|
||||
f"{orange_color}{round(len(muted_count) / total_findings_count * 100, 2)}% ({len(muted_count)}) MUTED{Style.RESET_ALL}",
|
||||
]
|
||||
]
|
||||
print(tabulate(overview_table, tablefmt="rounded_grid"))
|
||||
|
||||
@@ -7315,6 +7315,7 @@
|
||||
"ap-southeast-2",
|
||||
"ap-southeast-3",
|
||||
"ap-southeast-4",
|
||||
"ap-southeast-5",
|
||||
"ca-central-1",
|
||||
"ca-west-1",
|
||||
"eu-central-1",
|
||||
@@ -11097,6 +11098,7 @@
|
||||
"ap-southeast-1",
|
||||
"ap-southeast-2",
|
||||
"ap-southeast-3",
|
||||
"ap-southeast-4",
|
||||
"ca-central-1",
|
||||
"eu-central-1",
|
||||
"eu-central-2",
|
||||
|
||||
+13
-12
@@ -8,19 +8,20 @@ class autoscaling_group_launch_configuration_no_public_ip(Check):
|
||||
def execute(self):
|
||||
findings = []
|
||||
for group in autoscaling_client.groups:
|
||||
report = Check_Report_AWS(self.metadata())
|
||||
report.region = group.region
|
||||
report.resource_id = group.name
|
||||
report.resource_arn = group.arn
|
||||
report.resource_tags = group.tags
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Autoscaling group {group.name} does not have an associated launch configuration assigning a public IP address."
|
||||
|
||||
for lc in autoscaling_client.launch_configurations.values():
|
||||
if lc.name == group.launch_configuration_name and lc.public_ip:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Autoscaling group {group.name} has an associated launch configuration assigning a public IP address."
|
||||
if lc.name == group.launch_configuration_name:
|
||||
report = Check_Report_AWS(self.metadata())
|
||||
report.region = group.region
|
||||
report.resource_id = group.name
|
||||
report.resource_arn = group.arn
|
||||
report.resource_tags = group.tags
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Autoscaling group {group.name} does not have an associated launch configuration assigning a public IP address."
|
||||
|
||||
findings.append(report)
|
||||
if lc.public_ip:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Autoscaling group {group.name} has an associated launch configuration assigning a public IP address."
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
|
||||
+8
-11
@@ -8,20 +8,17 @@ class autoscaling_group_launch_configuration_requires_imdsv2(Check):
|
||||
def execute(self):
|
||||
findings = []
|
||||
for group in autoscaling_client.groups:
|
||||
report = Check_Report_AWS(self.metadata())
|
||||
report.region = group.region
|
||||
report.resource_id = group.name
|
||||
report.resource_arn = group.arn
|
||||
report.resource_tags = group.tags
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"Autoscaling group {group.name} has IMDSv2 disabled or not required."
|
||||
)
|
||||
|
||||
for (
|
||||
launch_configuration
|
||||
) in autoscaling_client.launch_configurations.values():
|
||||
if launch_configuration.name == group.launch_configuration_name:
|
||||
report = Check_Report_AWS(self.metadata())
|
||||
report.region = group.region
|
||||
report.resource_id = group.name
|
||||
report.resource_arn = group.arn
|
||||
report.resource_tags = group.tags
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Autoscaling group {group.name} has IMDSv2 disabled or not required."
|
||||
if (
|
||||
launch_configuration.http_endpoint == "enabled"
|
||||
and launch_configuration.http_tokens == "required"
|
||||
@@ -32,6 +29,6 @@ class autoscaling_group_launch_configuration_requires_imdsv2(Check):
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Autoscaling group {group.name} has metadata service disabled."
|
||||
|
||||
findings.append(report)
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
"CheckID": "awslambda_function_inside_vpc",
|
||||
"CheckTitle": "Ensure AWS Lambda Functions Are Deployed Inside a VPC",
|
||||
"CheckType": [],
|
||||
"ServiceName": "lambda",
|
||||
"ServiceName": "awslambda",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "arn:partition:lambda:region:account-id:function/function-name",
|
||||
"Severity": "low",
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
"CheckID": "awslambda_function_invoke_api_operations_cloudtrail_logging_enabled",
|
||||
"CheckTitle": "Check if Lambda functions invoke API operations are being recorded by CloudTrail.",
|
||||
"CheckType": [],
|
||||
"ServiceName": "lambda",
|
||||
"ServiceName": "awslambda",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "arn:partition:lambda:region:account-id:function/function-name",
|
||||
"Severity": "low",
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
"CheckID": "awslambda_function_no_secrets_in_code",
|
||||
"CheckTitle": "Find secrets in Lambda functions code.",
|
||||
"CheckType": [],
|
||||
"ServiceName": "lambda",
|
||||
"ServiceName": "awslambda",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "arn:partition:lambda:region:account-id:function/function-name",
|
||||
"Severity": "critical",
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
"CheckID": "awslambda_function_no_secrets_in_variables",
|
||||
"CheckTitle": "Find secrets in Lambda functions variables.",
|
||||
"CheckType": [],
|
||||
"ServiceName": "lambda",
|
||||
"ServiceName": "awslambda",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "arn:partition:lambda:region:account-id:function/function-name",
|
||||
"Severity": "critical",
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
"CheckID": "awslambda_function_not_publicly_accessible",
|
||||
"CheckTitle": "Check if Lambda functions have resource-based policy set as Public.",
|
||||
"CheckType": [],
|
||||
"ServiceName": "lambda",
|
||||
"ServiceName": "awslambda",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "arn:partition:lambda:region:account-id:function/function-name",
|
||||
"Severity": "critical",
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
import json
|
||||
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.providers.aws.services.awslambda.awslambda_client import awslambda_client
|
||||
|
||||
|
||||
def fixer(resource_id: str, region: str) -> bool:
|
||||
"""
|
||||
Remove the Lambda function's resource-based policy to prevent public access and add a new permission for the account.
|
||||
Specifically, this fixer deletes all permission statements associated with the Lambda function's policy and then adds a new permission.
|
||||
Requires the lambda:RemovePermission and lambda:AddPermission permissions.
|
||||
Permissions:
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "lambda:RemovePermission",
|
||||
"Resource": "*"
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "lambda:AddPermission",
|
||||
"Resource": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
Args:
|
||||
resource_id (str): The Lambda function name or ARN.
|
||||
region (str): AWS region where the Lambda function exists.
|
||||
Returns:
|
||||
bool: True if the operation is successful (policy removed and permission added), False otherwise.
|
||||
"""
|
||||
try:
|
||||
account_id = awslambda_client.audited_account
|
||||
|
||||
regional_client = awslambda_client.regional_clients[region]
|
||||
policy_response = regional_client.get_policy(FunctionName=resource_id)
|
||||
policy = json.loads(policy_response.get("Policy"))
|
||||
|
||||
for statement in policy.get("Statement", []):
|
||||
statement_id = statement.get("Sid")
|
||||
if statement_id:
|
||||
regional_client.remove_permission(
|
||||
FunctionName=resource_id, StatementId=statement_id
|
||||
)
|
||||
|
||||
regional_client.add_permission(
|
||||
FunctionName=resource_id,
|
||||
StatementId="ProwlerFixerStatement",
|
||||
Principal=account_id,
|
||||
Action="lambda:InvokeFunction",
|
||||
)
|
||||
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
"CheckID": "awslambda_function_url_cors_policy",
|
||||
"CheckTitle": "Check Lambda Function URL CORS configuration.",
|
||||
"CheckType": [],
|
||||
"ServiceName": "lambda",
|
||||
"ServiceName": "awslambda",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "arn:partition:lambda:region:account-id:function/function-name",
|
||||
"Severity": "medium",
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
"CheckID": "awslambda_function_url_public",
|
||||
"CheckTitle": "Check Public Lambda Function URL.",
|
||||
"CheckType": [],
|
||||
"ServiceName": "lambda",
|
||||
"ServiceName": "awslambda",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "arn:partition:lambda:region:account-id:function/function-name",
|
||||
"Severity": "high",
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
"CheckID": "awslambda_function_using_supported_runtimes",
|
||||
"CheckTitle": "Find obsolete Lambda runtimes.",
|
||||
"CheckType": [],
|
||||
"ServiceName": "lambda",
|
||||
"ServiceName": "awslambda",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "arn:partition:lambda:region:account-id:function/function-name",
|
||||
"Severity": "medium",
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
"CheckID": "awslambda_function_vpc_multi_az",
|
||||
"CheckTitle": "Check if AWS Lambda Function VPC is deployed Across Multiple Availability Zones",
|
||||
"CheckType": [],
|
||||
"ServiceName": "lambda",
|
||||
"ServiceName": "awslambda",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "arn:partition:lambda:region:account-id:function/function-name",
|
||||
"Severity": "medium",
|
||||
|
||||
@@ -183,21 +183,26 @@ class Backup(AWSService):
|
||||
def _list_recovery_points(self, regional_client):
|
||||
logger.info("Backup - Listing Recovery Points...")
|
||||
try:
|
||||
for backup_vault in self.backup_vaults:
|
||||
paginator = regional_client.get_paginator(
|
||||
"list_recovery_points_by_backup_vault"
|
||||
)
|
||||
for page in paginator.paginate(BackupVaultName=backup_vault.name):
|
||||
for recovery_point in page.get("RecoveryPoints", []):
|
||||
self.recovery_points.append(
|
||||
RecoveryPoint(
|
||||
arn=recovery_point.get("RecoveryPointArn"),
|
||||
backup_vault_name=backup_vault.name,
|
||||
encrypted=recovery_point.get("IsEncrypted", False),
|
||||
backup_vault_region=backup_vault.region,
|
||||
tags=[],
|
||||
)
|
||||
)
|
||||
if self.backup_vaults:
|
||||
for backup_vault in self.backup_vaults:
|
||||
paginator = regional_client.get_paginator(
|
||||
"list_recovery_points_by_backup_vault"
|
||||
)
|
||||
for page in paginator.paginate(BackupVaultName=backup_vault.name):
|
||||
for recovery_point in page.get("RecoveryPoints", []):
|
||||
arn = recovery_point.get("RecoveryPointArn")
|
||||
if arn:
|
||||
self.recovery_points.append(
|
||||
RecoveryPoint(
|
||||
arn=arn,
|
||||
backup_vault_name=backup_vault.name,
|
||||
encrypted=recovery_point.get(
|
||||
"IsEncrypted", False
|
||||
),
|
||||
backup_vault_region=backup_vault.region,
|
||||
tags=[],
|
||||
)
|
||||
)
|
||||
except ClientError as error:
|
||||
logger.error(
|
||||
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
|
||||
+2
-4
@@ -67,10 +67,8 @@ class cloudtrail_threat_detection_enumeration(Check):
|
||||
found_potential_enumeration = True
|
||||
report = Check_Report_AWS(self.metadata())
|
||||
report.region = cloudtrail_client.region
|
||||
report.resource_id = cloudtrail_client.audited_account
|
||||
report.resource_arn = cloudtrail_client._get_trail_arn_template(
|
||||
cloudtrail_client.region
|
||||
)
|
||||
report.resource_id = aws_identity_arn.split("/")[-1]
|
||||
report.resource_arn = aws_identity_arn
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Potential enumeration attack detected from AWS {aws_identity_type} {aws_identity_arn.split('/')[-1]} with an threshold of {identity_threshold}."
|
||||
findings.append(report)
|
||||
|
||||
+2
-4
@@ -67,10 +67,8 @@ class cloudtrail_threat_detection_llm_jacking(Check):
|
||||
found_potential_llm_jacking = True
|
||||
report = Check_Report_AWS(self.metadata())
|
||||
report.region = cloudtrail_client.region
|
||||
report.resource_id = cloudtrail_client.audited_account
|
||||
report.resource_arn = cloudtrail_client._get_trail_arn_template(
|
||||
cloudtrail_client.region
|
||||
)
|
||||
report.resource_id = aws_identity_arn.split("/")[-1]
|
||||
report.resource_arn = aws_identity_arn
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Potential LLM Jacking attack detected from AWS {aws_identity_type} {aws_identity_arn.split('/')[-1]} with an threshold of {identity_threshold}."
|
||||
findings.append(report)
|
||||
|
||||
+2
-4
@@ -69,10 +69,8 @@ class cloudtrail_threat_detection_privilege_escalation(Check):
|
||||
found_potential_privilege_escalation = True
|
||||
report = Check_Report_AWS(self.metadata())
|
||||
report.region = cloudtrail_client.region
|
||||
report.resource_id = cloudtrail_client.audited_account
|
||||
report.resource_arn = cloudtrail_client._get_trail_arn_template(
|
||||
cloudtrail_client.region
|
||||
)
|
||||
report.resource_id = aws_identity_arn.split("/")[-1]
|
||||
report.resource_arn = aws_identity_arn
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Potential privilege escalation attack detected from AWS {aws_identity_type} {aws_identity_arn.split('/')[-1]} with an threshold of {identity_threshold}."
|
||||
findings.append(report)
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
"ServiceName": "ec2",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "arn:partition:service:region:account-id",
|
||||
"Severity": "medium",
|
||||
"Severity": "high",
|
||||
"ResourceType": "AwsEc2Instance",
|
||||
"Description": "Ensure Instance Metadata Service Version 2 (IMDSv2) is enforced for EC2 instances at the account level to protect against SSRF vulnerabilities.",
|
||||
"Risk": "EC2 instances that use IMDSv1 are vulnerable to SSRF attacks.",
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
"ServiceName": "ec2",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
|
||||
"Severity": "medium",
|
||||
"Severity": "high",
|
||||
"ResourceType": "AwsEc2Instance",
|
||||
"Description": "Check if EC2 Instance Metadata Service Version 2 (IMDSv2) is Enabled and Required.",
|
||||
"Risk": "Using IMDSv2 will protect from misconfiguration and SSRF vulnerabilities. IMDSv1 will not.",
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.providers.aws.services.ec2.ec2_client import ec2_client
|
||||
from prowler.providers.aws.services.ec2.lib.security_groups import check_security_group
|
||||
|
||||
|
||||
def fixer(resource_id: str, region: str) -> bool:
|
||||
"""
|
||||
Revokes any ingress rule allowing CIFS ports (139, 445) from any address (0.0.0.0/0)
|
||||
for the EC2 instance's security groups.
|
||||
This fixer will only be triggered if the check identifies CIFS ports open to the Internet.
|
||||
Requires the ec2:RevokeSecurityGroupIngress permission.
|
||||
Permissions:
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "ec2:RevokeSecurityGroupIngress",
|
||||
"Resource": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
Args:
|
||||
resource_id (str): The EC2 instance ID.
|
||||
region (str): The AWS region where the EC2 instance exists.
|
||||
Returns:
|
||||
bool: True if the operation is successful (ingress rule revoked), False otherwise.
|
||||
"""
|
||||
try:
|
||||
regional_client = ec2_client.regional_clients[region]
|
||||
check_ports = [139, 445]
|
||||
for instance in ec2_client.instances:
|
||||
if instance.id == resource_id:
|
||||
for sg in ec2_client.security_groups.values():
|
||||
if sg.id in instance.security_groups:
|
||||
for ingress_rule in sg.ingress_rules:
|
||||
if check_security_group(
|
||||
ingress_rule, "tcp", check_ports, any_address=True
|
||||
):
|
||||
regional_client.revoke_security_group_ingress(
|
||||
GroupId=sg.id,
|
||||
IpPermissions=[ingress_rule],
|
||||
)
|
||||
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"Provider": "aws",
|
||||
"CheckID": "ec2_launch_template_imdsv2_required",
|
||||
"CheckTitle": "Amazon EC2 launch templates should have IMDSv2 enabled and required.",
|
||||
"CheckType": [
|
||||
"Software and Configuration Checks/AWS Security Best Practices"
|
||||
],
|
||||
"ServiceName": "ec2",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "arn:aws:ec2:region:account-id:launch-template/resource-id",
|
||||
"Severity": "high",
|
||||
"ResourceType": "AwsEc2LaunchTemplate",
|
||||
"Description": "This control checks if Amazon EC2 launch templates are configured with IMDSv2 enabled and required. The control fails if IMDSv2 is not enabled or required in the launch template versions.",
|
||||
"Risk": "Without IMDSv2 required, EC2 instances may be vulnerable to metadata service attacks, allowing unauthorized access to instance metadata, potentially leading to compromise of instance credentials or other sensitive data.",
|
||||
"RelatedUrl": "https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "aws ec2 modify-launch-template --launch-template-id <template-id> --version <version-number> --metadata-options HttpTokens=required",
|
||||
"NativeIaC": "",
|
||||
"Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/ec2-controls.html#ec2-170",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "To ensure EC2 launch templates have IMDSv2 enabled and required, update the template to configure the Instance Metadata Service Version 2 as required.",
|
||||
"Url": "https://docs.aws.amazon.com/autoscaling/ec2/userguide/create-launch-template.html#change-metadata-options"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_AWS
|
||||
from prowler.providers.aws.services.ec2.ec2_client import ec2_client
|
||||
|
||||
|
||||
class ec2_launch_template_imdsv2_required(Check):
|
||||
def execute(self):
|
||||
findings = []
|
||||
for template in ec2_client.launch_templates:
|
||||
report = Check_Report_AWS(self.metadata())
|
||||
report.region = template.region
|
||||
report.resource_id = template.id
|
||||
report.resource_arn = template.arn
|
||||
report.resource_tags = template.tags
|
||||
|
||||
versions_with_imdsv2_required = []
|
||||
versions_with_metadata_disabled = []
|
||||
versions_with_no_imdsv2 = []
|
||||
|
||||
for version in template.versions:
|
||||
if (
|
||||
version.template_data.http_endpoint == "enabled"
|
||||
and version.template_data.http_tokens == "required"
|
||||
):
|
||||
versions_with_imdsv2_required.append(str(version.version_number))
|
||||
elif version.template_data.http_endpoint == "disabled":
|
||||
versions_with_metadata_disabled.append(str(version.version_number))
|
||||
else:
|
||||
versions_with_no_imdsv2.append(str(version.version_number))
|
||||
|
||||
if versions_with_imdsv2_required:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"EC2 Launch Template {template.name} has IMDSv2 enabled and required in the following versions: {', '.join(versions_with_imdsv2_required)}."
|
||||
elif versions_with_metadata_disabled:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"EC2 Launch Template {template.name} has metadata service disabled in the following versions: {', '.join(versions_with_metadata_disabled)}."
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"EC2 Launch Template {template.name} has IMDSv2 disabled or not required in the following versions: {', '.join(versions_with_no_imdsv2)}."
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -10,7 +10,6 @@ from prowler.lib.scan_filters.scan_filters import is_resource_filtered
|
||||
from prowler.providers.aws.lib.service.service import AWSService
|
||||
|
||||
|
||||
################## EC2
|
||||
class EC2(AWSService):
|
||||
def __init__(self, provider):
|
||||
# Call AWSService's __init__
|
||||
@@ -569,6 +568,12 @@ class EC2(AWSService):
|
||||
),
|
||||
network_interfaces=enis,
|
||||
associate_public_ip_address=associate_public_ip,
|
||||
http_tokens=template_version["LaunchTemplateData"]
|
||||
.get("MetadataOptions", {})
|
||||
.get("HttpTokens", ""),
|
||||
http_endpoint=template_version["LaunchTemplateData"]
|
||||
.get("MetadataOptions", {})
|
||||
.get("HttpEndpoint", ""),
|
||||
),
|
||||
)
|
||||
)
|
||||
@@ -763,6 +768,8 @@ class TemplateData(BaseModel):
|
||||
user_data: str
|
||||
network_interfaces: Optional[list[NetworkInterface]]
|
||||
associate_public_ip_address: Optional[bool]
|
||||
http_tokens: Optional[str]
|
||||
http_endpoint: Optional[str]
|
||||
|
||||
|
||||
class LaunchTemplateVersion(BaseModel):
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.providers.aws.services.ecr.ecr_client import ecr_client
|
||||
|
||||
|
||||
def fixer(resource_id: str, region: str) -> bool:
|
||||
"""
|
||||
Modify the ECR repository's policy to remove public access.
|
||||
Specifically, this fixer delete the policy that had public access.
|
||||
Requires the ecr:DeleteRepositoryPolicy permission.
|
||||
Permissions:
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "ecr:DeleteRepositoryPolicy",
|
||||
"Resource": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
Args:
|
||||
resource_id (str): The ECR repository name.
|
||||
region (str): AWS region where the ECR repository exists.
|
||||
Returns:
|
||||
bool: True if the operation is successful (policy updated), False otherwise.
|
||||
"""
|
||||
try:
|
||||
regional_client = ecr_client.regional_clients[region]
|
||||
|
||||
regional_client.delete_repository_policy(repositoryName=resource_id)
|
||||
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
+1
-4
@@ -31,10 +31,7 @@ class firehose_stream_encrypted_at_rest(Check):
|
||||
f"Firehose Stream {stream.name} does have at rest encryption enabled."
|
||||
)
|
||||
|
||||
if (
|
||||
stream.kms_encryption != EncryptionStatus.ENABLED
|
||||
or not stream.kms_key_arn
|
||||
):
|
||||
if stream.kms_encryption != EncryptionStatus.ENABLED:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Firehose Stream {stream.name} does not have at rest encryption enabled."
|
||||
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.providers.aws.services.glacier.glacier_client import glacier_client
|
||||
|
||||
|
||||
def fixer(resource_id: str, region: str) -> bool:
|
||||
"""
|
||||
Modify the Glacier vault's policy to remove public access.
|
||||
Specifically, this fixer delete the vault policy that has public access.
|
||||
Requires the glacier:DeleteVaultAccessPolicy permission.
|
||||
Permissions:
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "glacier:DeleteVaultAccessPolicy",
|
||||
"Resource": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
Args:
|
||||
resource_id (str): The Glacier vault name.
|
||||
region (str): AWS region where the Glacier vault exists.
|
||||
Returns:
|
||||
bool: True if the operation is successful (policy updated), False otherwise.
|
||||
"""
|
||||
try:
|
||||
regional_client = glacier_client.regional_clients[region]
|
||||
|
||||
regional_client.delete_vault_access_policy(vaultName=resource_id)
|
||||
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
+2
-2
@@ -49,7 +49,7 @@ class iam_rotate_access_key_90_days(Check):
|
||||
old_access_keys = True
|
||||
report = Check_Report_AWS(self.metadata())
|
||||
report.region = iam_client.region
|
||||
report.resource_id = user["user"]
|
||||
report.resource_id = f"{user['user']}-access-key-1"
|
||||
report.resource_arn = user["arn"]
|
||||
report.resource_tags = user_tags
|
||||
report.status = "FAIL"
|
||||
@@ -66,7 +66,7 @@ class iam_rotate_access_key_90_days(Check):
|
||||
old_access_keys = True
|
||||
report = Check_Report_AWS(self.metadata())
|
||||
report.region = iam_client.region
|
||||
report.resource_id = user["user"]
|
||||
report.resource_id = f"{user['user']}-access-key-2"
|
||||
report.resource_arn = user["arn"]
|
||||
report.resource_tags = user_tags
|
||||
report.status = "FAIL"
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.providers.aws.services.opensearch.opensearch_client import (
|
||||
opensearch_client,
|
||||
)
|
||||
|
||||
|
||||
def fixer(resource_id: str, region: str) -> bool:
|
||||
"""
|
||||
Modify the OpenSearch domain's resource-based policy to remove public access.
|
||||
Specifically, this fixer update the domain config and add an empty policy to remove the old one.
|
||||
Requires the es:UpdateDomainConfig permission.
|
||||
Permissions:
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "es:UpdateDomainConfig",
|
||||
"Resource": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
Args:
|
||||
resource_id (str): The OpenSearch domain name.
|
||||
region (str): AWS region where the OpenSearch domain exists.
|
||||
Returns:
|
||||
bool: True if the operation is successful (policy updated), False otherwise.
|
||||
"""
|
||||
try:
|
||||
regional_client = opensearch_client.regional_clients[region]
|
||||
|
||||
regional_client.update_domain_config(
|
||||
DomainName=resource_id,
|
||||
AccessPolicies="",
|
||||
)
|
||||
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
+2
-2
@@ -6,8 +6,8 @@ class rds_cluster_non_default_port(Check):
|
||||
def execute(self):
|
||||
findings = []
|
||||
default_ports = {
|
||||
3306: ["mysql", "mariadb"],
|
||||
5432: ["postgres"],
|
||||
3306: ["mysql", "mariadb", "aurora-mysql"],
|
||||
5432: ["postgres", "aurora-postgresql"],
|
||||
1521: ["oracle"],
|
||||
1433: ["sqlserver"],
|
||||
50000: ["db2"],
|
||||
|
||||
+12
-9
@@ -37,18 +37,21 @@ class rds_instance_no_public_access(Check):
|
||||
):
|
||||
report.status_extended = f"RDS Instance {db_instance.id} is set as publicly accessible and security group {security_group.name} ({security_group.id}) has {db_instance.engine} port {db_instance_port} open to the Internet at endpoint {db_instance.endpoint.get('Address')} but is not in a public subnet."
|
||||
public_sg = True
|
||||
if db_instance.subnet_ids:
|
||||
for subnet_id in db_instance.subnet_ids:
|
||||
if (
|
||||
subnet_id in vpc_client.vpc_subnets
|
||||
and vpc_client.vpc_subnets[
|
||||
subnet_id
|
||||
].public
|
||||
):
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"RDS Instance {db_instance.id} is set as publicly accessible and security group {security_group.name} ({security_group.id}) has {db_instance.engine} port {db_instance_port} open to the Internet at endpoint {db_instance.endpoint.get('Address')} in a public subnet {subnet_id}."
|
||||
break
|
||||
if public_sg:
|
||||
break
|
||||
if public_sg:
|
||||
break
|
||||
if db_instance.subnet_ids:
|
||||
for subnet_id in db_instance.subnet_ids:
|
||||
if (
|
||||
subnet_id in vpc_client.vpc_subnets
|
||||
and vpc_client.vpc_subnets[subnet_id].public
|
||||
):
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"RDS Instance {db_instance.id} is set as publicly accessible and security group {security_group.name} ({security_group.id}) has {db_instance.engine} port {db_instance_port} open to the Internet at endpoint {db_instance.endpoint.get('Address')} in a public subnet {subnet_id}."
|
||||
break
|
||||
|
||||
findings.append(report)
|
||||
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@ class rds_instance_non_default_port(Check):
|
||||
def execute(self):
|
||||
findings = []
|
||||
default_ports = {
|
||||
3306: ["mysql", "mariadb"],
|
||||
5432: ["postgres"],
|
||||
3306: ["mysql", "mariadb", "aurora-mysql"],
|
||||
5432: ["postgres", "aurora-postgresql"],
|
||||
1521: ["oracle"],
|
||||
1433: ["sqlserver"],
|
||||
50000: ["db2"],
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.providers.aws.services.s3.s3_client import s3_client
|
||||
|
||||
|
||||
def fixer(resource_id: str, region: str) -> bool:
|
||||
"""
|
||||
Modify the S3 bucket's public access settings to block all public access.
|
||||
Specifically, this fixer configures the bucket's public access block settings to
|
||||
prevent any public access (ACLs and policies). Requires the s3:PutBucketPublicAccessBlock
|
||||
permission to modify the public access settings.
|
||||
Permissions:
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "s3:PutBucketPublicAccessBlock",
|
||||
"Resource": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
Args:
|
||||
resource_id (str): The S3 bucket name.
|
||||
region (str): AWS region where the S3 bucket exists.
|
||||
Returns:
|
||||
bool: True if the operation is successful (public access is blocked),
|
||||
False otherwise.
|
||||
"""
|
||||
try:
|
||||
regional_client = s3_client.regional_clients[region]
|
||||
regional_client.put_public_access_block(
|
||||
Bucket=resource_id,
|
||||
PublicAccessBlockConfiguration={
|
||||
"BlockPublicAcls": True,
|
||||
"IgnorePublicAcls": True,
|
||||
"BlockPublicPolicy": True,
|
||||
"RestrictPublicBuckets": True,
|
||||
},
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.providers.aws.services.s3.s3_client import s3_client
|
||||
|
||||
|
||||
def fixer(resource_id: str, region: str) -> bool:
|
||||
"""
|
||||
Modify the S3 bucket ACL to restrict public read access.
|
||||
Specifically, this fixer sets the ACL of the bucket to 'private' to prevent
|
||||
any public access to the S3 bucket.
|
||||
Requires the s3:PutBucketAcl permission.
|
||||
|
||||
Permissions:
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "s3:PutBucketAcl",
|
||||
"Resource": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Args:
|
||||
resource_id (str): The S3 bucket name.
|
||||
region (str): AWS region where the S3 bucket exists.
|
||||
|
||||
Returns:
|
||||
bool: True if the operation is successful (bucket access is updated), False otherwise.
|
||||
"""
|
||||
try:
|
||||
regional_client = s3_client.regional_clients[region]
|
||||
regional_client.put_bucket_acl(Bucket=resource_id, ACL="private")
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.providers.aws.services.s3.s3_client import s3_client
|
||||
|
||||
|
||||
def fixer(resource_id: str, region: str) -> bool:
|
||||
"""
|
||||
Modify the S3 bucket ACL to restrict public write access.
|
||||
Specifically, this fixer sets the ACL of the bucket to 'private' to prevent
|
||||
public write access to the S3 bucket. Requires the s3:PutBucketAcl permission.
|
||||
Permissions:
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "s3:PutBucketAcl",
|
||||
"Resource": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
Args:
|
||||
resource_id (str): The S3 bucket id.
|
||||
region (str): AWS region where the S3 bucket exists.
|
||||
Returns:
|
||||
bool: True if the operation is successful (bucket access is updated), False otherwise.
|
||||
"""
|
||||
try:
|
||||
regional_client = s3_client.regional_clients[region]
|
||||
regional_client.put_bucket_acl(Bucket=resource_id, ACL="private")
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
import json
|
||||
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.providers.aws.services.sqs.sqs_client import sqs_client
|
||||
|
||||
|
||||
def fixer(resource_id: str, region: str) -> bool:
|
||||
"""
|
||||
Modify the SQS queue's resource-based policy to remove public access and replace with trusted account access.
|
||||
Specifically, this fixer checks if any statement has a public Principal (e.g., "*" or "CanonicalUser")
|
||||
and replaces it with the ARN of the trusted AWS account.
|
||||
Requires the sqs:SetQueueAttributes permission.
|
||||
Permissions:
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "sqs:SetQueueAttributes",
|
||||
"Resource": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
Args:
|
||||
resource_id (str): The SQS queue name or ARN.
|
||||
region (str): AWS region where the SQS queue exists.
|
||||
Returns:
|
||||
bool: True if the operation is successful (policy updated), False otherwise.
|
||||
"""
|
||||
try:
|
||||
account_id = sqs_client.audited_account
|
||||
audited_partition = sqs_client.audited_partition
|
||||
|
||||
regional_client = sqs_client.regional_clients[region]
|
||||
|
||||
queue_name = resource_id.split("/")[-1]
|
||||
|
||||
trusted_policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "ProwlerFixerStatement",
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"AWS": account_id,
|
||||
},
|
||||
"Action": "sqs:*",
|
||||
"Resource": f"arn:{audited_partition}:sqs:{region}:{account_id}:{queue_name}",
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
regional_client.set_queue_attributes(
|
||||
QueueUrl=resource_id,
|
||||
Attributes={"Policy": json.dumps(trusted_policy)},
|
||||
)
|
||||
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
@@ -150,6 +150,22 @@ class WAFv2(AWSService):
|
||||
else:
|
||||
acl.rules.append(new_rule)
|
||||
|
||||
firewall_manager_managed_rg = get_web_acl.get("WebACL", {}).get(
|
||||
"PreProcessFirewallManagerRuleGroups", []
|
||||
) + get_web_acl.get("WebACL", {}).get(
|
||||
"PostProcessFirewallManagerRuleGroups", []
|
||||
)
|
||||
|
||||
for rule in firewall_manager_managed_rg:
|
||||
acl.rule_groups.append(
|
||||
Rule(
|
||||
name=rule.get("Name", ""),
|
||||
cloudwatch_metrics_enabled=rule.get(
|
||||
"VisibilityConfig", {}
|
||||
).get("CloudWatchMetricsEnabled", False),
|
||||
)
|
||||
)
|
||||
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{acl.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
@@ -193,13 +209,6 @@ class Rule(BaseModel):
|
||||
cloudwatch_metrics_enabled: bool = False
|
||||
|
||||
|
||||
class FirewallManagerRuleGroup(BaseModel):
|
||||
"""Model representing a rule group for the Web ACL."""
|
||||
|
||||
name: str
|
||||
cloudwatch_metrics_enabled: bool = False
|
||||
|
||||
|
||||
class WebAclv2(BaseModel):
|
||||
"""Model representing a Web ACL for WAFv2."""
|
||||
|
||||
|
||||
+4
-5
@@ -19,12 +19,11 @@ class app_minimum_tls_version_12(Check):
|
||||
report.location = app.location
|
||||
report.status_extended = f"Minimum TLS version is not set to 1.2 for app '{app_name}' in subscription '{subscription_name}'."
|
||||
|
||||
if (
|
||||
app.configurations
|
||||
and getattr(app.configurations, "min_tls_version", "") == "1.2"
|
||||
):
|
||||
if app.configurations and getattr(
|
||||
app.configurations, "min_tls_version", ""
|
||||
) in ["1.2", "1.3"]:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Minimum TLS version is set to 1.2 for app '{app_name}' in subscription '{subscription_name}'."
|
||||
report.status_extended = f"Minimum TLS version is set to {app.configurations.min_tls_version} for app '{app_name}' in subscription '{subscription_name}'."
|
||||
|
||||
findings.append(report)
|
||||
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"Provider": "azure",
|
||||
"CheckID": "sqlserver_recommended_minimal_tls_version",
|
||||
"CheckTitle": "Ensure SQL server has a recommended minimal TLS version required.",
|
||||
"CheckType": [],
|
||||
"ServiceName": "sqlserver",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "SQLServer",
|
||||
"Description": "Ensure that SQL Server instances are configured with the recommended minimal TLS version to maintain secure connections.",
|
||||
"Risk": "Using outdated or weak TLS versions can expose SQL Server instances to vulnerabilities, increasing the risk of data breaches and unauthorized access.",
|
||||
"RelatedUrl": "https://learn.microsoft.com/en-us/azure/azure-sql/database/connectivity-settings?view=azuresql&tabs=azure-portal#configure-minimum-tls-version",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "az sql server update -n sql-server-name -g sql-server-group --set minimalTlsVersion=<version>",
|
||||
"NativeIaC": "",
|
||||
"Other": "",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "1. Go to Azure SQL Server 2. Navigate to 'Security' -> 'Networking' 3. Select 'Connectivity' 4. Update the TLS version in the field 'Minimum TLS version' to a recommended minimal version (e.g., TLS 1.2).",
|
||||
"Url": "https://learn.microsoft.com/en-us/azure/azure-sql/database/connectivity-settings?view=azuresql&tabs=azure-portal#configure-minimum-tls-version"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": "Verify support for the TLS version from the application side before changing the minimal version."
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
from typing import List
|
||||
|
||||
from prowler.lib.check.models import Check, Check_Report_Azure
|
||||
from prowler.providers.azure.services.sqlserver.sqlserver_client import sqlserver_client
|
||||
|
||||
|
||||
class sqlserver_recommended_minimal_tls_version(Check):
|
||||
def execute(self) -> List[Check_Report_Azure]:
|
||||
findings = []
|
||||
recommended_minimal_tls_versions = sqlserver_client.audit_config.get(
|
||||
"recommended_minimal_tls_versions", ["1.2", "1.3"]
|
||||
)
|
||||
for subscription, sql_servers in sqlserver_client.sql_servers.items():
|
||||
for sql_server in sql_servers:
|
||||
report = Check_Report_Azure(self.metadata())
|
||||
report.subscription = subscription
|
||||
report.resource_name = sql_server.name
|
||||
report.resource_id = sql_server.id
|
||||
report.status = "FAIL"
|
||||
report.location = sql_server.location
|
||||
report.status_extended = f"SQL Server {sql_server.name} from subscription {subscription} is using TLS version {sql_server.minimal_tls_version} as minimal accepted which is not recommended. Please use one of the recommended versions: {', '.join(recommended_minimal_tls_versions)}."
|
||||
if sql_server.minimal_tls_version in recommended_minimal_tls_versions:
|
||||
report.status_extended = f"SQL Server {sql_server.name} from subscription {subscription} is using version {sql_server.minimal_tls_version} as minimal accepted which is recommended."
|
||||
report.status = "PASS"
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -181,8 +181,6 @@ class GcpProvider(Provider):
|
||||
message="No Project IDs can be accessed via Google Credentials.",
|
||||
)
|
||||
if project_ids:
|
||||
if self._default_project_id not in project_ids:
|
||||
self._default_project_id = project_ids[0]
|
||||
for input_project in project_ids:
|
||||
for (
|
||||
accessible_project_id,
|
||||
@@ -203,6 +201,10 @@ class GcpProvider(Provider):
|
||||
self._projects[project_id] = project
|
||||
self._project_ids.append(project_id)
|
||||
|
||||
# Change default project if not in active projects
|
||||
if self._project_ids and self._default_project_id not in self._project_ids:
|
||||
self._default_project_id = self._project_ids[0]
|
||||
|
||||
# Remove excluded projects if any input
|
||||
if excluded_project_ids:
|
||||
for excluded_project in excluded_project_ids:
|
||||
|
||||
+12
-12
@@ -23,7 +23,7 @@ packages = [
|
||||
{include = "dashboard"}
|
||||
]
|
||||
readme = "README.md"
|
||||
version = "5.0.0"
|
||||
version = "5.1.0"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
alive-progress = "3.2.0"
|
||||
@@ -48,19 +48,19 @@ azure-mgmt-storage = "21.2.1"
|
||||
azure-mgmt-subscription = "3.1.1"
|
||||
azure-mgmt-web = "7.3.1"
|
||||
azure-storage-blob = "12.24.0"
|
||||
boto3 = "1.35.71"
|
||||
botocore = "1.35.71"
|
||||
boto3 = "1.35.78"
|
||||
botocore = "1.35.79"
|
||||
colorama = "0.4.6"
|
||||
cryptography = "43.0.1"
|
||||
dash = "2.18.2"
|
||||
dash-bootstrap-components = "1.6.0"
|
||||
detect-secrets = "1.5.0"
|
||||
google-api-python-client = "2.154.0"
|
||||
google-api-python-client = "2.155.0"
|
||||
google-auth-httplib2 = ">=0.1,<0.3"
|
||||
jsonschema = "4.23.0"
|
||||
kubernetes = "31.0.0"
|
||||
microsoft-kiota-abstractions = "1.6.2"
|
||||
msgraph-sdk = "1.12.0"
|
||||
microsoft-kiota-abstractions = "1.6.6"
|
||||
msgraph-sdk = "1.14.0"
|
||||
numpy = "2.0.2"
|
||||
pandas = "2.2.3"
|
||||
py-ocsf-models = "0.2.0"
|
||||
@@ -70,14 +70,14 @@ python-dateutil = "^2.9.0.post0"
|
||||
pytz = "2024.2"
|
||||
schema = "0.7.7"
|
||||
shodan = "1.31.0"
|
||||
slack-sdk = "3.33.4"
|
||||
slack-sdk = "3.33.5"
|
||||
tabulate = "0.9.0"
|
||||
tzlocal = "5.2"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
bandit = "1.8.0"
|
||||
black = "24.10.0"
|
||||
coverage = "7.6.8"
|
||||
coverage = "7.6.9"
|
||||
docker = "7.1.0"
|
||||
flake8 = "7.1.1"
|
||||
freezegun = "1.5.1"
|
||||
@@ -85,14 +85,14 @@ mock = "5.1.0"
|
||||
moto = {extras = ["all"], version = "5.0.16"}
|
||||
openapi-schema-validator = "0.6.2"
|
||||
openapi-spec-validator = "0.7.1"
|
||||
pylint = "3.3.1"
|
||||
pytest = "8.3.3"
|
||||
pylint = "3.3.2"
|
||||
pytest = "8.3.4"
|
||||
pytest-cov = "6.0.0"
|
||||
pytest-env = "1.1.5"
|
||||
pytest-randomly = "3.16.0"
|
||||
pytest-xdist = "3.6.1"
|
||||
safety = "3.2.9"
|
||||
vulture = "2.13"
|
||||
vulture = "2.14"
|
||||
|
||||
[tool.poetry.group.docs]
|
||||
optional = true
|
||||
@@ -100,7 +100,7 @@ optional = true
|
||||
[tool.poetry.group.docs.dependencies]
|
||||
mkdocs = "1.6.1"
|
||||
mkdocs-git-revision-date-localized-plugin = "1.3.0"
|
||||
mkdocs-material = "9.5.46"
|
||||
mkdocs-material = "9.5.48"
|
||||
mkdocs-material-extensions = "1.3.1"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
|
||||
@@ -318,6 +318,7 @@ config_azure = {
|
||||
"php_latest_version": "8.2",
|
||||
"python_latest_version": "3.12",
|
||||
"java_latest_version": "17",
|
||||
"recommended_minimal_tls_versions": ["1.2", "1.3"],
|
||||
}
|
||||
|
||||
config_gcp = {"shodan_api_key": None}
|
||||
|
||||
@@ -379,6 +379,14 @@ azure:
|
||||
# azure.app_ensure_java_version_is_latest
|
||||
java_latest_version: "17"
|
||||
|
||||
# Azure SQL Server
|
||||
# azure.sqlserver_minimal_tls_version
|
||||
recommended_minimal_tls_versions:
|
||||
[
|
||||
"1.2",
|
||||
"1.3"
|
||||
]
|
||||
|
||||
# GCP Configuration
|
||||
gcp:
|
||||
# GCP Compute Configuration
|
||||
|
||||
+1
-13
@@ -171,10 +171,6 @@ class Test_autoscaling_group_launch_configuration_no_public_ip:
|
||||
AvailabilityZones=["us-east-1a", "us-east-1b"],
|
||||
)
|
||||
|
||||
autoscaling_group_arn = autoscaling_client.describe_auto_scaling_groups(
|
||||
AutoScalingGroupNames=[autoscaling_group_name]
|
||||
)["AutoScalingGroups"][0]["AutoScalingGroupARN"]
|
||||
|
||||
from prowler.providers.aws.services.autoscaling.autoscaling_service import (
|
||||
AutoScaling,
|
||||
)
|
||||
@@ -196,12 +192,4 @@ class Test_autoscaling_group_launch_configuration_no_public_ip:
|
||||
check = autoscaling_group_launch_configuration_no_public_ip()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Autoscaling group {autoscaling_group_name} does not have an associated launch configuration assigning a public IP address."
|
||||
)
|
||||
assert result[0].resource_id == autoscaling_group_name
|
||||
assert result[0].resource_tags == []
|
||||
assert result[0].resource_arn == autoscaling_group_arn
|
||||
assert len(result) == 0
|
||||
|
||||
+1
-13
@@ -119,10 +119,6 @@ class Test_autoscaling_group_launch_configuration_requires_imdsv2:
|
||||
AvailabilityZones=["us-east-1a", "us-east-1b"],
|
||||
)
|
||||
|
||||
autoscaling_group_arn = autoscaling_client.describe_auto_scaling_groups(
|
||||
AutoScalingGroupNames=[autoscaling_group_name]
|
||||
)["AutoScalingGroups"][0]["AutoScalingGroupARN"]
|
||||
|
||||
from prowler.providers.aws.services.autoscaling.autoscaling_service import (
|
||||
AutoScaling,
|
||||
)
|
||||
@@ -144,15 +140,7 @@ class Test_autoscaling_group_launch_configuration_requires_imdsv2:
|
||||
check = autoscaling_group_launch_configuration_requires_imdsv2()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Autoscaling group {autoscaling_group_name} has IMDSv2 disabled or not required."
|
||||
)
|
||||
assert result[0].resource_id == autoscaling_group_name
|
||||
assert result[0].resource_tags == []
|
||||
assert result[0].resource_arn == autoscaling_group_arn
|
||||
assert len(result) == 0
|
||||
|
||||
@mock_aws
|
||||
def test_groups_with_imdsv2_disabled(self):
|
||||
|
||||
+268
@@ -0,0 +1,268 @@
|
||||
from json import dumps
|
||||
from unittest import mock
|
||||
|
||||
from boto3 import client
|
||||
from moto import mock_aws
|
||||
|
||||
from tests.providers.aws.utils import (
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
AWS_REGION_EU_WEST_1,
|
||||
set_mocked_aws_provider,
|
||||
)
|
||||
|
||||
|
||||
class Test_awslambda_function_not_publicly_accessible_fixer:
|
||||
@mock_aws
|
||||
def test_function_public(self):
|
||||
# Create the mock IAM role
|
||||
iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1)
|
||||
role_name = "test-role"
|
||||
assume_role_policy_document = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {"Service": "lambda.amazonaws.com"},
|
||||
"Action": "sts:AssumeRole",
|
||||
}
|
||||
],
|
||||
}
|
||||
role_arn = iam_client.create_role(
|
||||
RoleName=role_name,
|
||||
AssumeRolePolicyDocument=dumps(assume_role_policy_document),
|
||||
)["Role"]["Arn"]
|
||||
|
||||
function_name = "test-lambda"
|
||||
|
||||
# Create the lambda function using boto3 client
|
||||
lambda_client = client("lambda", region_name=AWS_REGION_EU_WEST_1)
|
||||
lambda_client.create_function(
|
||||
FunctionName=function_name,
|
||||
Runtime="nodejs4.3",
|
||||
Role=role_arn,
|
||||
Handler="index.handler",
|
||||
Code={"ZipFile": b"fileb://file-path/to/your-deployment-package.zip"},
|
||||
Description="Test Lambda function",
|
||||
Timeout=3,
|
||||
MemorySize=128,
|
||||
Publish=True,
|
||||
Tags={"tag1": "value1", "tag2": "value2"},
|
||||
)["FunctionArn"]
|
||||
|
||||
# Attach the policy to the lambda function with a wildcard principal
|
||||
lambda_client.add_permission(
|
||||
FunctionName=function_name,
|
||||
StatementId="public-access",
|
||||
Action="lambda:InvokeFunction",
|
||||
Principal="*",
|
||||
)
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
|
||||
from prowler.providers.aws.services.awslambda.awslambda_service import Lambda
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.awslambda.awslambda_function_not_publicly_accessible.awslambda_function_not_publicly_accessible_fixer.awslambda_client",
|
||||
new=Lambda(aws_provider),
|
||||
):
|
||||
# Test Fixer
|
||||
from prowler.providers.aws.services.awslambda.awslambda_function_not_publicly_accessible.awslambda_function_not_publicly_accessible_fixer import (
|
||||
fixer,
|
||||
)
|
||||
|
||||
assert fixer(function_name, AWS_REGION_EU_WEST_1)
|
||||
|
||||
@mock_aws
|
||||
def test_function_public_with_source_account(self):
|
||||
# Create the mock IAM role
|
||||
iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1)
|
||||
role_name = "test-role"
|
||||
assume_role_policy_document = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {"Service": "lambda.amazonaws.com"},
|
||||
"Action": "sts:AssumeRole",
|
||||
}
|
||||
],
|
||||
}
|
||||
role_arn = iam_client.create_role(
|
||||
RoleName=role_name,
|
||||
AssumeRolePolicyDocument=dumps(assume_role_policy_document),
|
||||
)["Role"]["Arn"]
|
||||
|
||||
function_name = "test-lambda"
|
||||
|
||||
# Create the lambda function using boto3 client
|
||||
lambda_client = client("lambda", region_name=AWS_REGION_EU_WEST_1)
|
||||
function_arn = lambda_client.create_function(
|
||||
FunctionName=function_name,
|
||||
Runtime="nodejs4.3",
|
||||
Role=role_arn,
|
||||
Handler="index.handler",
|
||||
Code={"ZipFile": b"fileb://file-path/to/your-deployment-package.zip"},
|
||||
Description="Test Lambda function",
|
||||
Timeout=3,
|
||||
MemorySize=128,
|
||||
Publish=True,
|
||||
Tags={"tag1": "value1", "tag2": "value2"},
|
||||
)["FunctionArn"]
|
||||
|
||||
# Attach the policy to the lambda function with a wildcard principal
|
||||
lambda_client.add_permission(
|
||||
FunctionName=function_name,
|
||||
StatementId="public-access",
|
||||
Action="lambda:InvokeFunction",
|
||||
Principal="*",
|
||||
SourceArn=function_arn,
|
||||
SourceAccount=AWS_ACCOUNT_NUMBER,
|
||||
)
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
|
||||
from prowler.providers.aws.services.awslambda.awslambda_service import Lambda
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.awslambda.awslambda_function_not_publicly_accessible.awslambda_function_not_publicly_accessible_fixer.awslambda_client",
|
||||
new=Lambda(aws_provider),
|
||||
):
|
||||
# Test Fixer
|
||||
from prowler.providers.aws.services.awslambda.awslambda_function_not_publicly_accessible.awslambda_function_not_publicly_accessible_fixer import (
|
||||
fixer,
|
||||
)
|
||||
|
||||
assert fixer(function_name, AWS_REGION_EU_WEST_1)
|
||||
|
||||
@mock_aws
|
||||
def test_function_not_public(self):
|
||||
# Create the mock IAM role
|
||||
iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1)
|
||||
role_name = "test-role"
|
||||
assume_role_policy_document = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {"Service": "lambda.amazonaws.com"},
|
||||
"Action": "sts:AssumeRole",
|
||||
}
|
||||
],
|
||||
}
|
||||
role_arn = iam_client.create_role(
|
||||
RoleName=role_name,
|
||||
AssumeRolePolicyDocument=dumps(assume_role_policy_document),
|
||||
)["Role"]["Arn"]
|
||||
|
||||
function_name = "test-lambda"
|
||||
|
||||
# Create the lambda function using boto3 client
|
||||
lambda_client = client("lambda", region_name=AWS_REGION_EU_WEST_1)
|
||||
function_arn = lambda_client.create_function(
|
||||
FunctionName=function_name,
|
||||
Runtime="nodejs4.3",
|
||||
Role=role_arn,
|
||||
Handler="index.handler",
|
||||
Code={"ZipFile": b"fileb://file-path/to/your-deployment-package.zip"},
|
||||
Description="Test Lambda function",
|
||||
Timeout=3,
|
||||
MemorySize=128,
|
||||
Publish=True,
|
||||
Tags={"tag1": "value1", "tag2": "value2"},
|
||||
)["FunctionArn"]
|
||||
|
||||
# Attach the policy to the lambda function with a specific AWS account number as principal
|
||||
lambda_client.add_permission(
|
||||
FunctionName=function_name,
|
||||
StatementId="public-access",
|
||||
Action="lambda:InvokeFunction",
|
||||
Principal=AWS_ACCOUNT_NUMBER,
|
||||
SourceArn=function_arn,
|
||||
)
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
|
||||
from prowler.providers.aws.services.awslambda.awslambda_service import Lambda
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.awslambda.awslambda_function_not_publicly_accessible.awslambda_function_not_publicly_accessible_fixer.awslambda_client",
|
||||
new=Lambda(aws_provider),
|
||||
):
|
||||
# Test Fixer
|
||||
from prowler.providers.aws.services.awslambda.awslambda_function_not_publicly_accessible.awslambda_function_not_publicly_accessible_fixer import (
|
||||
fixer,
|
||||
)
|
||||
|
||||
assert fixer(function_name, AWS_REGION_EU_WEST_1)
|
||||
|
||||
@mock_aws
|
||||
def test_function_public_error(self):
|
||||
# Create the mock IAM role
|
||||
iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1)
|
||||
role_name = "test-role"
|
||||
assume_role_policy_document = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {"Service": "lambda.amazonaws.com"},
|
||||
"Action": "sts:AssumeRole",
|
||||
}
|
||||
],
|
||||
}
|
||||
role_arn = iam_client.create_role(
|
||||
RoleName=role_name,
|
||||
AssumeRolePolicyDocument=dumps(assume_role_policy_document),
|
||||
)["Role"]["Arn"]
|
||||
|
||||
function_name = "test-lambda"
|
||||
|
||||
# Create the lambda function using boto3 client
|
||||
lambda_client = client("lambda", region_name=AWS_REGION_EU_WEST_1)
|
||||
lambda_client.create_function(
|
||||
FunctionName=function_name,
|
||||
Runtime="nodejs4.3",
|
||||
Role=role_arn,
|
||||
Handler="index.handler",
|
||||
Code={"ZipFile": b"fileb://file-path/to/your-deployment-package.zip"},
|
||||
Description="Test Lambda function",
|
||||
Timeout=3,
|
||||
MemorySize=128,
|
||||
Publish=True,
|
||||
Tags={"tag1": "value1", "tag2": "value2"},
|
||||
)["FunctionArn"]
|
||||
|
||||
# Attach the policy to the lambda function with a wildcard principal
|
||||
lambda_client.add_permission(
|
||||
FunctionName=function_name,
|
||||
StatementId="public-access",
|
||||
Action="lambda:InvokeFunction",
|
||||
Principal="*",
|
||||
)
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
|
||||
from prowler.providers.aws.services.awslambda.awslambda_service import Lambda
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.awslambda.awslambda_function_not_publicly_accessible.awslambda_function_not_publicly_accessible_fixer.awslambda_client",
|
||||
new=Lambda(aws_provider),
|
||||
):
|
||||
# Test Fixer
|
||||
from prowler.providers.aws.services.awslambda.awslambda_function_not_publicly_accessible.awslambda_function_not_publicly_accessible_fixer import (
|
||||
fixer,
|
||||
)
|
||||
|
||||
assert not fixer("non-exsting", AWS_REGION_EU_WEST_1)
|
||||
+50
-35
@@ -19,10 +19,10 @@ def mock_get_trail_arn_template(region=None, *_) -> str:
|
||||
def mock__get_lookup_events__(trail=None, event_name=None, minutes=None, *_) -> list:
|
||||
return [
|
||||
{
|
||||
"CloudTrailEvent": '{"eventName": "DescribeAccessEntry", "userIdentity": {"type": "IAMUser", "principalId": "EXAMPLE6E4XEGITWATV6R", "arn": "arn:aws:iam::123456789012:user/Mateo", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "userName": "Mateo", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}'
|
||||
"CloudTrailEvent": '{"eventName": "DescribeAccessEntry", "userIdentity": {"type": "IAMUser", "principalId": "EXAMPLE6E4XEGITWATV6R", "arn": "arn:aws:iam::123456789012:user/Attacker", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "userName": "Attacker", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}'
|
||||
},
|
||||
{
|
||||
"CloudTrailEvent": '{"eventName": "DescribeAccountAttributes", "userIdentity": {"type": "IAMUser", "principalId": "EXAMPLE6E4XEGITWATV6R", "arn": "arn:aws:iam::123456789012:user/Mateo", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "userName": "Mateo", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}'
|
||||
"CloudTrailEvent": '{"eventName": "DescribeAccountAttributes", "userIdentity": {"type": "IAMUser", "principalId": "EXAMPLE6E4XEGITWATV6R", "arn": "arn:aws:iam::123456789012:user/Attacker", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "userName": "Attacker", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}'
|
||||
},
|
||||
]
|
||||
|
||||
@@ -50,12 +50,15 @@ class Test_cloudtrail_threat_detection_enumeration:
|
||||
cloudtrail_client.audited_account = AWS_ACCOUNT_NUMBER
|
||||
cloudtrail_client.region = AWS_REGION_US_EAST_1
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration import (
|
||||
@@ -99,12 +102,15 @@ class Test_cloudtrail_threat_detection_enumeration:
|
||||
cloudtrail_client._lookup_events = mock__get_lookup_events__
|
||||
cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration import (
|
||||
@@ -148,12 +154,15 @@ class Test_cloudtrail_threat_detection_enumeration:
|
||||
cloudtrail_client._lookup_events = mock__get_lookup_events__
|
||||
cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration import (
|
||||
@@ -167,13 +176,13 @@ class Test_cloudtrail_threat_detection_enumeration:
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Potential enumeration attack detected from AWS IAMUser Mateo with an threshold of 1.0."
|
||||
== "Potential enumeration attack detected from AWS IAMUser Attacker with an threshold of 1.0."
|
||||
)
|
||||
assert result[0].resource_id == AWS_ACCOUNT_NUMBER
|
||||
assert result[0].resource_id == "Attacker"
|
||||
assert result[0].region == AWS_REGION_US_EAST_1
|
||||
assert (
|
||||
result[0].resource_arn
|
||||
== f"arn:aws:cloudtrail:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:trail"
|
||||
== f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:user/Attacker"
|
||||
)
|
||||
|
||||
@mock_aws
|
||||
@@ -198,12 +207,15 @@ class Test_cloudtrail_threat_detection_enumeration:
|
||||
cloudtrail_client._lookup_events = mock__get_lookup_events__
|
||||
cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration import (
|
||||
@@ -247,12 +259,15 @@ class Test_cloudtrail_threat_detection_enumeration:
|
||||
cloudtrail_client._lookup_events = mock__get_lookup_events_aws_service__
|
||||
cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration import (
|
||||
|
||||
+50
-35
@@ -19,10 +19,10 @@ def mock_get_trail_arn_template(region=None, *_) -> str:
|
||||
def mock__get_lookup_events__(trail=None, event_name=None, minutes=None, *_) -> list:
|
||||
return [
|
||||
{
|
||||
"CloudTrailEvent": '{"eventName": "InvokeModel", "userIdentity": {"type": "IAMUser", "principalId": "EXAMPLE6E4XEGITWATV6R", "arn": "arn:aws:iam::123456789012:user/Mateo", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "userName": "Mateo", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}'
|
||||
"CloudTrailEvent": '{"eventName": "InvokeModel", "userIdentity": {"type": "IAMUser", "principalId": "EXAMPLE6E4XEGITWATV6R", "arn": "arn:aws:iam::123456789012:user/Attacker", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "userName": "Attacker", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}'
|
||||
},
|
||||
{
|
||||
"CloudTrailEvent": '{"eventName": "InvokeModelWithResponseStream", "userIdentity": {"type": "IAMUser", "principalId": "EXAMPLE6E4XEGITWATV6R", "arn": "arn:aws:iam::123456789012:user/Mateo", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "userName": "Mateo", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}'
|
||||
"CloudTrailEvent": '{"eventName": "InvokeModelWithResponseStream", "userIdentity": {"type": "IAMUser", "principalId": "EXAMPLE6E4XEGITWATV6R", "arn": "arn:aws:iam::123456789012:user/Attacker", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "userName": "Attacker", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}'
|
||||
},
|
||||
]
|
||||
|
||||
@@ -50,12 +50,15 @@ class Test_cloudtrail_threat_detection_llm_jacking:
|
||||
cloudtrail_client.audited_account = AWS_ACCOUNT_NUMBER
|
||||
cloudtrail_client.region = AWS_REGION_US_EAST_1
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking import (
|
||||
@@ -96,12 +99,15 @@ class Test_cloudtrail_threat_detection_llm_jacking:
|
||||
cloudtrail_client._lookup_events = mock__get_lookup_events__
|
||||
cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking import (
|
||||
@@ -145,12 +151,15 @@ class Test_cloudtrail_threat_detection_llm_jacking:
|
||||
cloudtrail_client._lookup_events = mock__get_lookup_events__
|
||||
cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking import (
|
||||
@@ -164,13 +173,13 @@ class Test_cloudtrail_threat_detection_llm_jacking:
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Potential LLM Jacking attack detected from AWS IAMUser Mateo with an threshold of 1.0."
|
||||
== "Potential LLM Jacking attack detected from AWS IAMUser Attacker with an threshold of 1.0."
|
||||
)
|
||||
assert result[0].resource_id == AWS_ACCOUNT_NUMBER
|
||||
assert result[0].resource_id == "Attacker"
|
||||
assert result[0].region == AWS_REGION_US_EAST_1
|
||||
assert (
|
||||
result[0].resource_arn
|
||||
== f"arn:aws:cloudtrail:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:trail"
|
||||
== f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:user/Attacker"
|
||||
)
|
||||
|
||||
@mock_aws
|
||||
@@ -195,12 +204,15 @@ class Test_cloudtrail_threat_detection_llm_jacking:
|
||||
cloudtrail_client._lookup_events = mock__get_lookup_events__
|
||||
cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking import (
|
||||
@@ -244,12 +256,15 @@ class Test_cloudtrail_threat_detection_llm_jacking:
|
||||
cloudtrail_client._lookup_events = mock__get_lookup_events_aws_service__
|
||||
cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking import (
|
||||
|
||||
+50
-35
@@ -19,10 +19,10 @@ def mock_get_trail_arn_template(region=None, *_) -> str:
|
||||
def mock__get_lookup_events__(trail=None, event_name=None, minutes=None, *_) -> list:
|
||||
return [
|
||||
{
|
||||
"CloudTrailEvent": '{"eventName": "CreateLoginProfile", "userIdentity": {"type": "IAMUser", "principalId": "EXAMPLE6E4XEGITWATV6R", "arn": "arn:aws:iam::123456789012:user/Mateo", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "userName": "Mateo", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}'
|
||||
"CloudTrailEvent": '{"eventName": "CreateLoginProfile", "userIdentity": {"type": "IAMUser", "principalId": "EXAMPLE6E4XEGITWATV6R", "arn": "arn:aws:iam::123456789012:user/Attacker", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "userName": "Attacker", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}'
|
||||
},
|
||||
{
|
||||
"CloudTrailEvent": '{"eventName": "UpdateLoginProfile", "userIdentity": {"type": "IAMUser", "principalId": "EXAMPLE6E4XEGITWATV6R", "arn": "arn:aws:iam::123456789012:user/Mateo", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "userName": "Mateo", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}'
|
||||
"CloudTrailEvent": '{"eventName": "UpdateLoginProfile", "userIdentity": {"type": "IAMUser", "principalId": "EXAMPLE6E4XEGITWATV6R", "arn": "arn:aws:iam::123456789012:user/Attacker", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "userName": "Attacker", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}'
|
||||
},
|
||||
]
|
||||
|
||||
@@ -50,12 +50,15 @@ class Test_cloudtrail_threat_detection_privilege_escalation:
|
||||
cloudtrail_client.audited_account = AWS_ACCOUNT_NUMBER
|
||||
cloudtrail_client.region = AWS_REGION_US_EAST_1
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation import (
|
||||
@@ -97,12 +100,15 @@ class Test_cloudtrail_threat_detection_privilege_escalation:
|
||||
cloudtrail_client._lookup_events = mock__get_lookup_events__
|
||||
cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation import (
|
||||
@@ -147,12 +153,15 @@ class Test_cloudtrail_threat_detection_privilege_escalation:
|
||||
cloudtrail_client._lookup_events = mock__get_lookup_events__
|
||||
cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation import (
|
||||
@@ -166,13 +175,13 @@ class Test_cloudtrail_threat_detection_privilege_escalation:
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Potential privilege escalation attack detected from AWS IAMUser Mateo with an threshold of 1.0."
|
||||
== "Potential privilege escalation attack detected from AWS IAMUser Attacker with an threshold of 1.0."
|
||||
)
|
||||
assert result[0].resource_id == AWS_ACCOUNT_NUMBER
|
||||
assert result[0].resource_id == "Attacker"
|
||||
assert result[0].region == AWS_REGION_US_EAST_1
|
||||
assert (
|
||||
result[0].resource_arn
|
||||
== f"arn:aws:cloudtrail:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:trail"
|
||||
== f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:user/Attacker"
|
||||
)
|
||||
|
||||
@mock_aws
|
||||
@@ -197,12 +206,15 @@ class Test_cloudtrail_threat_detection_privilege_escalation:
|
||||
cloudtrail_client._lookup_events = mock__get_lookup_events__
|
||||
cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation import (
|
||||
@@ -247,12 +259,15 @@ class Test_cloudtrail_threat_detection_privilege_escalation:
|
||||
cloudtrail_client._lookup_events = mock__get_lookup_events_aws_service__
|
||||
cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_aws_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation.cloudtrail_client",
|
||||
new=cloudtrail_client,
|
||||
),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation import (
|
||||
|
||||
+430
@@ -0,0 +1,430 @@
|
||||
from unittest import mock
|
||||
|
||||
import botocore
|
||||
import botocore.client
|
||||
from boto3 import client, resource
|
||||
from moto import mock_aws
|
||||
|
||||
from tests.providers.aws.utils import AWS_REGION_EU_WEST_1, set_mocked_aws_provider
|
||||
|
||||
mock_make_api_call = botocore.client.BaseClient._make_api_call
|
||||
|
||||
|
||||
def mock_make_api_call_error(self, operation_name, kwarg):
|
||||
if operation_name == "RevokeSecurityGroupIngress":
|
||||
raise botocore.exceptions.ClientError(
|
||||
{
|
||||
"Error": {
|
||||
"Code": "InvalidPermission.NotFound",
|
||||
"Message": "The specified rule does not exist in this security group.",
|
||||
}
|
||||
},
|
||||
operation_name,
|
||||
)
|
||||
return mock_make_api_call(self, operation_name, kwarg)
|
||||
|
||||
|
||||
class Test_ec2_instance_port_cifs_exposed_to_internet_fixer:
|
||||
@mock_aws
|
||||
def test_ec2_instance_exposed_port_in_private_subnet_with_ip4_and_ip6(self):
|
||||
# Create EC2 Mocked Resources
|
||||
ec2_client = client("ec2", region_name=AWS_REGION_EU_WEST_1)
|
||||
ec2_resource = resource("ec2", region_name=AWS_REGION_EU_WEST_1)
|
||||
vpc_id = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"]
|
||||
default_sg = ec2_client.describe_security_groups(GroupNames=["default"])[
|
||||
"SecurityGroups"
|
||||
][0]
|
||||
default_sg_id = default_sg["GroupId"]
|
||||
ec2_client.authorize_security_group_ingress(
|
||||
GroupId=default_sg_id,
|
||||
IpPermissions=[
|
||||
{
|
||||
"IpProtocol": "tcp",
|
||||
"FromPort": 1,
|
||||
"ToPort": 1000,
|
||||
"IpRanges": [{"CidrIp": "0.0.0.0/0"}, {"CidrIp": "10.0.0.0/24"}],
|
||||
"Ipv6Ranges": [{"CidrIpv6": "::/0"}, {"CidrIpv6": "2001:db8::/32"}],
|
||||
},
|
||||
{
|
||||
"IpProtocol": "tcp",
|
||||
"FromPort": 445,
|
||||
"ToPort": 445,
|
||||
"IpRanges": [{"CidrIp": "0.0.0.0/0"}, {"CidrIp": "10.0.0.0/24"}],
|
||||
"Ipv6Ranges": [{"CidrIpv6": "::/0"}, {"CidrIpv6": "2001:db8::/32"}],
|
||||
},
|
||||
],
|
||||
)
|
||||
subnet_id = ec2_client.create_subnet(VpcId=vpc_id, CidrBlock="10.0.0.0/16")[
|
||||
"Subnet"
|
||||
]["SubnetId"]
|
||||
instance_id = ec2_resource.create_instances(
|
||||
ImageId="ami-12345678",
|
||||
MinCount=1,
|
||||
MaxCount=1,
|
||||
InstanceType="t2.micro",
|
||||
SecurityGroupIds=[default_sg_id],
|
||||
SubnetId=subnet_id,
|
||||
TagSpecifications=[
|
||||
{"ResourceType": "instance", "Tags": [{"Key": "Name", "Value": "test"}]}
|
||||
],
|
||||
)[0].id
|
||||
|
||||
from prowler.providers.aws.services.ec2.ec2_service import EC2
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.ec2.ec2_instance_port_cifs_exposed_to_internet.ec2_instance_port_cifs_exposed_to_internet_fixer.ec2_client",
|
||||
new=EC2(aws_provider),
|
||||
):
|
||||
# Test Fixer
|
||||
from prowler.providers.aws.services.ec2.ec2_instance_port_cifs_exposed_to_internet.ec2_instance_port_cifs_exposed_to_internet_fixer import (
|
||||
fixer,
|
||||
)
|
||||
|
||||
assert fixer(instance_id, AWS_REGION_EU_WEST_1)
|
||||
|
||||
@mock_aws
|
||||
def test_ec2_instance_exposed_port_error(self):
|
||||
with mock.patch(
|
||||
"botocore.client.BaseClient._make_api_call", new=mock_make_api_call_error
|
||||
):
|
||||
# Create EC2 Mocked Resources
|
||||
ec2_client = client("ec2", region_name=AWS_REGION_EU_WEST_1)
|
||||
ec2_resource = resource("ec2", region_name=AWS_REGION_EU_WEST_1)
|
||||
vpc_id = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"]
|
||||
default_sg = ec2_client.describe_security_groups(GroupNames=["default"])[
|
||||
"SecurityGroups"
|
||||
][0]
|
||||
default_sg_id = default_sg["GroupId"]
|
||||
ec2_client.authorize_security_group_ingress(
|
||||
GroupId=default_sg_id,
|
||||
IpPermissions=[
|
||||
{
|
||||
"IpProtocol": "tcp",
|
||||
"FromPort": 1,
|
||||
"ToPort": 1000,
|
||||
"IpRanges": [
|
||||
{"CidrIp": "0.0.0.0/0"},
|
||||
{"CidrIp": "10.0.0.0/24"},
|
||||
],
|
||||
"Ipv6Ranges": [
|
||||
{"CidrIpv6": "::/0"},
|
||||
{"CidrIpv6": "2001:db8::/32"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"IpProtocol": "tcp",
|
||||
"FromPort": 445,
|
||||
"ToPort": 445,
|
||||
"IpRanges": [
|
||||
{"CidrIp": "0.0.0.0/0"},
|
||||
{"CidrIp": "10.0.0.0/24"},
|
||||
],
|
||||
"Ipv6Ranges": [
|
||||
{"CidrIpv6": "::/0"},
|
||||
{"CidrIpv6": "2001:db8::/32"},
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
subnet_id = ec2_client.create_subnet(VpcId=vpc_id, CidrBlock="10.0.0.0/16")[
|
||||
"Subnet"
|
||||
]["SubnetId"]
|
||||
instance_id = ec2_resource.create_instances(
|
||||
ImageId="ami-12345678",
|
||||
MinCount=1,
|
||||
MaxCount=1,
|
||||
InstanceType="t2.micro",
|
||||
SecurityGroupIds=[default_sg_id],
|
||||
SubnetId=subnet_id,
|
||||
TagSpecifications=[
|
||||
{
|
||||
"ResourceType": "instance",
|
||||
"Tags": [{"Key": "Name", "Value": "test"}],
|
||||
}
|
||||
],
|
||||
)[0].id
|
||||
|
||||
from prowler.providers.aws.services.ec2.ec2_service import EC2
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.ec2.ec2_instance_port_cifs_exposed_to_internet.ec2_instance_port_cifs_exposed_to_internet_fixer.ec2_client",
|
||||
new=EC2(aws_provider),
|
||||
):
|
||||
# Test Fixer
|
||||
from prowler.providers.aws.services.ec2.ec2_instance_port_cifs_exposed_to_internet.ec2_instance_port_cifs_exposed_to_internet_fixer import (
|
||||
fixer,
|
||||
)
|
||||
|
||||
assert not fixer(instance_id, AWS_REGION_EU_WEST_1)
|
||||
|
||||
@mock_aws
|
||||
def test_ec2_instance_exposed_port_in_private_subnet_only_with_ip4(self):
|
||||
# Create EC2 Mocked Resources
|
||||
ec2_client = client("ec2", region_name=AWS_REGION_EU_WEST_1)
|
||||
ec2_resource = resource("ec2", region_name=AWS_REGION_EU_WEST_1)
|
||||
vpc_id = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"]
|
||||
default_sg = ec2_client.describe_security_groups(GroupNames=["default"])[
|
||||
"SecurityGroups"
|
||||
][0]
|
||||
default_sg_id = default_sg["GroupId"]
|
||||
ec2_client.authorize_security_group_ingress(
|
||||
GroupId=default_sg_id,
|
||||
IpPermissions=[
|
||||
{
|
||||
"IpProtocol": "tcp",
|
||||
"FromPort": 139,
|
||||
"ToPort": 139,
|
||||
"IpRanges": [{"CidrIp": "0.0.0.0/0"}, {"CidrIp": "10.0.0.0/24"}],
|
||||
},
|
||||
{
|
||||
"IpProtocol": "tcp",
|
||||
"FromPort": 445,
|
||||
"ToPort": 445,
|
||||
"IpRanges": [{"CidrIp": "0.0.0.0/0"}, {"CidrIp": "10.0.0.0/24"}],
|
||||
},
|
||||
],
|
||||
)
|
||||
subnet_id = ec2_client.create_subnet(VpcId=vpc_id, CidrBlock="10.0.0.0/16")[
|
||||
"Subnet"
|
||||
]["SubnetId"]
|
||||
instance_id = ec2_resource.create_instances(
|
||||
ImageId="ami-12345678",
|
||||
MinCount=1,
|
||||
MaxCount=1,
|
||||
InstanceType="t2.micro",
|
||||
SecurityGroupIds=[default_sg_id],
|
||||
SubnetId=subnet_id,
|
||||
TagSpecifications=[
|
||||
{"ResourceType": "instance", "Tags": [{"Key": "Name", "Value": "test"}]}
|
||||
],
|
||||
)[0].id
|
||||
|
||||
from prowler.providers.aws.services.ec2.ec2_service import EC2
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.ec2.ec2_instance_port_cifs_exposed_to_internet.ec2_instance_port_cifs_exposed_to_internet_fixer.ec2_client",
|
||||
new=EC2(aws_provider),
|
||||
):
|
||||
# Test Fixer
|
||||
from prowler.providers.aws.services.ec2.ec2_instance_port_cifs_exposed_to_internet.ec2_instance_port_cifs_exposed_to_internet_fixer import (
|
||||
fixer,
|
||||
)
|
||||
|
||||
assert fixer(instance_id, AWS_REGION_EU_WEST_1)
|
||||
|
||||
@mock_aws
|
||||
def test_ec2_instance_exposed_port_in_private_subnet_only_with_ip6(self):
|
||||
# Create EC2 Mocked Resources
|
||||
ec2_client = client("ec2", region_name=AWS_REGION_EU_WEST_1)
|
||||
ec2_resource = resource("ec2", region_name=AWS_REGION_EU_WEST_1)
|
||||
vpc_id = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"]
|
||||
default_sg = ec2_client.describe_security_groups(GroupNames=["default"])[
|
||||
"SecurityGroups"
|
||||
][0]
|
||||
default_sg_id = default_sg["GroupId"]
|
||||
ec2_client.authorize_security_group_ingress(
|
||||
GroupId=default_sg_id,
|
||||
IpPermissions=[
|
||||
{
|
||||
"IpProtocol": "tcp",
|
||||
"FromPort": 139,
|
||||
"ToPort": 139,
|
||||
"Ipv6Ranges": [{"CidrIpv6": "::/0"}, {"CidrIpv6": "2001:db8::/32"}],
|
||||
},
|
||||
{
|
||||
"IpProtocol": "tcp",
|
||||
"FromPort": 445,
|
||||
"ToPort": 445,
|
||||
"Ipv6Ranges": [{"CidrIpv6": "::/0"}, {"CidrIpv6": "2001:db8::/32"}],
|
||||
},
|
||||
],
|
||||
)
|
||||
subnet_id = ec2_client.create_subnet(VpcId=vpc_id, CidrBlock="10.0.0.0/16")[
|
||||
"Subnet"
|
||||
]["SubnetId"]
|
||||
instance_id = ec2_resource.create_instances(
|
||||
ImageId="ami-12345678",
|
||||
MinCount=1,
|
||||
MaxCount=1,
|
||||
InstanceType="t2.micro",
|
||||
SecurityGroupIds=[default_sg_id],
|
||||
SubnetId=subnet_id,
|
||||
TagSpecifications=[
|
||||
{"ResourceType": "instance", "Tags": [{"Key": "Name", "Value": "test"}]}
|
||||
],
|
||||
)[0].id
|
||||
|
||||
from prowler.providers.aws.services.ec2.ec2_service import EC2
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.ec2.ec2_instance_port_cifs_exposed_to_internet.ec2_instance_port_cifs_exposed_to_internet_fixer.ec2_client",
|
||||
new=EC2(aws_provider),
|
||||
):
|
||||
# Test Fixer
|
||||
from prowler.providers.aws.services.ec2.ec2_instance_port_cifs_exposed_to_internet.ec2_instance_port_cifs_exposed_to_internet_fixer import (
|
||||
fixer,
|
||||
)
|
||||
|
||||
assert fixer(instance_id, AWS_REGION_EU_WEST_1)
|
||||
|
||||
@mock_aws
|
||||
def test_ec2_instance_exposed_port_in_public_subnet_only_139_port(self):
|
||||
# Create EC2 Mocked Resources
|
||||
ec2_client = client("ec2", region_name=AWS_REGION_EU_WEST_1)
|
||||
ec2_resource = resource("ec2", region_name=AWS_REGION_EU_WEST_1)
|
||||
vpc_id = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"]
|
||||
default_sg = ec2_client.describe_security_groups(GroupNames=["default"])[
|
||||
"SecurityGroups"
|
||||
][0]
|
||||
default_sg_id = default_sg["GroupId"]
|
||||
ec2_client.authorize_security_group_ingress(
|
||||
GroupId=default_sg_id,
|
||||
IpPermissions=[
|
||||
{
|
||||
"IpProtocol": "tcp",
|
||||
"FromPort": 139,
|
||||
"ToPort": 139,
|
||||
"IpRanges": [{"CidrIp": "0.0.0.0/0"}],
|
||||
}
|
||||
],
|
||||
)
|
||||
subnet_id = ec2_client.create_subnet(VpcId=vpc_id, CidrBlock="10.0.0.0/16")[
|
||||
"Subnet"
|
||||
]["SubnetId"]
|
||||
instance = ec2_resource.create_instances(
|
||||
ImageId="ami-12345678",
|
||||
MinCount=1,
|
||||
MaxCount=1,
|
||||
InstanceType="t2.micro",
|
||||
SecurityGroupIds=[default_sg_id],
|
||||
NetworkInterfaces=[
|
||||
{
|
||||
"DeviceIndex": 0,
|
||||
"SubnetId": subnet_id,
|
||||
"AssociatePublicIpAddress": True,
|
||||
}
|
||||
],
|
||||
TagSpecifications=[
|
||||
{"ResourceType": "instance", "Tags": [{"Key": "Name", "Value": "test"}]}
|
||||
],
|
||||
)[0]
|
||||
|
||||
from prowler.providers.aws.services.ec2.ec2_service import EC2
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.ec2.ec2_instance_port_cifs_exposed_to_internet.ec2_instance_port_cifs_exposed_to_internet_fixer.ec2_client",
|
||||
new=EC2(aws_provider),
|
||||
):
|
||||
# Test Fixer
|
||||
from prowler.providers.aws.services.ec2.ec2_instance_port_cifs_exposed_to_internet.ec2_instance_port_cifs_exposed_to_internet_fixer import (
|
||||
fixer,
|
||||
)
|
||||
|
||||
assert fixer(instance.id, AWS_REGION_EU_WEST_1)
|
||||
|
||||
@mock_aws
|
||||
def test_ec2_instance_exposed_port_with_public_ip_in_public_subnet_only_445_port(
|
||||
self,
|
||||
):
|
||||
# Create EC2 Mocked Resources
|
||||
ec2_client = client("ec2", region_name=AWS_REGION_EU_WEST_1)
|
||||
ec2_resource = resource("ec2", region_name=AWS_REGION_EU_WEST_1)
|
||||
vpc_id = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"]
|
||||
default_sg = ec2_client.describe_security_groups(GroupNames=["default"])[
|
||||
"SecurityGroups"
|
||||
][0]
|
||||
default_sg_id = default_sg["GroupId"]
|
||||
ec2_client.authorize_security_group_ingress(
|
||||
GroupId=default_sg_id,
|
||||
IpPermissions=[
|
||||
{
|
||||
"IpProtocol": "tcp",
|
||||
"FromPort": 445,
|
||||
"ToPort": 445,
|
||||
"IpRanges": [{"CidrIp": "0.0.0.0/0"}],
|
||||
}
|
||||
],
|
||||
)
|
||||
subnet_id = ec2_client.create_subnet(VpcId=vpc_id, CidrBlock="10.0.0.0/16")[
|
||||
"Subnet"
|
||||
]["SubnetId"]
|
||||
# add default route of subnet to an internet gateway to make it public
|
||||
igw_id = ec2_client.create_internet_gateway()["InternetGateway"][
|
||||
"InternetGatewayId"
|
||||
]
|
||||
# attach internet gateway to subnet
|
||||
ec2_client.attach_internet_gateway(InternetGatewayId=igw_id, VpcId=vpc_id)
|
||||
# create route table
|
||||
route_table_id = ec2_client.create_route_table(VpcId=vpc_id)["RouteTable"][
|
||||
"RouteTableId"
|
||||
]
|
||||
# associate route table with subnet
|
||||
ec2_client.associate_route_table(
|
||||
RouteTableId=route_table_id, SubnetId=subnet_id
|
||||
)
|
||||
# add route to route table
|
||||
ec2_client.create_route(
|
||||
RouteTableId=route_table_id,
|
||||
DestinationCidrBlock="0.0.0.0/0",
|
||||
GatewayId=igw_id,
|
||||
)
|
||||
instance = ec2_resource.create_instances(
|
||||
ImageId="ami-12345678",
|
||||
MinCount=1,
|
||||
MaxCount=1,
|
||||
InstanceType="t2.micro",
|
||||
SecurityGroupIds=[default_sg_id],
|
||||
NetworkInterfaces=[
|
||||
{
|
||||
"DeviceIndex": 0,
|
||||
"SubnetId": subnet_id,
|
||||
"AssociatePublicIpAddress": True,
|
||||
}
|
||||
],
|
||||
TagSpecifications=[
|
||||
{"ResourceType": "instance", "Tags": [{"Key": "Name", "Value": "test"}]}
|
||||
],
|
||||
)[0]
|
||||
|
||||
from prowler.providers.aws.services.ec2.ec2_service import EC2
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.ec2.ec2_instance_port_cifs_exposed_to_internet.ec2_instance_port_cifs_exposed_to_internet_fixer.ec2_client",
|
||||
new=EC2(aws_provider),
|
||||
):
|
||||
# Test Fixer
|
||||
from prowler.providers.aws.services.ec2.ec2_instance_port_cifs_exposed_to_internet.ec2_instance_port_cifs_exposed_to_internet_fixer import (
|
||||
fixer,
|
||||
)
|
||||
|
||||
assert fixer(instance.id, AWS_REGION_EU_WEST_1)
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
from unittest import mock
|
||||
|
||||
import botocore
|
||||
from boto3 import client
|
||||
from moto import mock_aws
|
||||
|
||||
from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider
|
||||
|
||||
make_api_call = botocore.client.BaseClient._make_api_call
|
||||
|
||||
|
||||
def mock_make_api_call(self, operation_name, kwarg):
|
||||
if operation_name == "DescribeLaunchTemplateVersions":
|
||||
return {
|
||||
"LaunchTemplateVersions": [
|
||||
{
|
||||
"VersionNumber": 1,
|
||||
"LaunchTemplateData": {
|
||||
"MetadataOptions": {
|
||||
"HttpEndpoint": "enabled",
|
||||
"HttpTokens": "required",
|
||||
}
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
return make_api_call(self, operation_name, kwarg)
|
||||
|
||||
|
||||
def mock_make_api_call_not_required(self, operation_name, kwarg):
|
||||
if operation_name == "DescribeLaunchTemplateVersions":
|
||||
return {
|
||||
"LaunchTemplateVersions": [
|
||||
{
|
||||
"VersionNumber": 1,
|
||||
"LaunchTemplateData": {
|
||||
"MetadataOptions": {
|
||||
"HttpEndpoint": "enabled",
|
||||
"HttpTokens": "optional",
|
||||
}
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
return make_api_call(self, operation_name, kwarg)
|
||||
|
||||
|
||||
class Test_ec2_launch_template_imdsv2_required:
|
||||
@mock_aws
|
||||
def test_no_launch_templates(self):
|
||||
ec2_client = client("ec2", region_name=AWS_REGION_US_EAST_1)
|
||||
ec2_client.launch_templates = []
|
||||
|
||||
from prowler.providers.aws.services.ec2.ec2_service import EC2
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.ec2.ec2_launch_template_imdsv2_required.ec2_launch_template_imdsv2_required.ec2_client",
|
||||
new=EC2(aws_provider),
|
||||
),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.ec2.ec2_launch_template_imdsv2_required.ec2_launch_template_imdsv2_required import (
|
||||
ec2_launch_template_imdsv2_required,
|
||||
)
|
||||
|
||||
check = ec2_launch_template_imdsv2_required()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 0
|
||||
|
||||
@mock_aws
|
||||
def test_launch_template_imdsv2_required(self):
|
||||
with mock.patch(
|
||||
"botocore.client.BaseClient._make_api_call", new=mock_make_api_call
|
||||
):
|
||||
launch_template_name = "test-imdsv2-required"
|
||||
ec2_client = client("ec2", region_name=AWS_REGION_US_EAST_1)
|
||||
ec2_client.create_launch_template(
|
||||
LaunchTemplateName=launch_template_name,
|
||||
VersionDescription="Launch Template with IMDSv2 required",
|
||||
LaunchTemplateData={
|
||||
"InstanceType": "t1.micro",
|
||||
"MetadataOptions": {
|
||||
"HttpEndpoint": "enabled",
|
||||
"HttpTokens": "required",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
launch_template_id = ec2_client.describe_launch_templates(
|
||||
LaunchTemplateNames=[launch_template_name]
|
||||
)["LaunchTemplates"][0]["LaunchTemplateId"]
|
||||
|
||||
from prowler.providers.aws.services.ec2.ec2_service import EC2
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.ec2.ec2_launch_template_imdsv2_required.ec2_launch_template_imdsv2_required.ec2_client",
|
||||
new=EC2(aws_provider),
|
||||
),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.ec2.ec2_launch_template_imdsv2_required.ec2_launch_template_imdsv2_required import (
|
||||
ec2_launch_template_imdsv2_required,
|
||||
)
|
||||
|
||||
check = ec2_launch_template_imdsv2_required()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"EC2 Launch Template {launch_template_name} has IMDSv2 enabled and required in the following versions: 1."
|
||||
)
|
||||
assert result[0].resource_id == launch_template_id
|
||||
assert result[0].region == AWS_REGION_US_EAST_1
|
||||
assert (
|
||||
result[0].resource_arn
|
||||
== f"arn:aws:ec2:{AWS_REGION_US_EAST_1}:123456789012:launch-template/{launch_template_id}"
|
||||
)
|
||||
assert result[0].resource_tags == []
|
||||
|
||||
@mock_aws
|
||||
def test_launch_template_imdsv2_not_required(self):
|
||||
with mock.patch(
|
||||
"botocore.client.BaseClient._make_api_call",
|
||||
new=mock_make_api_call_not_required,
|
||||
):
|
||||
ec2_client = client("ec2", region_name=AWS_REGION_US_EAST_1)
|
||||
launch_template_name = "test-imdsv2-not-required"
|
||||
ec2_client.create_launch_template(
|
||||
LaunchTemplateName=launch_template_name,
|
||||
VersionDescription="Launch Template without IMDSv2 required",
|
||||
LaunchTemplateData={
|
||||
"InstanceType": "t1.micro",
|
||||
"MetadataOptions": {
|
||||
"HttpEndpoint": "enabled",
|
||||
"HttpTokens": "optional",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
launch_template_id = ec2_client.describe_launch_templates(
|
||||
LaunchTemplateNames=[launch_template_name]
|
||||
)["LaunchTemplates"][0]["LaunchTemplateId"]
|
||||
|
||||
from prowler.providers.aws.services.ec2.ec2_service import EC2
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.ec2.ec2_launch_template_imdsv2_required.ec2_launch_template_imdsv2_required.ec2_client",
|
||||
new=EC2(aws_provider),
|
||||
),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.ec2.ec2_launch_template_imdsv2_required.ec2_launch_template_imdsv2_required import (
|
||||
ec2_launch_template_imdsv2_required,
|
||||
)
|
||||
|
||||
check = ec2_launch_template_imdsv2_required()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"EC2 Launch Template {launch_template_name} has IMDSv2 disabled or not required in the following versions: 1."
|
||||
)
|
||||
assert result[0].resource_id == launch_template_id
|
||||
assert result[0].region == AWS_REGION_US_EAST_1
|
||||
assert (
|
||||
result[0].resource_arn
|
||||
== f"arn:aws:ec2:{AWS_REGION_US_EAST_1}:123456789012:launch-template/{launch_template_id}"
|
||||
)
|
||||
assert result[0].resource_tags == []
|
||||
@@ -36,6 +36,10 @@ def mock_make_api_call(self, operation_name, kwarg):
|
||||
"foobar123".encode(encoding_format_utf_8)
|
||||
).decode(encoding_format_utf_8),
|
||||
"NetworkInterfaces": [{"AssociatePublicIpAddress": True}],
|
||||
"MetadataOptions": {
|
||||
"HttpEndpoint": "enabled",
|
||||
"HttpTokens": "optional",
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
@@ -741,6 +745,8 @@ class Test_EC2_Service:
|
||||
)
|
||||
|
||||
assert version.template_data.associate_public_ip_address
|
||||
assert version.template_data.http_endpoint == "enabled"
|
||||
assert version.template_data.http_tokens == "optional"
|
||||
|
||||
# Test EC2 Describe VPN Endpoints
|
||||
@mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call)
|
||||
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
from json import dumps
|
||||
from unittest import mock
|
||||
|
||||
from boto3 import client
|
||||
from moto import mock_aws
|
||||
|
||||
from tests.providers.aws.utils import AWS_REGION_EU_WEST_1, set_mocked_aws_provider
|
||||
|
||||
|
||||
class Test_ecr_repositories_not_publicly_accessible_fixer:
|
||||
@mock_aws
|
||||
def test_ecr_repository_public(self):
|
||||
ecr_client = client("ecr", region_name=AWS_REGION_EU_WEST_1)
|
||||
|
||||
repository_name = "test-repo"
|
||||
ecr_client.create_repository(repositoryName=repository_name)
|
||||
|
||||
ecr_client.set_repository_policy(
|
||||
repositoryName=repository_name,
|
||||
policyText=dumps(
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {"AWS": "*"},
|
||||
"Action": "ecr:*",
|
||||
"Resource": f"arn:aws:ecr:{AWS_REGION_EU_WEST_1}:123456789012:repository/{repository_name}",
|
||||
}
|
||||
],
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
|
||||
from prowler.providers.aws.services.ecr.ecr_service import ECR
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.ecr.ecr_repositories_not_publicly_accessible.ecr_repositories_not_publicly_accessible_fixer.ecr_client",
|
||||
new=ECR(aws_provider),
|
||||
):
|
||||
# Test Fixer
|
||||
from prowler.providers.aws.services.ecr.ecr_repositories_not_publicly_accessible.ecr_repositories_not_publicly_accessible_fixer import (
|
||||
fixer,
|
||||
)
|
||||
|
||||
assert fixer(repository_name, AWS_REGION_EU_WEST_1)
|
||||
|
||||
@mock_aws
|
||||
def test_ecr_repository_not_public(self):
|
||||
ecr_client = client("ecr", region_name=AWS_REGION_EU_WEST_1)
|
||||
|
||||
repository_name = "test-repo"
|
||||
ecr_client.create_repository(repositoryName=repository_name)
|
||||
|
||||
ecr_client.set_repository_policy(
|
||||
repositoryName=repository_name,
|
||||
policyText=dumps(
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {"AWS": "123456789012"},
|
||||
"Action": "ecr:*",
|
||||
"Resource": f"arn:aws:ecr:{AWS_REGION_EU_WEST_1}:123456789012:repository/{repository_name}",
|
||||
}
|
||||
],
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
|
||||
from prowler.providers.aws.services.ecr.ecr_service import ECR
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.ecr.ecr_repositories_not_publicly_accessible.ecr_repositories_not_publicly_accessible_fixer.ecr_client",
|
||||
new=ECR(aws_provider),
|
||||
):
|
||||
# Test Fixer
|
||||
from prowler.providers.aws.services.ecr.ecr_repositories_not_publicly_accessible.ecr_repositories_not_publicly_accessible_fixer import (
|
||||
fixer,
|
||||
)
|
||||
|
||||
assert fixer(repository_name, AWS_REGION_EU_WEST_1)
|
||||
|
||||
@mock_aws
|
||||
def test_ecr_repository_public_error(self):
|
||||
ecr_client = client("ecr", region_name=AWS_REGION_EU_WEST_1)
|
||||
|
||||
repository_name = "test-repo"
|
||||
ecr_client.create_repository(repositoryName=repository_name)
|
||||
|
||||
ecr_client.set_repository_policy(
|
||||
repositoryName=repository_name,
|
||||
policyText=dumps(
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {"AWS": "*"},
|
||||
"Action": "ecr:*",
|
||||
"Resource": f"arn:aws:ecr:{AWS_REGION_EU_WEST_1}:123456789012:repository/{repository_name}",
|
||||
}
|
||||
],
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
|
||||
from prowler.providers.aws.services.ecr.ecr_service import ECR
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.ecr.ecr_repositories_not_publicly_accessible.ecr_repositories_not_publicly_accessible_fixer.ecr_client",
|
||||
new=ECR(aws_provider),
|
||||
):
|
||||
# Test Fixer
|
||||
from prowler.providers.aws.services.ecr.ecr_repositories_not_publicly_accessible.ecr_repositories_not_publicly_accessible_fixer import (
|
||||
fixer,
|
||||
)
|
||||
|
||||
assert not fixer("repository_name_non_existing", AWS_REGION_EU_WEST_1)
|
||||
+68
-6
@@ -17,12 +17,15 @@ class Test_firehose_stream_encrypted_at_rest:
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.firehose.firehose_stream_encrypted_at_rest.firehose_stream_encrypted_at_rest.firehose_client",
|
||||
new=Firehose(aws_provider),
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.firehose.firehose_stream_encrypted_at_rest.firehose_stream_encrypted_at_rest.firehose_client",
|
||||
new=Firehose(aws_provider),
|
||||
),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.firehose.firehose_stream_encrypted_at_rest.firehose_stream_encrypted_at_rest import (
|
||||
@@ -94,6 +97,65 @@ class Test_firehose_stream_encrypted_at_rest:
|
||||
== f"Firehose Stream {stream_name} does have at rest encryption enabled."
|
||||
)
|
||||
|
||||
@mock_aws
|
||||
def test_stream_kms_encryption_enabled_aws_managed_key(self):
|
||||
# Generate S3 client
|
||||
s3_client = client("s3", region_name=AWS_REGION_EU_WEST_1)
|
||||
s3_client.create_bucket(
|
||||
Bucket="test-bucket",
|
||||
CreateBucketConfiguration={"LocationConstraint": AWS_REGION_EU_WEST_1},
|
||||
)
|
||||
|
||||
# Generate Firehose client
|
||||
firehose_client = client("firehose", region_name=AWS_REGION_EU_WEST_1)
|
||||
delivery_stream = firehose_client.create_delivery_stream(
|
||||
DeliveryStreamName="test-delivery-stream",
|
||||
DeliveryStreamType="DirectPut",
|
||||
S3DestinationConfiguration={
|
||||
"RoleARN": "arn:aws:iam::012345678901:role/firehose-role",
|
||||
"BucketARN": "arn:aws:s3:::test-bucket",
|
||||
"Prefix": "",
|
||||
"BufferingHints": {"IntervalInSeconds": 300, "SizeInMBs": 5},
|
||||
"CompressionFormat": "UNCOMPRESSED",
|
||||
},
|
||||
Tags=[{"Key": "key", "Value": "value"}],
|
||||
)
|
||||
arn = delivery_stream["DeliveryStreamARN"]
|
||||
stream_name = arn.split("/")[-1]
|
||||
|
||||
firehose_client.start_delivery_stream_encryption(
|
||||
DeliveryStreamName=stream_name,
|
||||
DeliveryStreamEncryptionConfigurationInput={
|
||||
"KeyType": "AWS_OWNED_CMK",
|
||||
},
|
||||
)
|
||||
|
||||
from prowler.providers.aws.services.firehose.firehose_service import Firehose
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
):
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.firehose.firehose_stream_encrypted_at_rest.firehose_stream_encrypted_at_rest.firehose_client",
|
||||
new=Firehose(aws_provider),
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.firehose.firehose_stream_encrypted_at_rest.firehose_stream_encrypted_at_rest import (
|
||||
firehose_stream_encrypted_at_rest,
|
||||
)
|
||||
|
||||
check = firehose_stream_encrypted_at_rest()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Firehose Stream {stream_name} does have at rest encryption enabled."
|
||||
)
|
||||
|
||||
@mock_aws
|
||||
def test_stream_kms_encryption_not_enabled(self):
|
||||
# Generate Firehose client
|
||||
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
from unittest import mock
|
||||
|
||||
import botocore
|
||||
from moto import mock_aws
|
||||
|
||||
from tests.providers.aws.utils import AWS_REGION_EU_WEST_1, set_mocked_aws_provider
|
||||
|
||||
mock_make_api_call = botocore.client.BaseClient._make_api_call
|
||||
|
||||
|
||||
def mock_make_api_call_public_vault(self, operation_name, kwarg):
|
||||
if operation_name == "DeleteVaultAccessPolicy":
|
||||
return {
|
||||
"ResponseMetadata": {
|
||||
"HTTPStatusCode": 204,
|
||||
"RequestId": "test-request-id",
|
||||
}
|
||||
}
|
||||
return mock_make_api_call(self, operation_name, kwarg)
|
||||
|
||||
|
||||
def mock_make_api_call_public_vault_error(self, operation_name, kwarg):
|
||||
if operation_name == "DeleteVaultAccessPolicy":
|
||||
raise botocore.exceptions.ClientError(
|
||||
{
|
||||
"Error": {
|
||||
"Code": "VaultNotFound",
|
||||
"Message": "VaultNotFound",
|
||||
}
|
||||
},
|
||||
operation_name,
|
||||
)
|
||||
return mock_make_api_call(self, operation_name, kwarg)
|
||||
|
||||
|
||||
class Test_glacier_vaults_policy_public_access_fixer:
|
||||
@mock_aws
|
||||
def test_glacier_vault_public(self):
|
||||
with mock.patch(
|
||||
"botocore.client.BaseClient._make_api_call",
|
||||
new=mock_make_api_call_public_vault,
|
||||
):
|
||||
from prowler.providers.aws.services.glacier.glacier_service import Glacier
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.glacier.glacier_vaults_policy_public_access.glacier_vaults_policy_public_access_fixer.glacier_client",
|
||||
new=Glacier(aws_provider),
|
||||
):
|
||||
from prowler.providers.aws.services.glacier.glacier_vaults_policy_public_access.glacier_vaults_policy_public_access_fixer import (
|
||||
fixer,
|
||||
)
|
||||
|
||||
assert fixer(resource_id="test-vault", region=AWS_REGION_EU_WEST_1)
|
||||
|
||||
@mock_aws
|
||||
def test_glacier_vault_public_error(self):
|
||||
with mock.patch(
|
||||
"botocore.client.BaseClient._make_api_call",
|
||||
new=mock_make_api_call_public_vault_error,
|
||||
):
|
||||
from prowler.providers.aws.services.glacier.glacier_service import Glacier
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.glacier.glacier_vaults_policy_public_access.glacier_vaults_policy_public_access_fixer.glacier_client",
|
||||
new=Glacier(aws_provider),
|
||||
):
|
||||
from prowler.providers.aws.services.glacier.glacier_vaults_policy_public_access.glacier_vaults_policy_public_access_fixer import (
|
||||
fixer,
|
||||
)
|
||||
|
||||
assert not fixer(resource_id="test-vault", region=AWS_REGION_EU_WEST_1)
|
||||
+18
@@ -11,6 +11,9 @@ class Test_glacier_vaults_policy_public_access:
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.glacier.glacier_service.Glacier",
|
||||
new=glacier_client,
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.glacier.glacier_client.glacier_client",
|
||||
new=glacier_client,
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.glacier.glacier_vaults_policy_public_access.glacier_vaults_policy_public_access import (
|
||||
@@ -37,6 +40,9 @@ class Test_glacier_vaults_policy_public_access:
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.glacier.glacier_service.Glacier",
|
||||
new=glacier_client,
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.glacier.glacier_client.glacier_client",
|
||||
new=glacier_client,
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.glacier.glacier_vaults_policy_public_access.glacier_vaults_policy_public_access import (
|
||||
@@ -89,6 +95,9 @@ class Test_glacier_vaults_policy_public_access:
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.glacier.glacier_service.Glacier",
|
||||
new=glacier_client,
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.glacier.glacier_client.glacier_client",
|
||||
new=glacier_client,
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.glacier.glacier_vaults_policy_public_access.glacier_vaults_policy_public_access import (
|
||||
@@ -141,6 +150,9 @@ class Test_glacier_vaults_policy_public_access:
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.glacier.glacier_service.Glacier",
|
||||
new=glacier_client,
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.glacier.glacier_client.glacier_client",
|
||||
new=glacier_client,
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.glacier.glacier_vaults_policy_public_access.glacier_vaults_policy_public_access import (
|
||||
@@ -193,6 +205,9 @@ class Test_glacier_vaults_policy_public_access:
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.glacier.glacier_service.Glacier",
|
||||
new=glacier_client,
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.glacier.glacier_client.glacier_client",
|
||||
new=glacier_client,
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.glacier.glacier_vaults_policy_public_access.glacier_vaults_policy_public_access import (
|
||||
@@ -249,6 +264,9 @@ class Test_glacier_vaults_policy_public_access:
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.glacier.glacier_service.Glacier",
|
||||
new=glacier_client,
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.glacier.glacier_client.glacier_client",
|
||||
new=glacier_client,
|
||||
):
|
||||
# Test Check
|
||||
from prowler.providers.aws.services.glacier.glacier_vaults_policy_public_access.glacier_vaults_policy_public_access import (
|
||||
|
||||
+44
-32
@@ -21,13 +21,16 @@ class Test_iam_rotate_access_key_90_days_test:
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_rotate_access_key_90_days.iam_rotate_access_key_90_days.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
) as service_client:
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_rotate_access_key_90_days.iam_rotate_access_key_90_days.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
) as service_client,
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_rotate_access_key_90_days.iam_rotate_access_key_90_days import (
|
||||
iam_rotate_access_key_90_days,
|
||||
)
|
||||
@@ -62,13 +65,16 @@ class Test_iam_rotate_access_key_90_days_test:
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_rotate_access_key_90_days.iam_rotate_access_key_90_days.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
) as service_client:
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_rotate_access_key_90_days.iam_rotate_access_key_90_days.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
) as service_client,
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_rotate_access_key_90_days.iam_rotate_access_key_90_days import (
|
||||
iam_rotate_access_key_90_days,
|
||||
)
|
||||
@@ -86,7 +92,7 @@ class Test_iam_rotate_access_key_90_days_test:
|
||||
result[0].status_extended
|
||||
== f"User {user} has not rotated access key 1 in over 90 days (100 days)."
|
||||
)
|
||||
assert result[0].resource_id == user
|
||||
assert result[0].resource_id == f"{user}-access-key-1"
|
||||
assert result[0].resource_arn == arn
|
||||
assert result[0].region == AWS_REGION_US_EAST_1
|
||||
assert result[0].resource_tags == [{"Key": "test-tag", "Value": "test"}]
|
||||
@@ -106,13 +112,16 @@ class Test_iam_rotate_access_key_90_days_test:
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_rotate_access_key_90_days.iam_rotate_access_key_90_days.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
) as service_client:
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_rotate_access_key_90_days.iam_rotate_access_key_90_days.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
) as service_client,
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_rotate_access_key_90_days.iam_rotate_access_key_90_days import (
|
||||
iam_rotate_access_key_90_days,
|
||||
)
|
||||
@@ -130,7 +139,7 @@ class Test_iam_rotate_access_key_90_days_test:
|
||||
result[0].status_extended
|
||||
== f"User {user} has not rotated access key 2 in over 90 days (100 days)."
|
||||
)
|
||||
assert result[0].resource_id == user
|
||||
assert result[0].resource_id == f"{user}-access-key-2"
|
||||
assert result[0].resource_arn == arn
|
||||
assert result[0].region == AWS_REGION_US_EAST_1
|
||||
assert result[0].resource_tags == [{"Key": "test-tag", "Value": "test"}]
|
||||
@@ -150,13 +159,16 @@ class Test_iam_rotate_access_key_90_days_test:
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
), mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_rotate_access_key_90_days.iam_rotate_access_key_90_days.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
) as service_client:
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_rotate_access_key_90_days.iam_rotate_access_key_90_days.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
) as service_client,
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_rotate_access_key_90_days.iam_rotate_access_key_90_days import (
|
||||
iam_rotate_access_key_90_days,
|
||||
)
|
||||
@@ -179,7 +191,7 @@ class Test_iam_rotate_access_key_90_days_test:
|
||||
result[0].status_extended
|
||||
== f"User {user} has not rotated access key 1 in over 90 days (100 days)."
|
||||
)
|
||||
assert result[0].resource_id == user
|
||||
assert result[0].resource_id == f"{user}-access-key-1"
|
||||
assert result[0].resource_arn == arn
|
||||
assert result[0].region == AWS_REGION_US_EAST_1
|
||||
assert result[0].resource_tags == [{"Key": "test-tag", "Value": "test"}]
|
||||
@@ -188,7 +200,7 @@ class Test_iam_rotate_access_key_90_days_test:
|
||||
result[1].status_extended
|
||||
== f"User {user} has not rotated access key 2 in over 90 days (100 days)."
|
||||
)
|
||||
assert result[1].resource_id == user
|
||||
assert result[1].resource_id == f"{user}-access-key-2"
|
||||
assert result[1].resource_arn == arn
|
||||
assert result[1].region == AWS_REGION_US_EAST_1
|
||||
assert result[1].resource_tags == [{"Key": "test-tag", "Value": "test"}]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user