Compare commits

..

16 Commits

Author SHA1 Message Date
github-actions
e37986c1b0 chore(release): 3.15.2 2024-03-21 09:21:15 +00:00
Sergio Garcia
85d6d025c5 fix(cloudtrail): use dictionary instead of list (#3579) 2024-03-21 09:56:17 +01:00
Pepe Fagoaga
c32f7ba158 fix(actions): Remove indent (#3577) 2024-03-21 09:56:09 +01:00
github-actions
50d8b23b19 chore(release): 3.15.1 2024-03-20 14:00:02 +00:00
Pepe Fagoaga
2039ec0c9f fix(action): Release on whatever branch (#3576) 2024-03-20 14:52:56 +01:00
Nacho Rivera
6a6ffbab45 chore(regions_update): Changes in regions for AWS services. (#3571)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-03-20 14:52:56 +01:00
Nacho Rivera
6871169730 chore(regions_update): Changes in regions for AWS services. (#3566)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-03-20 14:52:56 +01:00
dependabot[bot]
8e4d8b5a04 build(deps-dev): bump mkdocs-material from 9.5.12 to 9.5.14
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.12 to 9.5.14.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.12...9.5.14)

---
updated-dependencies:
- dependency-name: mkdocs-material
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-20 14:52:56 +01:00
dependabot[bot]
7ce499ec37 build(deps): bump azure-mgmt-compute from 30.5.0 to 30.6.0 (#3559)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-20 14:52:56 +01:00
dependabot[bot]
990cb7dae2 build(deps-dev): bump black from 24.2.0 to 24.3.0
Bumps [black](https://github.com/psf/black) from 24.2.0 to 24.3.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/24.2.0...24.3.0)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-20 14:52:56 +01:00
dependabot[bot]
9fea275472 build(deps): bump trufflesecurity/trufflehog from 3.69.0 to 3.70.2 (#3561)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-20 14:52:56 +01:00
dependabot[bot]
853cf8be25 build(deps): bump tj-actions/changed-files from 42 to 43 (#3560)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-20 14:52:56 +01:00
dependabot[bot]
0a3f972239 build(deps-dev): bump coverage from 7.4.3 to 7.4.4 (#3558)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-20 14:52:56 +01:00
Sergio Garcia
180864dab4 fix(iam): handle KeyError in service_last_accessed (#3555) 2024-03-20 14:52:56 +01:00
Sergio Garcia
d06ccb9af1 chore(compliance): rename AWS FTR compliance (#3550) 2024-03-20 14:52:56 +01:00
Nacho Rivera
00d9a391c4 chore(regions_update): Changes in regions for AWS services. (#3552)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-03-20 14:52:56 +01:00
668 changed files with 4477 additions and 25064 deletions

2
.github/CODEOWNERS vendored
View File

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

View File

@@ -4,7 +4,7 @@ on:
pull_request:
branches:
- 'master'
- 'v3'
- 'prowler-4.0-dev'
paths:
- 'docs/**'

View File

@@ -3,7 +3,6 @@ name: build-lint-push-containers
on:
push:
branches:
- "v3"
- "master"
paths-ignore:
- ".github/**"
@@ -14,90 +13,44 @@ on:
types: [published]
env:
# AWS Configuration
AWS_REGION_STG: eu-west-1
AWS_REGION_PLATFORM: eu-west-1
AWS_REGION: us-east-1
# Container's configuration
IMAGE_NAME: prowler
DOCKERFILE_PATH: ./Dockerfile
# Tags
LATEST_TAG: latest
STABLE_TAG: stable
# The RELEASE_TAG is set during runtime in releases
RELEASE_TAG: ""
# The PROWLER_VERSION and PROWLER_VERSION_MAJOR are set during runtime in releases
PROWLER_VERSION: ""
PROWLER_VERSION_MAJOR: ""
# TEMPORARY_TAG: temporary
# Python configuration
PYTHON_VERSION: 3.12
TEMPORARY_TAG: temporary
DOCKERFILE_PATH: ./Dockerfile
PYTHON_VERSION: 3.9
jobs:
# Build Prowler OSS container
container-build-push:
# needs: dockerfile-linter
runs-on: ubuntu-latest
outputs:
prowler_version_major: ${{ steps.get-prowler-version.outputs.PROWLER_VERSION_MAJOR }}
prowler_version: ${{ steps.update-prowler-version.outputs.PROWLER_VERSION }}
env:
POETRY_VIRTUALENVS_CREATE: "false"
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
- name: Setup python (release)
if: github.event_name == 'release'
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install Poetry
- name: Install dependencies (release)
if: github.event_name == 'release'
run: |
pipx install poetry
pipx inject poetry poetry-bumpversion
- name: Get Prowler version
id: get-prowler-version
run: |
PROWLER_VERSION="$(poetry version -s 2>/dev/null)"
# Store prowler version major just for the release
PROWLER_VERSION_MAJOR="${PROWLER_VERSION%%.*}"
echo "PROWLER_VERSION_MAJOR=${PROWLER_VERSION_MAJOR}" >> "${GITHUB_ENV}"
echo "PROWLER_VERSION_MAJOR=${PROWLER_VERSION_MAJOR}" >> "${GITHUB_OUTPUT}"
case ${PROWLER_VERSION_MAJOR} in
3)
echo "LATEST_TAG=v3-latest" >> "${GITHUB_ENV}"
echo "STABLE_TAG=v3-stable" >> "${GITHUB_ENV}"
;;
4)
echo "LATEST_TAG=latest" >> "${GITHUB_ENV}"
echo "STABLE_TAG=stable" >> "${GITHUB_ENV}"
;;
*)
# Fallback if any other version is present
echo "Releasing another Prowler major version, aborting..."
exit 1
;;
esac
- name: Update Prowler version (release)
id: update-prowler-version
if: github.event_name == 'release'
run: |
PROWLER_VERSION="${{ github.event.release.tag_name }}"
poetry version "${PROWLER_VERSION}"
echo "PROWLER_VERSION=${PROWLER_VERSION}" >> "${GITHUB_ENV}"
echo "PROWLER_VERSION=${PROWLER_VERSION}" >> "${GITHUB_OUTPUT}"
poetry version ${{ github.event.release.tag_name }}
- name: Login to DockerHub
uses: docker/login-action@v3
with:
@@ -118,7 +71,7 @@ jobs:
- name: Build and push container image (latest)
if: github.event_name == 'push'
uses: docker/build-push-action@v6
uses: docker/build-push-action@v5
with:
push: true
tags: |
@@ -130,16 +83,16 @@ jobs:
- name: Build and push container image (release)
if: github.event_name == 'release'
uses: docker/build-push-action@v6
uses: docker/build-push-action@v5
with:
# Use local context to get changes
# https://github.com/docker/build-push-action#path-context
context: .
push: true
tags: |
${{ secrets.DOCKER_HUB_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ env.PROWLER_VERSION }}
${{ secrets.DOCKER_HUB_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ github.event.release.tag_name }}
${{ secrets.DOCKER_HUB_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ env.STABLE_TAG }}
${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ env.PROWLER_VERSION }}
${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ github.event.release.tag_name }}
${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ env.STABLE_TAG }}
file: ${{ env.DOCKERFILE_PATH }}
cache-from: type=gha
@@ -149,26 +102,16 @@ jobs:
needs: container-build-push
runs-on: ubuntu-latest
steps:
- name: Get latest commit info (latest)
- name: Get latest commit info
if: github.event_name == 'push'
run: |
LATEST_COMMIT_HASH=$(echo ${{ github.event.after }} | cut -b -7)
echo "LATEST_COMMIT_HASH=${LATEST_COMMIT_HASH}" >> $GITHUB_ENV
- name: Dispatch event (latest)
if: github.event_name == 'push' && needs.container-build-push.outputs.prowler_version_major == '3'
- name: Dispatch event for latest
if: github.event_name == 'push'
run: |
curl https://api.github.com/repos/${{ secrets.DISPATCH_OWNER }}/${{ secrets.DISPATCH_REPO }}/dispatches \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.ACCESS_TOKEN }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
--data '{"event_type":"dispatch","client_payload":{"version":"v3-latest", "tag": "${{ env.LATEST_COMMIT_HASH }}"}}'
- name: Dispatch event (release)
if: github.event_name == 'release' && needs.container-build-push.outputs.prowler_version_major == '3'
curl https://api.github.com/repos/${{ secrets.DISPATCH_OWNER }}/${{ secrets.DISPATCH_REPO }}/dispatches -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.ACCESS_TOKEN }}" -H "X-GitHub-Api-Version: 2022-11-28" --data '{"event_type":"dispatch","client_payload":{"version":"latest", "tag": "${{ env.LATEST_COMMIT_HASH }}"}}'
- name: Dispatch event for release
if: github.event_name == 'release'
run: |
curl https://api.github.com/repos/${{ secrets.DISPATCH_OWNER }}/${{ secrets.DISPATCH_REPO }}/dispatches \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.ACCESS_TOKEN }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
--data '{"event_type":"dispatch","client_payload":{"version":"release", "tag":"${{ needs.container-build-push.outputs.prowler_version }}"}}'
curl https://api.github.com/repos/${{ secrets.DISPATCH_OWNER }}/${{ secrets.DISPATCH_REPO }}/dispatches -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.ACCESS_TOKEN }}" -H "X-GitHub-Api-Version: 2022-11-28" --data '{"event_type":"dispatch","client_payload":{"version":"release", "tag":"${{ github.event.release.tag_name }}"}}'

View File

@@ -13,10 +13,10 @@ name: "CodeQL"
on:
push:
branches: [ "master", "v3" ]
branches: [ "master", "prowler-4.0-dev" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "master", "v3" ]
branches: [ "master", "prowler-4.0-dev" ]
schedule:
- cron: '00 12 * * *'

View File

@@ -11,9 +11,8 @@ jobs:
with:
fetch-depth: 0
- name: TruffleHog OSS
uses: trufflesecurity/trufflehog@v3.79.0
uses: trufflesecurity/trufflehog@v3.70.2
with:
path: ./
base: ${{ github.event.repository.default_branch }}
head: HEAD
extra_args: --only-verified

View File

@@ -4,7 +4,7 @@ on:
pull_request_target:
branches:
- "master"
- "v3"
- "prowler-4.0-dev"
jobs:
labeler:

View File

@@ -4,11 +4,11 @@ on:
push:
branches:
- "master"
- "v3"
- "prowler-4.0-dev"
pull_request:
branches:
- "master"
- "v3"
- "prowler-4.0-dev"
jobs:
build:
runs-on: ubuntu-latest
@@ -20,7 +20,7 @@ jobs:
- uses: actions/checkout@v4
- name: Test if changes are in not ignored paths
id: are-non-ignored-files-changed
uses: tj-actions/changed-files@v44
uses: tj-actions/changed-files@v43
with:
files: ./**
files_ignore: |
@@ -73,7 +73,7 @@ jobs:
- name: Safety
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
run: |
poetry run safety check --ignore 67599 --ignore 70612
poetry run safety check
- name: Vulture
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
run: |

View File

@@ -8,7 +8,10 @@ env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
PYTHON_VERSION: 3.11
CACHE: "poetry"
# TODO: create a bot user for this kind of tasks, like prowler-bot
# This base branch is used to create a PR with the updated version
# We'd need to handle the base branch for v4 and v3, since they will be
# `master` and `3.0-dev`, respectively
GITHUB_BASE_BRANCH: "master"
GIT_COMMITTER_EMAIL: "sergio@prowler.com"
jobs:
@@ -18,23 +21,6 @@ jobs:
POETRY_VIRTUALENVS_CREATE: "false"
name: Release Prowler to PyPI
steps:
- name: Get Prowler version
run: |
PROWLER_VERSION="${{ env.RELEASE_TAG }}"
case ${PROWLER_VERSION%%.*} in
3)
echo "Releasing Prowler v3 with tag ${PROWLER_VERSION}"
;;
4)
echo "Releasing Prowler v4 with tag ${PROWLER_VERSION}"
;;
*)
echo "Releasing another Prowler major version, aborting..."
exit 1
;;
esac
- uses: actions/checkout@v4
- name: Install dependencies
@@ -53,7 +39,7 @@ jobs:
poetry version ${{ env.RELEASE_TAG }}
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v6
uses: crazy-max/ghaction-import-gpg@v4
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.GPG_PASSPHRASE }}
@@ -76,6 +62,12 @@ jobs:
# Push the tag
git push -f origin ${{ env.RELEASE_TAG }}
- name: Create new branch for the version update
run: |
git switch -c release-${{ env.RELEASE_TAG }}
git push --set-upstream origin release-${{ env.RELEASE_TAG }}
- name: Build Prowler package
run: |
poetry build
@@ -85,6 +77,23 @@ jobs:
poetry config pypi-token.pypi ${{ secrets.PYPI_API_TOKEN }}
poetry publish
- name: Create PR to update version in the branch
run: |
echo "### Description
This PR updates Prowler Version to ${{ env.RELEASE_TAG }}.
### License
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license." |\
gh pr create \
--base ${{ env.GITHUB_BASE_BRANCH }} \
--head release-${{ env.RELEASE_TAG }} \
--title "chore(release): update Prowler Version to ${{ env.RELEASE_TAG }}." \
--body-file -
env:
GH_TOKEN: ${{ secrets.PROWLER_ACCESS_TOKEN }}
- name: Replicate PyPI package
run: |
rm -rf ./dist && rm -rf ./build && rm -rf prowler.egg-info

View File

@@ -96,7 +96,7 @@ repos:
- id: safety
name: safety
description: "Safety is a tool that checks your installed dependencies for known security vulnerabilities"
entry: bash -c 'safety check --ignore 67599 --ignore 70612'
entry: bash -c 'safety check'
language: system
- id: vulture

View File

@@ -10,4 +10,4 @@
Want some swag as appreciation for your contribution?
# Prowler Developer Guide
https://docs.prowler.com/projects/prowler-open-source/en/latest/developer-guide/introduction/
https://docs.prowler.cloud/en/latest/tutorials/developer-guide/

View File

@@ -1,4 +1,4 @@
FROM python:3.12-alpine
FROM python:3.11-alpine
LABEL maintainer="https://github.com/prowler-cloud/prowler"

View File

@@ -8,7 +8,7 @@
<p align="center">
<b>Learn more at <a href="https://prowler.com">prowler.com</i></b>
</p>
<p align="center">
<a href="https://join.slack.com/t/prowler-workspace/shared_invite/zt-1hix76xsl-2uq222JIXrC7Q8It~9ZNog"><img width="30" height="30" alt="Prowler community on Slack" src="https://github.com/prowler-cloud/prowler/assets/3985464/3617e470-670c-47c9-9794-ce895ebdb627"></a>
<br>
@@ -49,7 +49,7 @@ It contains hundreds of controls covering CIS, NIST 800, NIST CSF, CISA, RBI, Fe
|---|---|---|---|---|
| AWS | 304 | 61 -> `prowler aws --list-services` | 28 -> `prowler aws --list-compliance` | 6 -> `prowler aws --list-categories` |
| GCP | 75 | 11 -> `prowler gcp --list-services` | 1 -> `prowler gcp --list-compliance` | 2 -> `prowler gcp --list-categories`|
| Azure | 127 | 16 -> `prowler azure --list-services` | 2 -> `prowler azure --list-compliance` | 2 -> `prowler azure --list-categories` |
| Azure | 109 | 16 -> `prowler azure --list-services` | CIS soon | 2 -> `prowler azure --list-categories` |
| Kubernetes | Work In Progress | - | CIS soon | - |
# 📖 Documentation

View File

@@ -243,11 +243,11 @@ Each Prowler check has metadata associated which is stored at the same level of
# Code holds different methods to remediate the FAIL finding
"Code": {
# CLI holds the command in the provider native CLI to remediate it
"CLI": "https://docs.prowler.com/checks/public_8#cli-command",
"CLI": "https://docs.bridgecrew.io/docs/public_8#cli-command",
# NativeIaC holds the native IaC code to remediate it, use "https://docs.bridgecrew.io/docs"
"NativeIaC": "",
# Other holds the other commands, scripts or code to remediate it, use "https://www.trendmicro.com/cloudoneconformity"
"Other": "https://docs.prowler.com/checks/public_8#aws-console",
"Other": "https://docs.bridgecrew.io/docs/public_8#aws-console",
# Terraform holds the Terraform code to remediate it, use "https://docs.bridgecrew.io/docs"
"Terraform": ""
},

View File

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

View File

@@ -175,8 +175,6 @@ class <Service>(ServiceParentClass):
f"{<item>.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
```
???+note
To avoid fake findings, when Prowler can't retrieve the items, because an Access Denied or similar error, we set that items value as `None`.
### Service Models

View File

@@ -509,113 +509,7 @@ class Test_compute_firewall_rdp_access_from_the_internet_allowed:
### Services
For testing Google Cloud Services, we have to follow the same logic as with the Google Cloud checks. We still mocking all API calls, but in this case, every API call to set up an attribute is defined in [fixtures file](https://github.com/prowler-cloud/prowler/blob/master/tests/providers/gcp/gcp_fixtures.py) in `mock_api_client` function. Remember that EVERY method of a service must be tested.
The following code shows a real example of a testing class, but it has more comments than usual for educational purposes.
```python title="BigQuery Service Test"
# We need to import the unittest.mock.patch to allow us to patch some objects
# not to use shared ones between test, hence to isolate the test
from unittest.mock import patch
# Import the class needed from the service file
from prowler.providers.gcp.services.bigquery.bigquery_service import BigQuery
# Necessary constans and functions from fixtures file
from tests.providers.gcp.gcp_fixtures import (
GCP_PROJECT_ID,
mock_api_client,
mock_is_api_active,
set_mocked_gcp_audit_info,
)
class TestBigQueryService:
# Only method needed to test full service
def test_service(self):
# In this case we are mocking the __is_api_active__ to ensure our mocked project is used
# And all the client to use our mocked API calls
with patch(
"prowler.providers.gcp.lib.service.service.GCPService.__is_api_active__",
new=mock_is_api_active,
), patch(
"prowler.providers.gcp.lib.service.service.GCPService.__generate_client__",
new=mock_api_client,
):
# Instantiate an object of class with the mocked provider
bigquery_client = BigQuery(
set_mocked_gcp_audit_info(project_ids=[GCP_PROJECT_ID])
)
# Check all attributes of the tested class is well set up according API calls mocked from GCP fixture file
assert bigquery_client.service == "bigquery"
assert bigquery_client.project_ids == [GCP_PROJECT_ID]
assert len(bigquery_client.datasets) == 2
assert bigquery_client.datasets[0].name == "unique_dataset1_name"
assert bigquery_client.datasets[0].id.__class__.__name__ == "str"
assert bigquery_client.datasets[0].region == "US"
assert bigquery_client.datasets[0].cmk_encryption
assert bigquery_client.datasets[0].public
assert bigquery_client.datasets[0].project_id == GCP_PROJECT_ID
assert bigquery_client.datasets[1].name == "unique_dataset2_name"
assert bigquery_client.datasets[1].id.__class__.__name__ == "str"
assert bigquery_client.datasets[1].region == "EU"
assert not bigquery_client.datasets[1].cmk_encryption
assert not bigquery_client.datasets[1].public
assert bigquery_client.datasets[1].project_id == GCP_PROJECT_ID
assert len(bigquery_client.tables) == 2
assert bigquery_client.tables[0].name == "unique_table1_name"
assert bigquery_client.tables[0].id.__class__.__name__ == "str"
assert bigquery_client.tables[0].region == "US"
assert bigquery_client.tables[0].cmk_encryption
assert bigquery_client.tables[0].project_id == GCP_PROJECT_ID
assert bigquery_client.tables[1].name == "unique_table2_name"
assert bigquery_client.tables[1].id.__class__.__name__ == "str"
assert bigquery_client.tables[1].region == "US"
assert not bigquery_client.tables[1].cmk_encryption
assert bigquery_client.tables[1].project_id == GCP_PROJECT_ID
```
As it can be confusing where all these values come from, I'll give an example to make this clearer. First we need to check
what is the API call used to obtain the datasets. In this case if we check the service the call is
`self.client.datasets().list(projectId=project_id)`.
Now in the fixture file we have to mock this call in our `MagicMock` client in the function `mock_api_client`. The best way to mock
is following the actual format, add one function where the client is passed to be changed, the format of this function name must be
`mock_api_<endpoint>_calls` (*endpoint* refers to the first attribute pointed after *client*).
In the example of BigQuery the function is called `mock_api_dataset_calls`. And inside of this function we found an assignation to
be used in the `__get_datasets__` method in BigQuery class:
```python
# Mocking datasets
dataset1_id = str(uuid4())
dataset2_id = str(uuid4())
client.datasets().list().execute.return_value = {
"datasets": [
{
"datasetReference": {
"datasetId": "unique_dataset1_name",
"projectId": GCP_PROJECT_ID,
},
"id": dataset1_id,
"location": "US",
},
{
"datasetReference": {
"datasetId": "unique_dataset2_name",
"projectId": GCP_PROJECT_ID,
},
"id": dataset2_id,
"location": "EU",
},
]
}
```
Coming soon ...
## Azure
@@ -623,8 +517,6 @@ client.datasets().list().execute.return_value = {
For the Azure Provider we don't have any library to mock out the API calls we use. So in this scenario we inject the objects in the service client using [MagicMock](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.MagicMock).
In essence, we create object instances and we run the check that we are testing with that instance. In the test we ensure the check executed correctly and results with the expected values.
The following code shows how to use MagicMock to create the service objects for a Azure check test.
```python
@@ -665,8 +557,11 @@ class Test_defender_ensure_defender_for_arm_is_on:
# In this scenario we have to mock also the Defender service and the defender_client from the check to enforce that the defender_client used is the one created within this check because patch != import, and if you execute tests in parallel some objects can be already initialised hence the check won't be isolated.
# In this case we don't use the Moto decorator, we use the mocked Defender client for both objects
with mock.patch(
"prowler.providers.azure.services.defender.defender_ensure_defender_for_arm_is_on.defender_ensure_defender_for_arm_is_on.defender_client",
with mock.patch(
"prowler.providers.azure.services.defender.defender_service.Defender",
new=defender_client,
), mock.patch(
"prowler.providers.azure.services.defender.defender_client.defender_client",
new=defender_client,
):
@@ -680,7 +575,7 @@ class Test_defender_ensure_defender_for_arm_is_on:
check = defender_ensure_defender_for_arm_is_on()
# And then, call the execute() function to run the check
# against the Defender client we've set up.
# against the IAM client we've set up.
result = check.execute()
# Last but not least, we need to assert all the fields
@@ -698,171 +593,4 @@ class Test_defender_ensure_defender_for_arm_is_on:
### Services
For the Azure Services tests, the idea is similar, we test that the functions we've done for capturing the values of the different objects using the Azure API work correctly. Again, we create an object instance and verify that the values captured for that instance are correct.
The following code shows how a service test looks like.
```python
#We import patch from unittest.mock for simulating objects, the ones that we'll test with.
from unittest.mock import patch
#Importing FlowLogs from azure.mgmt.network.models allows us to create objects corresponding
#to flow log settings for Azure networking resources.
from azure.mgmt.network.models import FlowLog
#We import the different classes of the Network Service so we can use them.
from prowler.providers.azure.services.network.network_service import (
BastionHost,
Network,
NetworkWatcher,
PublicIp,
SecurityGroup,
)
#Azure constants
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION,
set_mocked_azure_audit_info,
)
#Mocks the behavior of a function responsible for retrieving security groups from a network service so
#basically this is the instance for SecurityGroup that we are going to use
def mock_network_get_security_groups(_):
return {
AZURE_SUBSCRIPTION: [
SecurityGroup(
id="id",
name="name",
location="location",
security_rules=[],
)
]
}
#We do the same for all the components we need, BastionHost, NetworkWatcher and PublicIp in this case
def mock_network_get_bastion_hosts(_):
return {
AZURE_SUBSCRIPTION: [
BastionHost(
id="id",
name="name",
location="location",
)
]
}
def mock_network_get_network_watchers(_):
return {
AZURE_SUBSCRIPTION: [
NetworkWatcher(
id="id",
name="name",
location="location",
flow_logs=[FlowLog(enabled=True, retention_policy=90)],
)
]
}
def mock_network_get_public_ip_addresses(_):
return {
AZURE_SUBSCRIPTION: [
PublicIp(
id="id",
name="name",
location="location",
ip_address="ip_address",
)
]
}
#We use the 'path' decorator to replace during the test, the original get functions with the mock functions.
#In this case we are replacing the '__get_security_groups__' with the 'mock_network_get_security_groups'.
#We do the same for the rest of the functions.
@patch(
"prowler.providers.azure.services.network.network_service.Network.__get_security_groups__",
new=mock_network_get_security_groups,
)
@patch(
"prowler.providers.azure.services.network.network_service.Network.__get_bastion_hosts__",
new=mock_network_get_bastion_hosts,
)
@patch(
"prowler.providers.azure.services.network.network_service.Network.__get_network_watchers__",
new=mock_network_get_network_watchers,
)
@patch(
"prowler.providers.azure.services.network.network_service.Network.__get_public_ip_addresses__",
new=mock_network_get_public_ip_addresses,
)
#We create the class for finally testing the methods
class Test_Network_Service:
#Verifies that Network class initializes correctly a client object
def test__get_client__(self):
#Creates instance of the Network class with the audit information provided
network = Network(set_mocked_azure_audit_info())
#Checks if the client is not being initialize correctly
assert (
network.clients[AZURE_SUBSCRIPTION].__class__.__name__
== "NetworkManagementClient"
)
#Verifies Securiy Group are set correctly
def test__get_security_groups__(self):
network = Network(set_mocked_azure_audit_info())
assert (
network.security_groups[AZURE_SUBSCRIPTION][0].__class__.__name__
== "SecurityGroup"
)
#As you can see, every field must be right according to the mocking method
assert network.security_groups[AZURE_SUBSCRIPTION][0].id == "id"
assert network.security_groups[AZURE_SUBSCRIPTION][0].name == "name"
assert network.security_groups[AZURE_SUBSCRIPTION][0].location == "location"
assert network.security_groups[AZURE_SUBSCRIPTION][0].security_rules == []
#Verifies Network Watchers are set correctly
def test__get_network_watchers__(self):
network = Network(set_mocked_azure_audit_info())
assert (
network.network_watchers[AZURE_SUBSCRIPTION][0].__class__.__name__
== "NetworkWatcher"
)
assert network.network_watchers[AZURE_SUBSCRIPTION][0].id == "id"
assert network.network_watchers[AZURE_SUBSCRIPTION][0].name == "name"
assert network.network_watchers[AZURE_SUBSCRIPTION][0].location == "location"
assert network.network_watchers[AZURE_SUBSCRIPTION][0].flow_logs == [
FlowLog(enabled=True, retention_policy=90)
]
#Verifies Flow Logs are set correctly
def __get_flow_logs__(self):
network = Network(set_mocked_azure_audit_info())
nw_name = "name"
assert (
network.network_watchers[AZURE_SUBSCRIPTION][0]
.flow_logs[nw_name][0]
.__class__.__name__
== "FlowLog"
)
assert network.network_watchers[AZURE_SUBSCRIPTION][0].flow_logs == [
FlowLog(enabled=True, retention_policy=90)
]
assert (
network.network_watchers[AZURE_SUBSCRIPTION][0].flow_logs[0].enabled is True
)
assert (
network.network_watchers[AZURE_SUBSCRIPTION][0]
.flow_logs[0]
.retention_policy
== 90
)
...
```
The code continues with some more verifications the same way.
Hopefully this will result useful for understanding and creating new Azure Services checks.
Please refer to the [Azure checks tests](./unit-testing.md#azure) for more information on how to create tests and check the existing services tests [here](https://github.com/prowler-cloud/prowler/tree/master/tests/providers/azure/services).
Coming soon ...

View File

@@ -64,17 +64,16 @@ The other three cases does not need additional configuration, `--az-cli-auth` an
To use each one you need to pass the proper flag to the execution. Prowler for Azure handles two types of permission scopes, which are:
- **Microsoft Entra ID permissions**: Used to retrieve metadata from the identity assumed by Prowler (not mandatory to have access to execute the tool).
- **Azure Active Directory permissions**: Used to retrieve metadata from the identity assumed by Prowler and future AAD checks (not mandatory to have access to execute the tool)
- **Subscription scope permissions**: Required to launch the checks against your resources, mandatory to launch the tool.
#### Microsoft Entra ID scope
#### Azure Active Directory scope
Microsoft Entra ID (AAD earlier) permissions required by the tool are the following:
- `Directory.Read.All`
- `Policy.Read.All`
- `UserAuthenticationMethod.Read.All`
The best way to assign it is through the Azure web console:
@@ -87,10 +86,9 @@ The best way to assign it is through the Azure web console:
5. In the left menu bar, select "API permissions"
6. Then click on "+ Add a permission" and select "Microsoft Graph"
7. Once in the "Microsoft Graph" view, select "Application permissions"
8. Finally, search for "Directory", "Policy" and "UserAuthenticationMethod" select the following permissions:
8. Finally, search for "Directory" and "Policy" and select the following permissions:
- `Directory.Read.All`
- `Policy.Read.All`
- `UserAuthenticationMethod.Read.All`
![EntraID Permissions](../img/AAD-permissions.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 376 KiB

After

Width:  |  Height:  |  Size: 358 KiB

View File

@@ -95,8 +95,7 @@ checks_v3_to_v2_mapping = {
"ec2_networkacl_allow_ingress_any_port": "extra7138",
"ec2_networkacl_allow_ingress_tcp_port_22": "check45",
"ec2_networkacl_allow_ingress_tcp_port_3389": "check46",
"ec2_securitygroup_allow_ingress_from_internet_to_all_ports": "extra748",
"ec2_securitygroup_allow_ingress_from_internet_to_any_port": "extra74",
"ec2_securitygroup_allow_ingress_from_internet_to_any_port": "extra748",
"ec2_securitygroup_allow_ingress_from_internet_to_port_mongodb_27017_27018": "extra753",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_ftp_port_20_21": "extra7134",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22": "check41",

View File

@@ -17,8 +17,6 @@ Currently, the available frameworks are:
- `cis_1.5_aws`
- `cis_2.0_aws`
- `cis_2.0_gcp`
- `cis_2.0_azure`
- `cis_2.1_azure`
- `cis_3.0_aws`
- `cisa_aws`
- `ens_rd2022_aws`

View File

@@ -33,8 +33,6 @@ The following list includes all the AWS checks with configurable variables that
| `drs_job_exist` | `allowlist_non_default_regions` | Boolean |
| `guardduty_is_enabled` | `allowlist_non_default_regions` | Boolean |
| `securityhub_enabled` | `allowlist_non_default_regions` | Boolean |
| `rds_instance_backup_enabled` | `check_rds_instance_replicas` | Boolean |
| `acm_certificates_expiration_check` | `days_to_expire_threshold` | Integer |
## Azure
@@ -61,6 +59,7 @@ The following list includes all the Azure checks with configurable variables tha
```yaml title="config.yaml"
# AWS Configuration
aws:
# AWS Global Configuration
# aws.allowlist_non_default_regions --> Allowlist Failed Findings in non-default regions for GuardDuty, SecurityHub, DRS and Config
allowlist_non_default_regions: False
@@ -73,7 +72,6 @@ aws:
# AWS EC2 Configuration
# aws.ec2_elastic_ip_shodan
# TODO: create common config
shodan_api_key: null
# aws.ec2_securitygroup_with_many_ingress_egress_rules --> by default is 50 rules
max_security_group_rules: 50
@@ -102,67 +100,48 @@ aws:
# aws.awslambda_function_using_supported_runtimes
obsolete_lambda_runtimes:
[
"java8",
"go1.x",
"provided",
"python3.6",
"python2.7",
"python3.7",
"nodejs4.3",
"nodejs4.3-edge",
"nodejs6.10",
"nodejs",
"nodejs8.10",
"nodejs10.x",
"nodejs12.x",
"nodejs14.x",
"dotnet5.0",
"dotnetcore1.0",
"dotnetcore2.0",
"dotnetcore2.1",
"dotnetcore3.1",
"ruby2.5",
"ruby2.7",
]
# AWS Organizations
# aws.organizations_scp_check_deny_regions
# aws.organizations_enabled_regions: [
# "eu-central-1",
# "eu-west-1",
# organizations_scp_check_deny_regions
# organizations_enabled_regions: [
# 'eu-central-1',
# 'eu-west-1',
# "us-east-1"
# ]
organizations_enabled_regions: []
organizations_trusted_delegated_administrators: []
# AWS ECR
# aws.ecr_repositories_scan_vulnerabilities_in_latest_image
# ecr_repositories_scan_vulnerabilities_in_latest_image
# CRITICAL
# HIGH
# MEDIUM
ecr_repository_vulnerability_minimum_severity: "MEDIUM"
# AWS Trusted Advisor
# aws.trustedadvisor_premium_support_plan_subscribed
# trustedadvisor_premium_support_plan_subscribed
verify_premium_support_plans: True
# AWS RDS
# aws.rds_instance_backup_enabled
# Whether to check RDS instance replicas or not
check_rds_instance_replicas: False
# AWS ACM Configuration
# aws.acm_certificates_expiration_check
days_to_expire_threshold: 7
# Azure Configuration
azure:
# Azure Network Configuration
# azure.network_public_ip_shodan
# TODO: create common config
shodan_api_key: null
# Azure App Service
# Azure App Configuration
# azure.app_ensure_php_version_is_latest
php_latest_version: "8.2"
# azure.app_ensure_python_version_is_latest

View File

@@ -11,12 +11,6 @@ prowler <provider> --ignore-unused-services
## Services that can be ignored
### AWS
#### ACM
You can have certificates in ACM that is not in use by any AWS resource.
Prowler will check if every certificate is going to expire soon, if this certificate is not in use by default it is not going to be check if it is expired, is going to expire soon or it is good.
- `acm_certificates_expiration_check`
#### Athena
When you create an AWS Account, Athena will create a default primary workgroup for you.
Prowler will check if that workgroup is enabled and if it is being used by checking if there were queries in the last 45 days.
@@ -36,10 +30,9 @@ If EBS default encyption is not enabled, sensitive information at rest is not pr
- `ec2_ebs_default_encryption`
If your Security groups are not properly configured the attack surface is increased, nonetheless, Prowler will detect those security groups that are being used (they are attached) to only notify those that are being used. This logic applies to the 15 checks related to open ports in security groups and the check for the default security group.
If your Security groups are not properly configured the attack surface is increased, nonetheless, Prowler will detect those security groups that are being used (they are attached) to only notify those that are being used. This logic applies to the 15 checks related to open ports in security groups.
- `ec2_securitygroup_allow_ingress_from_internet_to_port_X` (15 checks)
- `ec2_securitygroup_default_restrict_traffic`
Prowler will also check for used Network ACLs to only alerts those with open ports that are being used.
@@ -76,15 +69,3 @@ You should enable Public Access Block at the account level to prevent the exposu
VPC Flow Logs provide visibility into network traffic that traverses the VPC and can be used to detect anomalous traffic or insight during security workflows. Nevertheless, Prowler will only check if the Flow Logs are enabled for those VPCs that are in use, in other words, only the VPCs where you have ENIs (network interfaces).
- `vpc_flow_logs_enabled`
VPC subnets must not have public IP addresses by default to prevent the exposure of your resources to the internet. Prowler will only check this configuration for those VPCs that are in use, in other words, only the VPCs where you have ENIs (network interfaces).
- `vpc_subnet_no_public_ip_by_default`
VPCs should have separate private and public subnets to prevent the exposure of your resources to the internet. Prowler will only check this configuration for those VPCs that are in use, in other words, only the VPCs where you have ENIs (network interfaces).
- `vpc_subnet_separate_private_public`
VPCs should have subnets in different availability zones to prevent a single point of failure. Prowler will only check this configuration for those VPCs that are in use, in other words, only the VPCs where you have ENIs (network interfaces).
- `vpc_subnet_different_az`

View File

@@ -11,7 +11,7 @@ prowler <provider> --slack
![Prowler Slack Message](img/slack-prowler-message.png)
???+ note
Slack integration needs SLACK_API_TOKEN and SLACK_CHANNEL_NAME environment variables.
Slack integration needs SLACK_API_TOKEN and SLACK_CHANNEL_ID environment variables.
### Configuration
@@ -35,4 +35,4 @@ To configure the Slack Integration, follow the next steps:
4. Set the following environment variables that Prowler will read:
- `SLACK_API_TOKEN`: the *Slack App OAuth Token* that was previously get.
- `SLACK_CHANNEL_NAME`: the name of your Slack Channel where Prowler will send the message.
- `SLACK_CHANNEL_ID`: the name of your Slack Channel where Prowler will send the message.

View File

@@ -16,23 +16,8 @@ theme:
- navigation.sections
- navigation.top
palette:
# Palette toggle for light mode
- media: "(prefers-color-scheme: light)"
scheme: default
primary: black
accent: green
toggle:
icon: material/weather-night
name: Switch to dark mode
# Palette toggle for dark mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
primary: black
accent: green
toggle:
icon: material/weather-sunny
name: Switch to light mode
primary: black
accent: green
plugins:
- search

1744
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -204,23 +204,17 @@ def prowler():
stats = extract_findings_statistics(findings)
if args.slack:
if "SLACK_API_TOKEN" in os.environ and (
"SLACK_CHANNEL_NAME" in os.environ or "SLACK_CHANNEL_ID" in os.environ
):
if "SLACK_API_TOKEN" in os.environ and "SLACK_CHANNEL_ID" in os.environ:
_ = send_slack_message(
os.environ["SLACK_API_TOKEN"],
(
os.environ["SLACK_CHANNEL_NAME"]
if "SLACK_CHANNEL_NAME" in os.environ
else os.environ["SLACK_CHANNEL_ID"]
),
os.environ["SLACK_CHANNEL_ID"],
stats,
provider,
audit_info,
)
else:
logger.critical(
"Slack integration needs SLACK_API_TOKEN and SLACK_CHANNEL_NAME environment variables (see more in https://docs.prowler.cloud/en/latest/tutorials/integrations/#slack)."
"Slack integration needs SLACK_API_TOKEN and SLACK_CHANNEL_ID environment variables (see more in https://docs.prowler.cloud/en/latest/tutorials/integrations/#slack)."
)
sys.exit(1)

View File

@@ -363,7 +363,7 @@
"Checks": [
"ec2_ami_public",
"ec2_instance_public_ip",
"ec2_securitygroup_allow_ingress_from_internet_to_all_ports",
"ec2_securitygroup_allow_ingress_from_internet_to_any_port",
"ec2_securitygroup_allow_ingress_from_internet_to_port_mongodb_27017_27018",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_ftp_port_20_21",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22",

View File

@@ -719,7 +719,7 @@
"ec2_networkacl_allow_ingress_any_port",
"ec2_networkacl_allow_ingress_tcp_port_22",
"ec2_networkacl_allow_ingress_tcp_port_3389",
"ec2_securitygroup_allow_ingress_from_internet_to_all_ports",
"ec2_securitygroup_allow_ingress_from_internet_to_any_port",
"ec2_securitygroup_allow_ingress_from_internet_to_port_mongodb_27017_27018",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_ftp_port_20_21",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22",

View File

@@ -1168,7 +1168,7 @@
"Id": "5.2",
"Description": "Ensure no security groups allow ingress from 0.0.0.0/0 to remote server administration ports",
"Checks": [
"ec2_securitygroup_allow_ingress_from_internet_to_all_ports",
"ec2_securitygroup_allow_ingress_from_internet_to_any_port",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_3389"
],

View File

@@ -1252,7 +1252,7 @@
"Id": "5.2",
"Description": "Ensure no security groups allow ingress from 0.0.0.0/0 to remote server administration ports",
"Checks": [
"ec2_securitygroup_allow_ingress_from_internet_to_all_ports",
"ec2_securitygroup_allow_ingress_from_internet_to_any_port",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_3389"
],
@@ -1275,7 +1275,7 @@
"Id": "5.3",
"Description": "Ensure no security groups allow ingress from ::/0 to remote server administration ports",
"Checks": [
"ec2_securitygroup_allow_ingress_from_internet_to_all_ports",
"ec2_securitygroup_allow_ingress_from_internet_to_any_port",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_3389"
],

View File

@@ -1250,7 +1250,7 @@
"Id": "5.2",
"Description": "Ensure no security groups allow ingress from 0.0.0.0/0 to remote server administration ports",
"Checks": [
"ec2_securitygroup_allow_ingress_from_internet_to_all_ports",
"ec2_securitygroup_allow_ingress_from_internet_to_any_port",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_3389"
],
@@ -1273,7 +1273,7 @@
"Id": "5.3",
"Description": "Ensure no security groups allow ingress from ::/0 to remote server administration ports",
"Checks": [
"ec2_securitygroup_allow_ingress_from_internet_to_all_ports",
"ec2_securitygroup_allow_ingress_from_internet_to_any_port",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_3389"
],

View File

@@ -1208,7 +1208,7 @@
"Id": "5.2",
"Description": "Ensure no security groups allow ingress from 0.0.0.0/0 to remote server administration ports",
"Checks": [
"ec2_securitygroup_allow_ingress_from_internet_to_all_ports",
"ec2_securitygroup_allow_ingress_from_internet_to_any_port",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_3389"
],
@@ -1231,7 +1231,7 @@
"Id": "5.3",
"Description": "Ensure no security groups allow ingress from ::/0 to remote server administration ports",
"Checks": [
"ec2_securitygroup_allow_ingress_from_internet_to_all_ports",
"ec2_securitygroup_allow_ingress_from_internet_to_any_port",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_3389"
],

View File

@@ -134,7 +134,7 @@
"vpc_endpoint_connections_trust_boundaries",
"ec2_securitygroup_default_restrict_traffic",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22",
"ec2_securitygroup_allow_ingress_from_internet_to_all_ports"
"ec2_securitygroup_allow_ingress_from_internet_to_any_port"
]
},
{
@@ -297,7 +297,7 @@
"vpc_flow_logs_enabled",
"ec2_networkacl_allow_ingress_any_port",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22",
"ec2_securitygroup_allow_ingress_from_internet_to_all_ports"
"ec2_securitygroup_allow_ingress_from_internet_to_any_port"
]
},
{

File diff suppressed because it is too large Load Diff

View File

@@ -106,7 +106,7 @@
"ec2_networkacl_allow_ingress_any_port",
"ec2_networkacl_allow_ingress_tcp_port_22",
"ec2_networkacl_allow_ingress_tcp_port_3389",
"ec2_securitygroup_allow_ingress_from_internet_to_all_ports",
"ec2_securitygroup_allow_ingress_from_internet_to_any_port",
"ec2_securitygroup_allow_ingress_from_internet_to_port_mongodb_27017_27018",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_ftp_port_20_21",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22",
@@ -1022,7 +1022,7 @@
"ec2_networkacl_allow_ingress_any_port",
"ec2_networkacl_allow_ingress_tcp_port_22",
"ec2_networkacl_allow_ingress_tcp_port_3389",
"ec2_securitygroup_allow_ingress_from_internet_to_all_ports",
"ec2_securitygroup_allow_ingress_from_internet_to_any_port",
"ec2_securitygroup_allow_ingress_from_internet_to_port_mongodb_27017_27018",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_ftp_port_20_21",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22",
@@ -1468,7 +1468,7 @@
"ec2_networkacl_allow_ingress_any_port",
"ec2_networkacl_allow_ingress_tcp_port_22",
"ec2_networkacl_allow_ingress_tcp_port_3389",
"ec2_securitygroup_allow_ingress_from_internet_to_all_ports",
"ec2_securitygroup_allow_ingress_from_internet_to_any_port",
"ec2_securitygroup_allow_ingress_from_internet_to_port_mongodb_27017_27018",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_ftp_port_20_21",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22",
@@ -1648,7 +1648,7 @@
"ec2_networkacl_allow_ingress_any_port",
"ec2_networkacl_allow_ingress_tcp_port_22",
"ec2_networkacl_allow_ingress_tcp_port_3389",
"ec2_securitygroup_allow_ingress_from_internet_to_all_ports",
"ec2_securitygroup_allow_ingress_from_internet_to_any_port",
"ec2_securitygroup_allow_ingress_from_internet_to_port_mongodb_27017_27018",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_ftp_port_20_21",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22",
@@ -1900,7 +1900,7 @@
"ec2_networkacl_allow_ingress_any_port",
"ec2_networkacl_allow_ingress_tcp_port_22",
"ec2_networkacl_allow_ingress_tcp_port_3389",
"ec2_securitygroup_allow_ingress_from_internet_to_all_ports",
"ec2_securitygroup_allow_ingress_from_internet_to_any_port",
"ec2_securitygroup_allow_ingress_from_internet_to_port_mongodb_27017_27018",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_ftp_port_20_21",
"ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22",

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -11,7 +11,7 @@ from prowler.lib.logger import logger
timestamp = datetime.today()
timestamp_utc = datetime.now(timezone.utc).replace(tzinfo=timezone.utc)
prowler_version = "3.16.11"
prowler_version = "3.15.2"
html_logo_url = "https://github.com/prowler-cloud/prowler/"
html_logo_img = "https://user-images.githubusercontent.com/3985464/113734260-7ba06900-96fb-11eb-82bc-d4f68a1e2710.png"
square_logo_img = "https://user-images.githubusercontent.com/38561120/235905862-9ece5bd7-9aa3-4e48-807a-3a9035eb8bfb.png"

View File

@@ -52,27 +52,18 @@ aws:
# aws.awslambda_function_using_supported_runtimes
obsolete_lambda_runtimes:
[
"java8",
"go1.x",
"provided",
"python3.6",
"python2.7",
"python3.7",
"nodejs4.3",
"nodejs4.3-edge",
"nodejs6.10",
"nodejs",
"nodejs8.10",
"nodejs10.x",
"nodejs12.x",
"nodejs14.x",
"dotnet5.0",
"dotnetcore1.0",
"dotnetcore2.0",
"dotnetcore2.1",
"dotnetcore3.1",
"ruby2.5",
"ruby2.7",
]
# AWS Organizations
@@ -96,15 +87,6 @@ aws:
# trustedadvisor_premium_support_plan_subscribed
verify_premium_support_plans: True
# AWS RDS
# aws.rds_instance_backup_enabled
# Whether to check RDS instance replicas or not
check_rds_instance_replicas: False
# AWS ACM Configuration
# aws.acm_certificates_expiration_check
days_to_expire_threshold: 7
# Azure Configuration
azure:
# Azure Network Configuration

View File

@@ -34,7 +34,6 @@ def update_checks_metadata_with_compliance(
# Save it into the check's metadata
bulk_checks_metadata[check].Compliance = check_compliance
check_compliance = []
# Add requirements of Manual Controls
for framework in bulk_compliance_frameworks.values():
for requirement in framework.Requirements:
@@ -71,6 +70,7 @@ def update_checks_metadata_with_compliance(
"Recommendation": {"Text": "", "Url": ""},
},
"Categories": [],
"Tags": {},
"DependsOn": [],
"RelatedTo": [],
"Notes": "",

View File

@@ -46,8 +46,6 @@ class ENS_Requirement_Attribute(BaseModel):
Tipo: ENS_Requirement_Attribute_Tipos
Nivel: ENS_Requirement_Attribute_Nivel
Dimensiones: list[ENS_Requirement_Attribute_Dimensiones]
ModoEjecucion: str
Dependencias: list[str]
# Generic Compliance Requirement Attribute
@@ -89,7 +87,6 @@ class CIS_Requirement_Attribute(BaseModel):
RemediationProcedure: str
AuditProcedure: str
AdditionalInformation: str
DefaultValue: Optional[str]
References: str

View File

@@ -11,7 +11,6 @@ from prowler.lib.outputs.models import (
Check_Output_CSV_AWS_CIS,
Check_Output_CSV_AWS_ISO27001_2013,
Check_Output_CSV_AWS_Well_Architected,
Check_Output_CSV_AZURE_CIS,
Check_Output_CSV_ENS_RD2022,
Check_Output_CSV_GCP_CIS,
Check_Output_CSV_Generic_Compliance,
@@ -36,7 +35,6 @@ def add_manual_controls(output_options, audit_info, file_descriptors):
manual_finding.region = ""
manual_finding.location = ""
manual_finding.project_id = ""
manual_finding.subscription = ""
fill_compliance(
output_options, manual_finding, audit_info, file_descriptors
)
@@ -84,10 +82,6 @@ def fill_compliance(output_options, finding, audit_info, file_descriptors):
Requirements_Attributes_Dimensiones=",".join(
attribute.Dimensiones
),
Requirements_Attributes_ModoEjecucion=attribute.ModoEjecucion,
Requirements_Attributes_Dependencias=",".join(
attribute.Dependencias
),
Status=finding.status,
StatusExtended=finding.status_extended,
ResourceId=finding.resource_id,
@@ -167,36 +161,7 @@ def fill_compliance(output_options, finding, audit_info, file_descriptors):
csv_header = generate_csv_fields(
Check_Output_CSV_GCP_CIS
)
elif compliance.Provider == "Azure":
compliance_row = Check_Output_CSV_AZURE_CIS(
Provider=finding.check_metadata.Provider,
Description=compliance.Description,
Subscription=finding.subscription,
AssessmentDate=outputs_unix_timestamp(
output_options.unix_timestamp, timestamp
),
Requirements_Id=requirement_id,
Requirements_Description=requirement_description,
Requirements_Attributes_Section=attribute.Section,
Requirements_Attributes_Profile=attribute.Profile,
Requirements_Attributes_AssessmentStatus=attribute.AssessmentStatus,
Requirements_Attributes_Description=attribute.Description,
Requirements_Attributes_RationaleStatement=attribute.RationaleStatement,
Requirements_Attributes_ImpactStatement=attribute.ImpactStatement,
Requirements_Attributes_RemediationProcedure=attribute.RemediationProcedure,
Requirements_Attributes_AuditProcedure=attribute.AuditProcedure,
Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation,
Requirements_Attributes_DefaultValue=attribute.DefaultValue,
Requirements_Attributes_References=attribute.References,
Status=finding.status,
StatusExtended=finding.status_extended,
ResourceId=finding.resource_id,
ResourceName=finding.resource_name,
CheckId=finding.check_metadata.CheckID,
)
csv_header = generate_csv_fields(
Check_Output_CSV_AZURE_CIS
)
elif (
"AWS-Well-Architected-Framework" in compliance.Framework
and compliance.Provider == "AWS"
@@ -304,19 +269,11 @@ def fill_compliance(output_options, finding, audit_info, file_descriptors):
attributes_categories = ""
attributes_values = ""
attributes_comments = ""
attributes_aws_services = ", ".join(
attribute.AWSService for attribute in requirement.Attributes
)
attributes_categories = ", ".join(
attribute.Category for attribute in requirement.Attributes
)
attributes_values = ", ".join(
attribute.Value for attribute in requirement.Attributes
)
attributes_comments = ", ".join(
attribute.Comment for attribute in requirement.Attributes
)
for attribute in requirement.Attributes:
attributes_aws_services += attribute.AWSService + "\n"
attributes_categories += attribute.Category + "\n"
attributes_values += attribute.Value + "\n"
attributes_comments += attribute.Comment + "\n"
compliance_row = Check_Output_MITRE_ATTACK(
Provider=finding.check_metadata.Provider,
Description=compliance.Description,

View File

@@ -15,7 +15,6 @@ from prowler.lib.outputs.models import (
Check_Output_CSV_AWS_CIS,
Check_Output_CSV_AWS_ISO27001_2013,
Check_Output_CSV_AWS_Well_Architected,
Check_Output_CSV_AZURE_CIS,
Check_Output_CSV_ENS_RD2022,
Check_Output_CSV_GCP_CIS,
Check_Output_CSV_Generic_Compliance,
@@ -24,7 +23,6 @@ from prowler.lib.outputs.models import (
)
from prowler.lib.utils.utils import file_exists, open_file
from prowler.providers.aws.lib.audit_info.models import AWS_Audit_Info
from prowler.providers.azure.lib.audit_info.models import Azure_Audit_Info
from prowler.providers.common.outputs import get_provider_output_model
from prowler.providers.gcp.lib.audit_info.models import GCP_Audit_Info
@@ -115,16 +113,7 @@ def fill_file_descriptors(output_modes, output_directory, output_filename, audit
filename, output_mode, audit_info, Check_Output_CSV_GCP_CIS
)
file_descriptors.update({output_mode: file_descriptor})
elif isinstance(audit_info, Azure_Audit_Info):
filename = f"{output_directory}/{output_filename}_{output_mode}{csv_file_suffix}"
if "cis_" in output_mode:
file_descriptor = initialize_file_descriptor(
filename,
output_mode,
audit_info,
Check_Output_CSV_AZURE_CIS,
)
file_descriptors.update({output_mode: file_descriptor})
elif isinstance(audit_info, AWS_Audit_Info):
if output_mode == "json-asff":
filename = f"{output_directory}/{output_filename}{json_asff_file_suffix}"

View File

@@ -1,4 +1,3 @@
import html
import importlib
import sys
from os import path
@@ -31,9 +30,9 @@ def add_html_header(file_descriptor, audit_info):
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<!-- Required meta tags -->
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<style>
.read-more {
color: #00f;
@@ -49,7 +48,7 @@ def add_html_header(file_descriptor, audit_info):
</style>
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous" />
integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
<!-- https://datatables.net/download/index with jQuery, DataTables, Buttons, SearchPanes, and Select //-->
<link rel="stylesheet" type="text/css"
href="https://cdn.datatables.net/v/dt/jqc-1.12.4/dt-1.10.25/b-1.7.1/sp-1.4.0/sl-1.3.3/datatables.min.css" />
@@ -79,13 +78,13 @@ def add_html_header(file_descriptor, audit_info):
<div class="container-fluid">
<div class="row mt-3">
<div class="col-md-4">
<a href=\""""
<a href="""
+ html_logo_url
+ """\"><img class="float-left card-img-left mt-4 mr-4 ml-4"
src=\""""
+ """><img class="float-left card-img-left mt-4 mr-4 ml-4"
src="""
+ html_logo_img
+ """\"
alt="prowler-logo" /></a>
+ """
alt="prowler-logo"></a>
<div class="card">
<div class="card-header">
Report Information
@@ -183,13 +182,13 @@ def fill_html(file_descriptor, finding, output_options):
<td>{finding.check_metadata.Severity}</td>
<td>{finding.check_metadata.ServiceName}</td>
<td>{finding.location.lower() if isinstance(finding, Check_Report_GCP) else finding.region if isinstance(finding, Check_Report_AWS) else ""}</td>
<td>{finding.check_metadata.CheckID.replace("_", "<wbr />_")}</td>
<td>{finding.check_metadata.CheckID.replace("_", "<wbr>_")}</td>
<td>{finding.check_metadata.CheckTitle}</td>
<td>{finding.resource_id.replace("<", "&lt;").replace(">", "&gt;").replace("_", "<wbr />_")}</td>
<td>{finding.resource_id.replace("<", "&lt;").replace(">", "&gt;").replace("_", "<wbr>_")}</td>
<td>{parse_html_string(unroll_tags(finding.resource_tags))}</td>
<td>{finding.status_extended.replace("<", "&lt;").replace(">", "&gt;").replace("_", "<wbr />_")}</td>
<td><p class="show-read-more">{html.escape(finding.check_metadata.Risk)}</p></td>
<td><p class="show-read-more">{html.escape(finding.check_metadata.Remediation.Recommendation.Text)}</p> <a class="read-more" href="{finding.check_metadata.Remediation.Recommendation.Url}"><i class="fas fa-external-link-alt"></i></a></td>
<td>{finding.status_extended.replace("<", "&lt;").replace(">", "&gt;").replace("_", "<wbr>_")}</td>
<td><p class="show-read-more">{finding.check_metadata.Risk}</p></td>
<td><p class="show-read-more">{finding.check_metadata.Remediation.Recommendation.Text}</p> <a class="read-more" href="{finding.check_metadata.Remediation.Recommendation.Url}"><i class="fas fa-external-link-alt"></i></a></td>
<td><p class="show-read-more">{parse_html_string(unroll_dict(get_check_compliance(finding, finding.check_metadata.Provider, output_options)))}</p></td>
</tr>
"""
@@ -248,6 +247,8 @@ def add_html_footer(output_filename, output_directory):
</table>
</div>
</div>
</div>
</div>
<!-- Table search and paginator -->
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->

View File

@@ -100,17 +100,7 @@ def fill_json_asff(finding_output, audit_info, finding, output_options):
if not finding.check_metadata.Remediation.Recommendation.Url:
finding.check_metadata.Remediation.Recommendation.Url = "https://docs.aws.amazon.com/securityhub/latest/userguide/what-is-securityhub.html"
finding_output.Remediation = {
"Recommendation": {
"Text": (
(
finding.check_metadata.Remediation.Recommendation.Text[:509]
+ "..."
)
if len(finding.check_metadata.Remediation.Recommendation.Text) > 512
else finding.check_metadata.Remediation.Recommendation.Text
),
"Url": finding.check_metadata.Remediation.Recommendation.Url,
}
"Recommendation": finding.check_metadata.Remediation.Recommendation
}
return finding_output

View File

@@ -24,8 +24,7 @@ def get_check_compliance(finding, provider, output_options):
compliance_fw = compliance.Framework
if compliance.Version:
compliance_fw = f"{compliance_fw}-{compliance.Version}"
# compliance.Provider == "Azure" or "GCP" or "AWS"
if compliance.Provider.upper() == provider.upper():
if compliance.Provider == provider.upper():
if compliance_fw not in check_compliance:
check_compliance[compliance_fw] = []
for requirement in compliance.Requirements:
@@ -537,8 +536,6 @@ class Check_Output_CSV_ENS_RD2022(BaseModel):
Requirements_Attributes_Nivel: str
Requirements_Attributes_Tipo: str
Requirements_Attributes_Dimensiones: str
Requirements_Attributes_ModoEjecucion: str
Requirements_Attributes_Dependencias: Optional[str]
Status: str
StatusExtended: str
ResourceId: str
@@ -602,35 +599,6 @@ class Check_Output_CSV_GCP_CIS(BaseModel):
CheckId: str
class Check_Output_CSV_AZURE_CIS(BaseModel):
"""
Check_Output_CSV_CIS generates a finding's output in CSV CIS format.
"""
Provider: str
Description: str
Subscription: str
AssessmentDate: str
Requirements_Id: str
Requirements_Description: str
Requirements_Attributes_Section: str
Requirements_Attributes_Profile: str
Requirements_Attributes_AssessmentStatus: str
Requirements_Attributes_Description: str
Requirements_Attributes_RationaleStatement: str
Requirements_Attributes_ImpactStatement: str
Requirements_Attributes_RemediationProcedure: str
Requirements_Attributes_AuditProcedure: str
Requirements_Attributes_AdditionalInformation: str
Requirements_Attributes_DefaultValue: str
Requirements_Attributes_References: str
Status: str
StatusExtended: str
ResourceId: str
ResourceName: str
CheckId: str
class Check_Output_CSV_Generic_Compliance(BaseModel):
"""
Check_Output_CSV_Generic_Compliance generates a finding's output in CSV Generic Compliance format.

View File

@@ -67,15 +67,15 @@ def report(check_findings, output_options, audit_info):
compliance in output_options.output_modes
for compliance in available_compliance_frameworks
):
add_manual_controls(
fill_compliance(
output_options,
finding,
audit_info,
file_descriptors,
)
fill_compliance(
add_manual_controls(
output_options,
finding,
audit_info,
file_descriptors,
)

View File

@@ -45,7 +45,6 @@ def display_summary_table(
"Service": "",
"Provider": "",
"Total": 0,
"Pass": 0,
"Critical": 0,
"High": 0,
"Medium": 0,
@@ -79,7 +78,6 @@ def display_summary_table(
current["Total"] += 1
if finding.status == "PASS":
pass_count += 1
current["Pass"] += 1
elif finding.status == "FAIL":
fail_count += 1
if finding.check_metadata.Severity == "critical":
@@ -159,8 +157,7 @@ def add_service_to_table(findings_table, current):
)
current["Status"] = f"{Fore.RED}FAIL ({total_fails}){Style.RESET_ALL}"
else:
current["Status"] = f"{Fore.GREEN}PASS ({current['Pass']}){Style.RESET_ALL}"
current["Status"] = f"{Fore.GREEN}PASS ({current['Total']}){Style.RESET_ALL}"
findings_table["Provider"].append(current["Provider"])
findings_table["Service"].append(current["Service"])
findings_table["Status"].append(current["Status"])

View File

@@ -1,13 +1,10 @@
import os
import pathlib
import sys
from datetime import datetime
from boto3 import client, session
from botocore.credentials import RefreshableCredentials
from botocore.session import get_session
from pytz import utc
from tzlocal import get_localzone
from prowler.config.config import aws_services_json_file
from prowler.lib.check.check import list_modules, recover_checks_from_service
@@ -17,7 +14,6 @@ from prowler.providers.aws.config import (
AWS_STS_GLOBAL_ENDPOINT_REGION,
ROLE_SESSION_NAME,
)
from prowler.providers.aws.lib.audit_info.audit_info import current_audit_info
from prowler.providers.aws.lib.audit_info.models import AWS_Assume_Role, AWS_Audit_Info
from prowler.providers.aws.lib.credentials.credentials import create_sts_session
@@ -102,31 +98,18 @@ class AWS_Provider:
# https://github.com/boto/botocore/blob/098cc255f81a25b852e1ecdeb7adebd94c7b1b73/botocore/credentials.py#L570
def refresh_credentials(self):
logger.info("Refreshing assumed credentials...")
current_credentials = current_audit_info.credentials
refreshed_credentials = {
"access_key": current_credentials.aws_access_key_id,
"secret_key": current_credentials.aws_secret_access_key,
"token": current_credentials.aws_session_token,
"expiry_time": (
current_credentials.expiration.isoformat()
if hasattr(current_credentials, "expiration")
else current_credentials.expiry_time.isoformat()
),
}
if datetime.fromisoformat(refreshed_credentials["expiry_time"]) <= datetime.now(
get_localzone()
):
response = assume_role(self.aws_session, self.role_info)
refreshed_credentials = dict(
# Keys of the dict has to be the same as those that are being searched in the parent class
# https://github.com/boto/botocore/blob/098cc255f81a25b852e1ecdeb7adebd94c7b1b73/botocore/credentials.py#L609
access_key=response["Credentials"]["AccessKeyId"],
secret_key=response["Credentials"]["SecretAccessKey"],
token=response["Credentials"]["SessionToken"],
expiry_time=response["Credentials"]["Expiration"].isoformat(),
)
logger.info("Refreshed Credentials:")
logger.info(refreshed_credentials)
response = assume_role(self.aws_session, self.role_info)
refreshed_credentials = dict(
# Keys of the dict has to be the same as those that are being searched in the parent class
# https://github.com/boto/botocore/blob/098cc255f81a25b852e1ecdeb7adebd94c7b1b73/botocore/credentials.py#L609
access_key=response["Credentials"]["AccessKeyId"],
secret_key=response["Credentials"]["SecretAccessKey"],
token=response["Credentials"]["SessionToken"],
expiry_time=response["Credentials"]["Expiration"].isoformat(),
)
logger.info("Refreshed Credentials:")
logger.info(refreshed_credentials)
return refreshed_credentials
@@ -163,17 +146,6 @@ def assume_role(
sts_client = create_sts_session(session, sts_endpoint_region)
assumed_credentials = sts_client.assume_role(**assume_role_arguments)
# Convert the UTC datetime object to your local timezone
credentials_expiration_local_time = (
assumed_credentials["Credentials"]["Expiration"]
.replace(tzinfo=utc)
.astimezone(get_localzone())
)
assumed_credentials["Credentials"][
"Expiration"
] = credentials_expiration_local_time
except Exception as error:
logger.critical(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}"

View File

@@ -157,10 +157,7 @@
"us-west-1",
"us-west-2"
],
"aws-cn": [
"cn-north-1",
"cn-northwest-1"
],
"aws-cn": [],
"aws-us-gov": [
"us-gov-east-1",
"us-gov-west-1"
@@ -237,7 +234,6 @@
"ap-east-1",
"ap-northeast-1",
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
@@ -264,7 +260,6 @@
"aws": [
"ap-northeast-1",
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
@@ -291,7 +286,6 @@
"aws": [
"ap-northeast-1",
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
@@ -398,22 +392,15 @@
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-south-2",
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
"eu-south-1",
"eu-south-2",
"eu-west-1",
"eu-west-2",
"eu-west-3",
"il-central-1",
"me-central-1",
"me-south-1",
"sa-east-1",
@@ -761,18 +748,6 @@
"aws-us-gov": []
}
},
"apptest": {
"regions": {
"aws": [
"ap-southeast-2",
"eu-central-1",
"sa-east-1",
"us-east-1"
],
"aws-cn": [],
"aws-us-gov": []
}
},
"aps": {
"regions": {
"aws": [
@@ -1187,6 +1162,47 @@
]
}
},
"backupstorage": {
"regions": {
"aws": [
"af-south-1",
"ap-east-1",
"ap-northeast-1",
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-south-2",
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
"eu-south-1",
"eu-south-2",
"eu-west-1",
"eu-west-2",
"eu-west-3",
"me-central-1",
"me-south-1",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-1",
"us-west-2"
],
"aws-cn": [
"cn-north-1",
"cn-northwest-1"
],
"aws-us-gov": [
"us-gov-east-1",
"us-gov-west-1"
]
}
},
"batch": {
"regions": {
"aws": [
@@ -1230,28 +1246,12 @@
]
}
},
"bcm-data-exports": {
"regions": {
"aws": [
"us-east-1"
],
"aws-cn": [],
"aws-us-gov": []
}
},
"bedrock": {
"regions": {
"aws": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
"ca-central-1",
"eu-central-1",
"eu-west-1",
"eu-west-2",
"eu-west-3",
"sa-east-1",
"us-east-1",
"us-west-2"
],
@@ -1273,8 +1273,6 @@
"braket": {
"regions": {
"aws": [
"eu-north-1",
"eu-west-2",
"us-east-1",
"us-west-1",
"us-west-2"
@@ -1417,14 +1415,8 @@
"chime-sdk-media-pipelines": {
"regions": {
"aws": [
"ap-northeast-1",
"ap-northeast-2",
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
"ca-central-1",
"eu-central-1",
"eu-west-2",
"us-east-1",
"us-west-2"
],
@@ -1435,7 +1427,6 @@
"chime-sdk-meetings": {
"regions": {
"aws": [
"af-south-1",
"ap-northeast-1",
"ap-northeast-2",
"ap-south-1",
@@ -2268,17 +2259,14 @@
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-south-2",
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
"eu-south-1",
"eu-south-2",
"eu-west-1",
"eu-west-2",
"eu-west-3",
@@ -2305,17 +2293,14 @@
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-south-2",
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
"eu-south-1",
"eu-south-2",
"eu-west-1",
"eu-west-2",
"eu-west-3",
@@ -2344,17 +2329,14 @@
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-south-2",
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
"eu-south-1",
"eu-south-2",
"eu-west-1",
"eu-west-2",
"eu-west-3",
@@ -2571,8 +2553,6 @@
"connectcases": {
"regions": {
"aws": [
"ap-northeast-1",
"ap-northeast-2",
"ap-southeast-1",
"ap-southeast-2",
"ca-central-1",
@@ -2605,46 +2585,6 @@
]
}
},
"controlcatalog": {
"regions": {
"aws": [
"af-south-1",
"ap-east-1",
"ap-northeast-1",
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-south-2",
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
"eu-south-1",
"eu-south-2",
"eu-west-1",
"eu-west-2",
"eu-west-3",
"il-central-1",
"me-central-1",
"me-south-1",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-1",
"us-west-2"
],
"aws-cn": [],
"aws-us-gov": [
"us-gov-east-1",
"us-gov-west-1"
]
}
},
"controltower": {
"regions": {
"aws": [
@@ -2660,7 +2600,6 @@
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
@@ -2886,22 +2825,6 @@
"aws-us-gov": []
}
},
"deadline-cloud": {
"regions": {
"aws": [
"ap-northeast-1",
"ap-southeast-1",
"ap-southeast-2",
"eu-central-1",
"eu-west-1",
"us-east-1",
"us-east-2",
"us-west-2"
],
"aws-cn": [],
"aws-us-gov": []
}
},
"deepcomposer": {
"regions": {
"aws": [
@@ -3145,7 +3068,6 @@
"eu-west-1",
"eu-west-2",
"eu-west-3",
"me-central-1",
"sa-east-1",
"us-east-1",
"us-east-2",
@@ -4262,7 +4184,6 @@
"eu-central-1",
"eu-north-1",
"eu-south-1",
"eu-south-2",
"eu-west-1",
"eu-west-2",
"eu-west-3",
@@ -4295,7 +4216,6 @@
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
@@ -4417,7 +4337,6 @@
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
@@ -4460,7 +4379,6 @@
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
@@ -4503,7 +4421,6 @@
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
@@ -4545,7 +4462,6 @@
"ap-southeast-2",
"ap-southeast-3",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
@@ -4588,7 +4504,6 @@
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
@@ -4699,7 +4614,6 @@
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
@@ -4946,7 +4860,6 @@
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-south-2",
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-3",
@@ -4956,7 +4869,6 @@
"eu-central-2",
"eu-north-1",
"eu-south-1",
"eu-south-2",
"eu-west-1",
"eu-west-2",
"eu-west-3",
@@ -4994,7 +4906,6 @@
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
@@ -5276,6 +5187,16 @@
]
}
},
"iot-roborunner": {
"regions": {
"aws": [
"eu-central-1",
"us-east-1"
],
"aws-cn": [],
"aws-us-gov": []
}
},
"iot1click-devices": {
"regions": {
"aws": [
@@ -5626,7 +5547,6 @@
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
@@ -5824,10 +5744,7 @@
"aws-cn": [
"cn-north-1"
],
"aws-us-gov": [
"us-gov-east-1",
"us-gov-west-1"
]
"aws-us-gov": []
}
},
"kms": {
@@ -6075,9 +5992,7 @@
"us-west-2"
],
"aws-cn": [],
"aws-us-gov": [
"us-gov-west-1"
]
"aws-us-gov": []
}
},
"lexv2-models": {
@@ -6096,9 +6011,7 @@
"us-west-2"
],
"aws-cn": [],
"aws-us-gov": [
"us-gov-west-1"
]
"aws-us-gov": []
}
},
"license-manager": {
@@ -6116,7 +6029,6 @@
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
@@ -6202,7 +6114,6 @@
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
@@ -6221,10 +6132,7 @@
"us-west-2"
],
"aws-cn": [],
"aws-us-gov": [
"us-gov-east-1",
"us-gov-west-1"
]
"aws-us-gov": []
}
},
"lightsail": {
@@ -6576,9 +6484,7 @@
"aws": [
"us-east-1"
],
"aws-cn": [
"cn-northwest-1"
],
"aws-cn": [],
"aws-us-gov": []
}
},
@@ -6670,7 +6576,6 @@
"eu-west-1",
"eu-west-2",
"eu-west-3",
"me-central-1",
"sa-east-1",
"us-east-1",
"us-east-2",
@@ -6692,7 +6597,6 @@
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-south-2",
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-4",
@@ -6702,7 +6606,6 @@
"eu-west-1",
"eu-west-2",
"eu-west-3",
"me-central-1",
"sa-east-1",
"us-east-1",
"us-east-2",
@@ -6832,7 +6735,6 @@
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-south-2",
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-4",
@@ -6841,7 +6743,6 @@
"eu-north-1",
"eu-west-1",
"eu-west-3",
"me-central-1",
"sa-east-1",
"us-east-1",
"us-east-2",
@@ -7076,7 +6977,6 @@
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
@@ -7116,8 +7016,6 @@
"mwaa": {
"regions": {
"aws": [
"af-south-1",
"ap-east-1",
"ap-northeast-1",
"ap-northeast-2",
"ap-south-1",
@@ -7126,15 +7024,12 @@
"ca-central-1",
"eu-central-1",
"eu-north-1",
"eu-south-1",
"eu-west-1",
"eu-west-2",
"eu-west-3",
"me-south-1",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-1",
"us-west-2"
],
"aws-cn": [
@@ -7151,7 +7046,6 @@
"ap-east-1",
"ap-northeast-1",
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
@@ -7249,7 +7143,6 @@
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-3",
"ca-central-1",
"eu-central-1",
"eu-north-1",
@@ -7257,7 +7150,6 @@
"eu-west-1",
"eu-west-2",
"eu-west-3",
"il-central-1",
"me-south-1",
"sa-east-1",
"us-east-1",
@@ -7332,7 +7224,6 @@
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
@@ -7456,15 +7347,10 @@
"regions": {
"aws": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
"ca-central-1",
"eu-central-1",
"eu-west-1",
"eu-west-2",
"eu-west-3",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
@@ -8015,16 +7901,6 @@
"aws-us-gov": []
}
},
"qbusiness": {
"regions": {
"aws": [
"us-east-1",
"us-west-2"
],
"aws-cn": [],
"aws-us-gov": []
}
},
"qldb": {
"regions": {
"aws": [
@@ -8066,18 +7942,14 @@
"quicksight": {
"regions": {
"aws": [
"af-south-1",
"ap-northeast-1",
"ap-northeast-2",
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-3",
"ca-central-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
"eu-south-1",
"eu-west-1",
"eu-west-2",
"eu-west-3",
@@ -8345,19 +8217,14 @@
"aws": [
"ap-south-1",
"ca-central-1",
"eu-central-2",
"eu-south-2",
"eu-west-3",
"me-central-1",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-1",
"us-west-2"
],
"aws-cn": [
"cn-north-1",
"cn-northwest-1"
"cn-north-1"
],
"aws-us-gov": []
}
@@ -8389,12 +8256,7 @@
"repostspace": {
"regions": {
"aws": [
"ap-southeast-1",
"ap-southeast-2",
"ca-central-1",
"eu-central-1",
"eu-west-1",
"us-east-1",
"us-west-2"
],
"aws-cn": [],
@@ -8435,29 +8297,19 @@
"resource-explorer-2": {
"regions": {
"aws": [
"af-south-1",
"ap-east-1",
"ap-northeast-1",
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-south-2",
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
"eu-south-1",
"eu-south-2",
"eu-west-1",
"eu-west-2",
"eu-west-3",
"il-central-1",
"me-central-1",
"me-south-1",
"sa-east-1",
"us-east-1",
@@ -8756,31 +8608,15 @@
"rum": {
"regions": {
"aws": [
"af-south-1",
"ap-northeast-1",
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-south-2",
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
"eu-south-1",
"eu-south-2",
"eu-west-1",
"eu-west-2",
"eu-west-3",
"me-central-1",
"me-south-1",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-1",
"us-west-2"
],
"aws-cn": [],
@@ -8833,30 +8669,18 @@
"s3control": {
"regions": {
"aws": [
"af-south-1",
"ap-east-1",
"ap-northeast-1",
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-south-2",
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
"eu-south-1",
"eu-south-2",
"eu-west-1",
"eu-west-2",
"eu-west-3",
"il-central-1",
"me-central-1",
"me-south-1",
"sa-east-1",
"us-east-1",
"us-east-2",
@@ -9317,10 +9141,7 @@
"us-west-2"
],
"aws-cn": [],
"aws-us-gov": [
"us-gov-east-1",
"us-gov-west-1"
]
"aws-us-gov": []
}
},
"serverlessrepo": {
@@ -9551,7 +9372,6 @@
],
"aws-cn": [],
"aws-us-gov": [
"us-gov-east-1",
"us-gov-west-1"
]
}
@@ -9571,7 +9391,6 @@
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
@@ -9717,7 +9536,6 @@
"eu-west-1",
"eu-west-2",
"eu-west-3",
"il-central-1",
"me-central-1",
"sa-east-1",
"us-east-1",
@@ -9758,6 +9576,21 @@
"aws-us-gov": []
}
},
"snowmobile": {
"regions": {
"aws": [
"us-east-1",
"us-east-2",
"us-west-1",
"us-west-2"
],
"aws-cn": [],
"aws-us-gov": [
"us-gov-east-1",
"us-gov-west-1"
]
}
},
"sns": {
"regions": {
"aws": [
@@ -9974,7 +9807,6 @@
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-south-2",
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-3",
@@ -9984,7 +9816,6 @@
"eu-central-2",
"eu-north-1",
"eu-south-1",
"eu-south-2",
"eu-west-1",
"eu-west-2",
"eu-west-3",
@@ -10065,7 +9896,6 @@
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
@@ -10275,15 +10105,6 @@
]
}
},
"taxsettings": {
"regions": {
"aws": [
"us-east-1"
],
"aws-cn": [],
"aws-us-gov": []
}
},
"textract": {
"regions": {
"aws": [
@@ -10340,24 +10161,6 @@
]
}
},
"timestream-influxdb": {
"regions": {
"aws": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"us-east-1",
"us-east-2",
"us-west-2"
],
"aws-cn": [],
"aws-us-gov": []
}
},
"timestream-write": {
"regions": {
"aws": [
@@ -10441,7 +10244,6 @@
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
@@ -10625,7 +10427,6 @@
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
@@ -10634,7 +10435,6 @@
"eu-west-1",
"eu-west-2",
"eu-west-3",
"il-central-1",
"me-central-1",
"me-south-1",
"sa-east-1",
@@ -10644,10 +10444,7 @@
"us-west-2"
],
"aws-cn": [],
"aws-us-gov": [
"us-gov-east-1",
"us-gov-west-1"
]
"aws-us-gov": []
}
},
"vmwarecloudonaws": {
@@ -10748,8 +10545,6 @@
"regions": {
"aws": [
"ap-northeast-1",
"ap-northeast-2",
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
"ca-central-1",
@@ -10757,11 +10552,8 @@
"eu-north-1",
"eu-west-1",
"eu-west-2",
"eu-west-3",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-1",
"us-west-2"
],
"aws-cn": [],
@@ -10823,7 +10615,6 @@
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
@@ -10908,7 +10699,6 @@
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
@@ -11151,4 +10941,4 @@
}
}
}
}
}

View File

@@ -118,8 +118,8 @@ def parse_allowlist_file(audit_info, allowlist_file):
def allowlist_findings(
allowlist: dict,
audited_account: str,
check_findings: list[Any],
) -> list[Any]:
check_findings: [Any],
):
# Check if finding is allowlisted
for finding in check_findings:
if is_allowlisted(
@@ -141,21 +141,7 @@ def is_allowlisted(
finding_region: str,
finding_resource: str,
finding_tags,
) -> bool:
"""
Check if the provided finding is allowlisted for the audited account, check, region, resource and tags.
Args:
mutelist (dict): Dictionary containing information about allowlisted checks for different accounts.
audited_account (str): The account being audited.
check (str): The check to be evaluated for allowlisting.
finding_region (str): The region where the finding occurred.
finding_resource (str): The resource related to the finding.
finding_tags: The tags associated with the finding.
Returns:
bool: True if the finding is allowlisted for the audited account, check, region, resource and tags., otherwise False.
"""
):
try:
# By default is not allowlisted
is_finding_allowlisted = False
@@ -177,10 +163,10 @@ def is_allowlisted(
return is_finding_allowlisted
except Exception as error:
logger.error(
logger.critical(
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
)
return False
sys.exit(1)
def is_allowlisted_in_check(
@@ -190,21 +176,7 @@ def is_allowlisted_in_check(
finding_region,
finding_resource,
finding_tags,
) -> bool:
"""
Check if the provided check is allowlisted.
Args:
allowlisted_checks (dict): Dictionary containing information about allowlisted checks.
audited_account (str): The account to be audited.
check (str): The check to be evaluated for allowlisting.
finding_region (str): The region where the finding occurred.
finding_resource (str): The resource related to the finding.
finding_tags (str): The tags associated with the finding.
Returns:
bool: True if the check is allowlisted, otherwise False.
"""
):
try:
# Default value is not allowlisted
is_check_allowlisted = False
@@ -213,23 +185,14 @@ def is_allowlisted_in_check(
# map lambda to awslambda
allowlisted_check = re.sub("^lambda", "awslambda", allowlisted_check)
check_match = (
"*" == allowlisted_check
or check == allowlisted_check
or re.search(allowlisted_check, check)
)
# Check if the finding is excepted
exceptions = allowlisted_check_info.get("Exceptions")
if (
is_excepted(
exceptions,
audited_account,
finding_region,
finding_resource,
finding_tags,
)
and check_match
if is_excepted(
exceptions,
audited_account,
finding_region,
finding_resource,
finding_tags,
):
# Break loop and return default value since is excepted
break
@@ -242,7 +205,11 @@ def is_allowlisted_in_check(
allowlisted_tags = "*"
# If there is a *, it affects to all checks
if check_match:
if (
"*" == allowlisted_check
or check == allowlisted_check
or re.search(allowlisted_check, check)
):
allowlisted_in_check = True
allowlisted_in_region = is_allowlisted_in_region(
allowlisted_regions, finding_region
@@ -271,74 +238,44 @@ def is_allowlisted_in_check(
return is_check_allowlisted
except Exception as error:
logger.error(
logger.critical(
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
)
return False
sys.exit(1)
def is_allowlisted_in_region(
allowlisted_regions,
finding_region,
) -> bool:
"""
Check if the finding_region is present in the allowlisted_regions.
Args:
allowlisted_regions (list): List of regions in the allowlist.
finding_region (str): Region to check if it is allowlisted.
Returns:
bool: True if the finding_region is present in any of the allowlisted_regions, otherwise False.
"""
):
try:
return __is_item_matched__(allowlisted_regions, finding_region)
except Exception as error:
logger.error(
logger.critical(
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
)
return False
sys.exit(1)
def is_allowlisted_in_tags(allowlisted_tags, finding_tags) -> bool:
"""
Check if any of the allowlisted tags are present in the finding tags.
Args:
allowlisted_tags (list): List of allowlisted tags to be checked.
finding_tags (str): String containing tags to search for allowlisted tags.
Returns:
bool: True if any of the allowlisted tags are present in the finding tags, otherwise False.
"""
def is_allowlisted_in_tags(allowlisted_tags, finding_tags):
try:
return __is_item_matched__(allowlisted_tags, finding_tags)
except Exception as error:
logger.error(
logger.critical(
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
)
return False
sys.exit(1)
def is_allowlisted_in_resource(allowlisted_resources, finding_resource) -> bool:
"""
Check if any of the allowlisted_resources are present in the finding_resource.
Args:
allowlisted_resources (list): List of allowlisted resources to be checked.
finding_resource (str): Resource to search for allowlisted resources.
Returns:
bool: True if any of the allowlisted_resources are present in the finding_resource, otherwise False.
"""
def is_allowlisted_in_resource(allowlisted_resources, finding_resource):
try:
return __is_item_matched__(allowlisted_resources, finding_resource)
except Exception as error:
logger.error(
logger.critical(
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
)
return False
sys.exit(1)
def is_excepted(
@@ -347,20 +284,8 @@ def is_excepted(
finding_region,
finding_resource,
finding_tags,
) -> bool:
"""
Check if the provided account, region, resource, and tags are excepted based on the exceptions dictionary.
Args:
exceptions (dict): Dictionary containing exceptions for different attributes like Accounts, Regions, Resources, and Tags.
audited_account (str): The account to be audited.
finding_region (str): The region where the finding occurred.
finding_resource (str): The resource related to the finding.
finding_tags (str): The tags associated with the finding.
Returns:
bool: True if the account, region, resource, and tags are excepted based on the exceptions, otherwise False.
"""
):
"""is_excepted returns True if the account, region, resource and tags are excepted"""
try:
excepted = False
is_account_excepted = False
@@ -400,36 +325,26 @@ def is_excepted(
excepted = True
return excepted
except Exception as error:
logger.error(
logger.critical(
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
)
return False
sys.exit(1)
def __is_item_matched__(matched_items, finding_items):
"""
Check if any of the items in matched_items are present in finding_items.
Args:
matched_items (list): List of items to be matched.
finding_items (str): String to search for matched items.
Returns:
bool: True if any of the matched_items are present in finding_items, otherwise False.
"""
"""__is_item_matched__ return True if any of the matched_items are present in the finding_items, otherwise returns False."""
try:
is_item_matched = False
if matched_items and (finding_items or finding_items == ""):
for item in matched_items:
if item.startswith("*"):
item = ".*" + item[1:]
if item == "*":
item = ".*"
if re.search(item, finding_items):
is_item_matched = True
break
return is_item_matched
except Exception as error:
logger.error(
logger.critical(
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
)
# If something unexpected happens return not matched, thus False
return False
sys.exit(1)

View File

@@ -69,9 +69,6 @@ Caller Identity ARN: {Fore.YELLOW}[{audit_info.audited_identity_arn}]{Style.RESE
def create_sts_session(
session: session.Session, aws_region: str
) -> session.Session.client:
sts_endpoint_url = (
f"https://sts.{aws_region}.amazonaws.com"
if "cn-" not in aws_region
else f"https://sts.{aws_region}.amazonaws.com.cn"
return session.client(
"sts", aws_region, endpoint_url=f"https://sts.{aws_region}.amazonaws.com"
)
return session.client("sts", aws_region, endpoint_url=sts_endpoint_url)

View File

@@ -29,8 +29,6 @@ def is_condition_block_restrictive(
"aws:principalaccount",
"aws:resourceaccount",
"aws:sourcearn",
"aws:sourcevpc",
"aws:sourcevpce",
],
"StringLike": [
"aws:sourceaccount",
@@ -39,8 +37,6 @@ def is_condition_block_restrictive(
"aws:principalarn",
"aws:resourceaccount",
"aws:principalaccount",
"aws:sourcevpc",
"aws:sourcevpce",
],
"ArnLike": ["aws:sourcearn", "aws:principalarn"],
"ArnEquals": ["aws:sourcearn", "aws:principalarn"],

View File

@@ -86,7 +86,7 @@ def verify_security_hub_integration_enabled_per_region(
error_message = client_error.response["Error"]["Message"]
if (
error_code == "InvalidAccessException"
and f"Account {aws_account_number} is not subscribed to AWS Security Hub"
and f"Account {aws_account_number} is not subscribed to AWS Security Hub in region {region}"
in error_message
):
logger.warning(

View File

@@ -19,17 +19,6 @@ class AWSService:
- Also handles if the AWS Service is Global
"""
failed_checks = set()
@classmethod
def set_failed_check(cls, check_id=None, arn=None):
if check_id is not None and arn is not None:
cls.failed_checks.add((check_id.split(".")[-1], arn))
@classmethod
def is_failed_check(cls, check_id, arn):
return (check_id.split(".")[-1], arn) in cls.failed_checks
def __init__(self, service: str, audit_info: AWS_Audit_Info, global_service=False):
# Audit Information
self.audit_info = audit_info

View File

@@ -11,13 +11,13 @@
"Severity": "medium",
"ResourceType": "Other",
"Description": "Maintain current contact details.",
"Risk": "Ensure contact email and telephone details for AWS accounts are current and map to more than one individual in your organization. An AWS account supports a number of contact details, and AWS will use these to contact the account owner if activity judged to be in breach of Acceptable Use Policy. If an AWS account is observed to be behaving in a prohibited or suspicious manner, AWS will attempt to contact the account owner by email and phone using the contact details listed. If this is unsuccessful and the account behavior needs urgent mitigation, proactive measures may be taken, including throttling of traffic between the account exhibiting suspicious behavior and the AWS API endpoints and the Internet. This will result in impaired service to and from the account in question.",
"Risk": "Ensure contact email and telephone details for AWS accounts are current and map to more than one individual in your organization. An AWS account supports a number of contact details; and AWS will use these to contact the account owner if activity judged to be in breach of Acceptable Use Policy. If an AWS account is observed to be behaving in a prohibited or suspicious manner; AWS will attempt to contact the account owner by email and phone using the contact details listed. If this is unsuccessful and the account behavior needs urgent mitigation; proactive measures may be taken; including throttling of traffic between the account exhibiting suspicious behavior and the AWS API endpoints and the Internet. This will result in impaired service to and from the account in question.",
"RelatedUrl": "",
"Remediation": {
"Code": {
"CLI": "No command available.",
"NativeIaC": "",
"Other": "https://docs.prowler.com/checks/aws/iam-policies/iam_18-maintain-contact-details#aws-console",
"Other": "https://docs.bridgecrew.io/docs/iam_18-maintain-contact-details#aws-console",
"Terraform": ""
},
"Recommendation": {

View File

@@ -11,13 +11,13 @@
"Severity": "medium",
"ResourceType": "Other",
"Description": "Maintain different contact details to security, billing and operations.",
"Risk": "Ensure contact email and telephone details for AWS accounts are current and map to more than one individual in your organization. An AWS account supports a number of contact details, and AWS will use these to contact the account owner if activity judged to be in breach of Acceptable Use Policy. If an AWS account is observed to be behaving in a prohibited or suspicious manner, AWS will attempt to contact the account owner by email and phone using the contact details listed. If this is unsuccessful and the account behavior needs urgent mitigation, proactive measures may be taken, including throttling of traffic between the account exhibiting suspicious behavior and the AWS API endpoints and the Internet. This will result in impaired service to and from the account in question.",
"Risk": "Ensure contact email and telephone details for AWS accounts are current and map to more than one individual in your organization. An AWS account supports a number of contact details; and AWS will use these to contact the account owner if activity judged to be in breach of Acceptable Use Policy. If an AWS account is observed to be behaving in a prohibited or suspicious manner; AWS will attempt to contact the account owner by email and phone using the contact details listed. If this is unsuccessful and the account behavior needs urgent mitigation; proactive measures may be taken; including throttling of traffic between the account exhibiting suspicious behavior and the AWS API endpoints and the Internet. This will result in impaired service to and from the account in question.",
"RelatedUrl": "https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-update-contact.html",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "https://docs.prowler.com/checks/aws/iam-policies/iam_18-maintain-contact-details#aws-console",
"Other": "https://docs.bridgecrew.io/docs/iam_18-maintain-contact-details#aws-console",
"Terraform": ""
},
"Recommendation": {

View File

@@ -6,26 +6,22 @@ class account_maintain_different_contact_details_to_security_billing_and_operati
Check
):
def execute(self):
findings = []
if account_client.contact_base:
report = Check_Report_AWS(self.metadata())
report.region = account_client.region
report.resource_id = account_client.audited_account
report.resource_arn = account_client.audited_account_arn
report = Check_Report_AWS(self.metadata())
report.region = account_client.region
report.resource_id = account_client.audited_account
report.resource_arn = account_client.audited_account_arn
if (
len(account_client.contact_phone_numbers)
== account_client.number_of_contacts
and len(account_client.contact_names)
== account_client.number_of_contacts
# This is because the primary contact has no email field
and len(account_client.contact_emails)
== account_client.number_of_contacts - 1
):
report.status = "PASS"
report.status_extended = "SECURITY, BILLING and OPERATIONS contacts found and they are different between each other and between ROOT contact."
else:
report.status = "FAIL"
report.status_extended = "SECURITY, BILLING and OPERATIONS contacts not found or they are not different between each other and between ROOT contact."
findings.append(report)
return findings
if (
len(account_client.contact_phone_numbers)
== account_client.number_of_contacts
and len(account_client.contact_names) == account_client.number_of_contacts
# This is because the primary contact has no email field
and len(account_client.contact_emails)
== account_client.number_of_contacts - 1
):
report.status = "PASS"
report.status_extended = "SECURITY, BILLING and OPERATIONS contacts found and they are different between each other and between ROOT contact."
else:
report.status = "FAIL"
report.status_extended = "SECURITY, BILLING and OPERATIONS contacts not found or they are not different between each other and between ROOT contact."
return [report]

View File

@@ -17,7 +17,7 @@
"Code": {
"CLI": "No command available.",
"NativeIaC": "",
"Other": "https://docs.prowler.com/checks/aws/iam-policies/iam_19#aws-console",
"Other": "https://docs.bridgecrew.io/docs/iam_19#aws-console",
"Terraform": ""
},
"Recommendation": {

View File

@@ -17,7 +17,7 @@
"Code": {
"CLI": "No command available.",
"NativeIaC": "",
"Other": "https://docs.prowler.com/checks/aws/iam-policies/iam_15",
"Other": "https://docs.bridgecrew.io/docs/iam_15",
"Terraform": ""
},
"Recommendation": {

View File

@@ -18,29 +18,28 @@ class Account(AWSService):
self.contacts_security = self.__get_alternate_contact__("SECURITY")
self.contacts_operations = self.__get_alternate_contact__("OPERATIONS")
if self.contact_base:
# Set of contact phone numbers
self.contact_phone_numbers = {
self.contact_base.phone_number,
self.contacts_billing.phone_number,
self.contacts_security.phone_number,
self.contacts_operations.phone_number,
}
# Set of contact phone numbers
self.contact_phone_numbers = {
self.contact_base.phone_number,
self.contacts_billing.phone_number,
self.contacts_security.phone_number,
self.contacts_operations.phone_number,
}
# Set of contact names
self.contact_names = {
self.contact_base.name,
self.contacts_billing.name,
self.contacts_security.name,
self.contacts_operations.name,
}
# Set of contact names
self.contact_names = {
self.contact_base.name,
self.contacts_billing.name,
self.contacts_security.name,
self.contacts_operations.name,
}
# Set of contact emails
self.contact_emails = {
self.contacts_billing.email,
self.contacts_security.email,
self.contacts_operations.email,
}
# Set of contact emails
self.contact_emails = {
self.contacts_billing.email,
self.contacts_security.email,
self.contacts_operations.email,
}
def __get_contact_information__(self):
try:
@@ -54,16 +53,10 @@ class Account(AWSService):
phone_number=primary_account_contact.get("PhoneNumber"),
)
except Exception as error:
if error.response["Error"]["Code"] == "AccessDeniedException":
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return None
else:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return Contact(type="PRIMARY")
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return Contact(type="PRIMARY")
def __get_alternate_contact__(self, contact_type: str):
try:

View File

@@ -21,7 +21,7 @@
"Terraform": ""
},
"Recommendation": {
"Text": "Monitor certificate expiration and take automated action to renew, replace or remove. Having shorter TTL for any security artifact is a general recommendation, but requires additional automation in place. If not longer required delete certificate. Use AWS config using the managed rule: acm-certificate-expiration-check.",
"Text": "Monitor certificate expiration and take automated action to renew; replace or remove. Having shorter TTL for any security artifact is a general recommendation; but requires additional automation in place. If not longer required delete certificate. Use AWS config using the managed rule: acm-certificate-expiration-check.",
"Url": "https://docs.aws.amazon.com/config/latest/developerguide/acm-certificate-expiration-check.html"
}
},

View File

@@ -1,36 +1,33 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.acm.acm_client import acm_client
DAYS_TO_EXPIRE_THRESHOLD = 7
class acm_certificates_expiration_check(Check):
def execute(self):
findings = []
for certificate in acm_client.certificates:
if certificate.in_use or not acm_client.audit_info.ignore_unused_services:
report = Check_Report_AWS(self.metadata())
report.region = certificate.region
if certificate.expiration_days > acm_client.audit_config.get(
"days_to_expire_threshold", 7
):
report.status = "PASS"
report.status_extended = f"ACM Certificate {certificate.id} for {certificate.name} expires in {certificate.expiration_days} days."
report.resource_id = certificate.id
report.resource_details = certificate.name
report.resource_arn = certificate.arn
report.resource_tags = certificate.tags
report = Check_Report_AWS(self.metadata())
report.region = certificate.region
if certificate.expiration_days > DAYS_TO_EXPIRE_THRESHOLD:
report.status = "PASS"
report.status_extended = f"ACM Certificate {certificate.id} for {certificate.name} expires in {certificate.expiration_days} days."
report.resource_id = certificate.id
report.resource_details = certificate.name
report.resource_arn = certificate.arn
report.resource_tags = certificate.tags
else:
report.status = "FAIL"
if certificate.expiration_days < 0:
report.status_extended = f"ACM Certificate {certificate.id} for {certificate.name} has expired ({abs(certificate.expiration_days)} days ago)."
else:
report.status = "FAIL"
if certificate.expiration_days < 0:
report.status_extended = f"ACM Certificate {certificate.id} for {certificate.name} has expired ({abs(certificate.expiration_days)} days ago)."
report.check_metadata.Severity = "high"
else:
report.status_extended = f"ACM Certificate {certificate.id} for {certificate.name} is about to expire in {certificate.expiration_days} days."
report.check_metadata.Severity = "medium"
report.status_extended = f"ACM Certificate {certificate.id} for {certificate.name} is about to expire in {certificate.expiration_days} days."
report.resource_id = certificate.id
report.resource_details = certificate.name
report.resource_arn = certificate.arn
report.resource_tags = certificate.tags
report.resource_id = certificate.id
report.resource_details = certificate.name
report.resource_arn = certificate.arn
report.resource_tags = certificate.tags
findings.append(report)
findings.append(report)
return findings

View File

@@ -6,30 +6,29 @@ class acm_certificates_transparency_logs_enabled(Check):
def execute(self):
findings = []
for certificate in acm_client.certificates:
if certificate.in_use or not acm_client.audit_info.ignore_unused_services:
report = Check_Report_AWS(self.metadata())
report.region = certificate.region
if certificate.type == "IMPORTED":
report.status = "PASS"
report.status_extended = f"ACM Certificate {certificate.id} for {certificate.name} is imported."
report = Check_Report_AWS(self.metadata())
report.region = certificate.region
if certificate.type == "IMPORTED":
report.status = "PASS"
report.status_extended = f"ACM Certificate {certificate.id} for {certificate.name} is imported."
report.resource_id = certificate.id
report.resource_details = certificate.name
report.resource_arn = certificate.arn
report.resource_tags = certificate.tags
else:
if not certificate.transparency_logging:
report.status = "FAIL"
report.status_extended = f"ACM Certificate {certificate.id} for {certificate.name} has Certificate Transparency logging disabled."
report.resource_id = certificate.id
report.resource_details = certificate.name
report.resource_arn = certificate.arn
report.resource_tags = certificate.tags
else:
if not certificate.transparency_logging:
report.status = "FAIL"
report.status_extended = f"ACM Certificate {certificate.id} for {certificate.name} has Certificate Transparency logging disabled."
report.resource_id = certificate.id
report.resource_details = certificate.name
report.resource_arn = certificate.arn
report.resource_tags = certificate.tags
else:
report.status = "PASS"
report.status_extended = f"ACM Certificate {certificate.id} for {certificate.name} has Certificate Transparency logging enabled."
report.resource_id = certificate.id
report.resource_details = certificate.name
report.resource_arn = certificate.arn
report.resource_tags = certificate.tags
findings.append(report)
report.status = "PASS"
report.status_extended = f"ACM Certificate {certificate.id} for {certificate.name} has Certificate Transparency logging enabled."
report.resource_id = certificate.id
report.resource_details = certificate.name
report.resource_arn = certificate.arn
report.resource_tags = certificate.tags
findings.append(report)
return findings

View File

@@ -50,7 +50,6 @@ class ACM(AWSService):
id=certificate["CertificateArn"].split("/")[-1],
type=certificate["Type"],
expiration_days=certificate_expiration_time,
in_use=certificate.get("InUse", False),
transparency_logging=False,
region=regional_client.region,
)
@@ -100,6 +99,5 @@ class Certificate(BaseModel):
type: str
tags: Optional[list] = []
expiration_days: int
in_use: bool
transparency_logging: Optional[bool]
region: str

View File

@@ -19,9 +19,9 @@
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "https://docs.prowler.com/checks/aws/public-policies/public_6-api-gateway-authorizer-set#cloudformation",
"NativeIaC": "https://docs.bridgecrew.io/docs/public_6-api-gateway-authorizer-set#cloudformation",
"Other": "",
"Terraform": "https://docs.prowler.com/checks/aws/public-policies/public_6-api-gateway-authorizer-set#terraform"
"Terraform": "https://docs.bridgecrew.io/docs/public_6-api-gateway-authorizer-set#terraform"
},
"Recommendation": {
"Text": "Implement Amazon Cognito or a Lambda function to control access to your API.",

View File

@@ -21,7 +21,7 @@
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": "https://docs.prowler.com/checks/aws/logging-policies/ensure-api-gateway-stage-have-logging-level-defined-as-appropiate#terraform"
"Terraform": "https://docs.bridgecrew.io/docs/ensure-api-gateway-stage-have-logging-level-defined-as-appropiate#terraform"
},
"Recommendation": {
"Text": "Monitoring is an important part of maintaining the reliability, availability and performance of API Gateway and your AWS solutions. You should collect monitoring data from all of the parts of your AWS solution. CloudTrail provides a record of actions taken by a user, role, or an AWS service in API Gateway. Using the information collected by CloudTrail, you can determine the request that was made to API Gateway, the IP address from which the request was made, who made the request, etc.",

View File

@@ -1,6 +1,5 @@
from typing import Optional
from botocore.exceptions import ClientError
from pydantic import BaseModel
from prowler.lib.logger import logger
@@ -48,28 +47,12 @@ class APIGateway(AWSService):
logger.info("APIGateway - Getting Rest APIs authorizer...")
try:
for rest_api in self.rest_apis:
try:
regional_client = self.regional_clients[rest_api.region]
authorizers = regional_client.get_authorizers(
restApiId=rest_api.id
)["items"]
if authorizers:
rest_api.authorizer = True
except ClientError as error:
if error.response["Error"]["Code"] == "NotFoundException":
logger.warning(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
else:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
regional_client = self.regional_clients[rest_api.region]
authorizers = regional_client.get_authorizers(restApiId=rest_api.id)[
"items"
]
if authorizers:
rest_api.authorizer = True
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
@@ -79,25 +62,10 @@ class APIGateway(AWSService):
logger.info("APIGateway - Describing Rest API...")
try:
for rest_api in self.rest_apis:
try:
regional_client = self.regional_clients[rest_api.region]
rest_api_info = regional_client.get_rest_api(restApiId=rest_api.id)
if rest_api_info["endpointConfiguration"]["types"] == ["PRIVATE"]:
rest_api.public_endpoint = False
except ClientError as error:
if error.response["Error"]["Code"] == "NotFoundException":
logger.warning(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
else:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
regional_client = self.regional_clients[rest_api.region]
rest_api_info = regional_client.get_rest_api(restApiId=rest_api.id)
if rest_api_info["endpointConfiguration"]["types"] == ["PRIVATE"]:
rest_api.public_endpoint = False
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
@@ -107,44 +75,29 @@ class APIGateway(AWSService):
logger.info("APIGateway - Getting stages for Rest APIs...")
try:
for rest_api in self.rest_apis:
try:
regional_client = self.regional_clients[rest_api.region]
stages = regional_client.get_stages(restApiId=rest_api.id)
for stage in stages["item"]:
waf = None
logging = False
client_certificate = False
if "webAclArn" in stage:
waf = stage["webAclArn"]
if "methodSettings" in stage:
if stage["methodSettings"]:
logging = True
if "clientCertificateId" in stage:
client_certificate = True
arn = f"arn:{self.audited_partition}:apigateway:{regional_client.region}::/restapis/{rest_api.id}/stages/{stage['stageName']}"
rest_api.stages.append(
Stage(
name=stage["stageName"],
arn=arn,
logging=logging,
client_certificate=client_certificate,
waf=waf,
tags=[stage.get("tags")],
)
regional_client = self.regional_clients[rest_api.region]
stages = regional_client.get_stages(restApiId=rest_api.id)
for stage in stages["item"]:
waf = None
logging = False
client_certificate = False
if "webAclArn" in stage:
waf = stage["webAclArn"]
if "methodSettings" in stage:
if stage["methodSettings"]:
logging = True
if "clientCertificateId" in stage:
client_certificate = True
arn = f"arn:{self.audited_partition}:apigateway:{regional_client.region}::/restapis/{rest_api.id}/stages/{stage['stageName']}"
rest_api.stages.append(
Stage(
name=stage["stageName"],
arn=arn,
logging=logging,
client_certificate=client_certificate,
waf=waf,
tags=[stage.get("tags")],
)
except ClientError as error:
if error.response["Error"]["Code"] == "NotFoundException":
logger.warning(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
else:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
@@ -155,50 +108,33 @@ class APIGateway(AWSService):
logger.info("APIGateway - Getting API resources...")
try:
for rest_api in self.rest_apis:
try:
regional_client = self.regional_clients[rest_api.region]
get_resources_paginator = regional_client.get_paginator(
"get_resources"
)
for page in get_resources_paginator.paginate(restApiId=rest_api.id):
for resource in page["items"]:
id = resource["id"]
resource_methods = []
methods_auth = {}
for resource_method in resource.get(
"resourceMethods", {}
).keys():
resource_methods.append(resource_method)
regional_client = self.regional_clients[rest_api.region]
get_resources_paginator = regional_client.get_paginator("get_resources")
for page in get_resources_paginator.paginate(restApiId=rest_api.id):
for resource in page["items"]:
id = resource["id"]
resource_methods = []
methods_auth = {}
for resource_method in resource.get(
"resourceMethods", {}
).keys():
resource_methods.append(resource_method)
for resource_method in resource_methods:
if resource_method != "OPTIONS":
method_config = regional_client.get_method(
restApiId=rest_api.id,
resourceId=id,
httpMethod=resource_method,
)
auth_type = method_config["authorizationType"]
methods_auth.update({resource_method: auth_type})
rest_api.resources.append(
PathResourceMethods(
path=resource["path"], resource_methods=methods_auth
for resource_method in resource_methods:
if resource_method != "OPTIONS":
method_config = regional_client.get_method(
restApiId=rest_api.id,
resourceId=id,
httpMethod=resource_method,
)
)
except ClientError as error:
if error.response["Error"]["Code"] == "NotFoundException":
logger.warning(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
else:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
auth_type = method_config["authorizationType"]
methods_auth.update({resource_method: auth_type})
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
rest_api.resources.append(
PathResourceMethods(
path=resource["path"], resource_methods=methods_auth
)
)
except Exception as error:
logger.error(

View File

@@ -20,8 +20,8 @@
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "https://docs.prowler.com/checks/aws/logging-policies/bc_aws_logging_30#aws-console",
"Terraform": "https://docs.prowler.com/checks/aws/logging-policies/bc_aws_logging_30#cloudformation"
"Other": "https://docs.bridgecrew.io/docs/bc_aws_logging_30#aws-console",
"Terraform": "https://docs.bridgecrew.io/docs/bc_aws_logging_30#cloudformation"
},
"Recommendation": {
"Text": "Monitoring is an important part of maintaining the reliability, availability and performance of API Gateway and your AWS solutions. You should collect monitoring data from all of the parts of your AWS solution. CloudTrail provides a record of actions taken by a user, role, or an AWS service in API Gateway. Using the information collected by CloudTrail, you can determine the request that was made to API Gateway, the IP address from which the request was made, who made the request, etc.",

View File

@@ -32,7 +32,7 @@ class ApiGatewayV2(AWSService):
arn=arn,
id=apigw["ApiId"],
region=regional_client.region,
name=apigw.get("Name", ""),
name=apigw["Name"],
tags=[apigw.get("Tags")],
)
)

View File

@@ -18,7 +18,7 @@
"CLI": "aws athena update-work-group --region <REGION> --work-group <workgroup_name> --configuration-updates ResultConfigurationUpdates={EncryptionConfiguration={EncryptionOption=SSE_S3|SSE_KMS|CSE_KMS}}",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/Athena/encryption-enabled.html",
"Terraform": "https://docs.prowler.com/checks/aws/general-policies/ensure-that-athena-workgroup-is-encrypted#terraform"
"Terraform": "https://docs.bridgecrew.io/docs/ensure-that-athena-workgroup-is-encrypted#terraform"
},
"Recommendation": {
"Text": "Enable Encryption. Use a CMK where possible. It will provide additional management and privacy benefits.",

View File

@@ -16,9 +16,9 @@
"Remediation": {
"Code": {
"CLI": "aws athena update-work-group --region <REGION> --work-group <workgroup_name> --configuration-updates EnforceWorkGroupConfiguration=True",
"NativeIaC": "https://docs.prowler.com/checks/aws/general-policies/bc_aws_general_33#cloudformation",
"NativeIaC": "https://docs.bridgecrew.io/docs/bc_aws_general_33#cloudformation",
"Other": "",
"Terraform": "https://docs.prowler.com/checks/aws/general-policies/bc_aws_general_33#terraform"
"Terraform": "https://docs.bridgecrew.io/docs/bc_aws_general_33#terraform"
},
"Recommendation": {
"Text": "Ensure that workgroup configuration is enforced so it cannot be overriden by client-side settings.",
@@ -29,4 +29,4 @@
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
}

View File

@@ -9,7 +9,7 @@
"Severity": "low",
"ResourceType": "AwsLambdaFunction",
"Description": "Check if Lambda functions invoke API operations are being recorded by CloudTrail.",
"Risk": "If logs are not enabled, monitoring of service use and threat analysis is not possible.",
"Risk": "If logs are not enabled; monitoring of service use and threat analysis is not possible.",
"RelatedUrl": "https://docs.aws.amazon.com/lambda/latest/dg/logging-using-cloudtrail.html",
"Remediation": {
"Code": {

View File

@@ -9,7 +9,7 @@
"Severity": "critical",
"ResourceType": "AwsLambdaFunction",
"Description": "Find secrets in Lambda functions code.",
"Risk": "The use of a hard-coded password increases the possibility of password guessing. If hard-coded passwords are used, it is possible that malicious users gain access through the account in question.",
"Risk": "The use of a hard-coded password increases the possibility of password guessing. If hard-coded passwords are used; it is possible that malicious users gain access through the account in question.",
"RelatedUrl": "https://docs.aws.amazon.com/secretsmanager/latest/userguide/lambda-functions.html",
"Remediation": {
"Code": {

View File

@@ -9,14 +9,14 @@
"Severity": "critical",
"ResourceType": "AwsLambdaFunction",
"Description": "Find secrets in Lambda functions variables.",
"Risk": "The use of a hard-coded password increases the possibility of password guessing. If hard-coded passwords are used, it is possible that malicious users gain access through the account in question.",
"Risk": "The use of a hard-coded password increases the possibility of password guessing. If hard-coded passwords are used; it is possible that malicious users gain access through the account in question.",
"RelatedUrl": "https://docs.aws.amazon.com/secretsmanager/latest/userguide/lambda-functions.html",
"Remediation": {
"Code": {
"CLI": "https://docs.prowler.com/checks/aws/secrets-policies/bc_aws_secrets_3#cli-command",
"NativeIaC": "https://docs.prowler.com/checks/aws/secrets-policies/bc_aws_secrets_3#cloudformation",
"CLI": "https://docs.bridgecrew.io/docs/bc_aws_secrets_3#cli-command",
"NativeIaC": "https://docs.bridgecrew.io/docs/bc_aws_secrets_3#cloudformation",
"Other": "",
"Terraform": "https://docs.prowler.com/checks/aws/secrets-policies/bc_aws_secrets_3#terraform"
"Terraform": "https://docs.bridgecrew.io/docs/bc_aws_secrets_3#terraform"
},
"Recommendation": {
"Text": "Use Secrets Manager to securely provide database credentials to Lambda functions and secure the databases as well as use the credentials to connect and query them without hardcoding the secrets in code or passing them through environmental variables.",

View File

@@ -9,7 +9,7 @@
"Severity": "medium",
"ResourceType": "AwsLambdaFunction",
"Description": "Find obsolete Lambda runtimes.",
"Risk": "If you have functions running on a runtime that will be deprecated in the next 60 days, Lambda notifies you by email that you should prepare by migrating your function to a supported runtime. In some cases, such as security issues that require a backwards-incompatible update, or software that does not support a long-term support (LTS) schedule, advance notice might not be possible. After a runtime is deprecated, Lambda might retire it completely at any time by disabling invocation. Deprecated runtimes are not eligible for security updates or technical support.",
"Risk": "If you have functions running on a runtime that will be deprecated in the next 60 days; Lambda notifies you by email that you should prepare by migrating your function to a supported runtime. In some cases; such as security issues that require a backwards-incompatible update; or software that does not support a long-term support (LTS) schedule; advance notice might not be possible. After a runtime is deprecated; Lambda might retire it completely at any time by disabling invocation. Deprecated runtimes are not eligible for security updates or technical support.",
"RelatedUrl": "https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html",
"Remediation": {
"Code": {

View File

@@ -1,7 +1,6 @@
from datetime import datetime
from typing import Optional
from botocore.client import ClientError
from pydantic import BaseModel
from prowler.lib.logger import logger
@@ -38,8 +37,6 @@ class Backup(AWSService):
self.audit_resources,
)
):
if self.backup_vaults is None:
self.backup_vaults = []
self.backup_vaults.append(
BackupVault(
arn=configuration.get("BackupVaultArn"),
@@ -58,13 +55,7 @@ class Backup(AWSService):
),
)
)
except ClientError as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
if error.response["Error"]["Code"] == "AccessDeniedException":
if not self.backup_vaults:
self.backup_vaults = None
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"

View File

@@ -5,24 +5,24 @@ from prowler.providers.aws.services.backup.backup_client import backup_client
class backup_vaults_encrypted(Check):
def execute(self):
findings = []
if backup_client.backup_vaults:
for backup_vault in backup_client.backup_vaults:
# By default we assume that the result is fail
report = Check_Report_AWS(self.metadata())
report.status = "FAIL"
for backup_vault in backup_client.backup_vaults:
# By default we assume that the result is fail
report = Check_Report_AWS(self.metadata())
report.status = "FAIL"
report.status_extended = (
f"Backup Vault {backup_vault.name} is not encrypted."
)
report.resource_arn = backup_vault.arn
report.resource_id = backup_vault.name
report.region = backup_vault.region
# if it is encrypted we only change the status and the status extended
if backup_vault.encryption:
report.status = "PASS"
report.status_extended = (
f"Backup Vault {backup_vault.name} is not encrypted."
f"Backup Vault {backup_vault.name} is encrypted."
)
report.resource_arn = backup_vault.arn
report.resource_id = backup_vault.name
report.region = backup_vault.region
# if it is encrypted we only change the status and the status extended
if backup_vault.encryption:
report.status = "PASS"
report.status_extended = (
f"Backup Vault {backup_vault.name} is encrypted."
)
# then we store the finding
findings.append(report)
# then we store the finding
findings.append(report)
return findings

View File

@@ -5,19 +5,18 @@ from prowler.providers.aws.services.backup.backup_client import backup_client
class backup_vaults_exist(Check):
def execute(self):
findings = []
if backup_client.backup_vaults is not None:
report = Check_Report_AWS(self.metadata())
report.status = "FAIL"
report.status_extended = "No Backup Vault exist."
report.resource_arn = backup_client.backup_vault_arn_template
report.resource_id = backup_client.audited_account
report.region = backup_client.region
if backup_client.backup_vaults:
report.status = "PASS"
report.status_extended = f"At least one backup vault exists: {backup_client.backup_vaults[0].name}."
report.resource_arn = backup_client.backup_vaults[0].arn
report.resource_id = backup_client.backup_vaults[0].name
report.region = backup_client.backup_vaults[0].region
report = Check_Report_AWS(self.metadata())
report.status = "FAIL"
report.status_extended = "No Backup Vault exist."
report.resource_arn = backup_client.backup_vault_arn_template
report.resource_id = backup_client.audited_account
report.region = backup_client.region
if backup_client.backup_vaults:
report.status = "PASS"
report.status_extended = f"At least one backup vault exists: {backup_client.backup_vaults[0].name}."
report.resource_arn = backup_client.backup_vaults[0].arn
report.resource_id = backup_client.backup_vaults[0].name
report.region = backup_client.backup_vaults[0].region
findings.append(report)
findings.append(report)
return findings

View File

@@ -10,10 +10,10 @@
"ResourceType": "AwsCloudFormationStack",
"Description": "Find secrets in CloudFormation outputs",
"Risk": "Secrets hardcoded into CloudFormation outputs can be used by malware and bad actors to gain lateral access to other services.",
"RelatedUrl": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html",
"RelatedUrl": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-secretsmanager-secret-generatesecretstring.html",
"Remediation": {
"Code": {
"CLI": "https://docs.prowler.com/checks/aws/secrets-policies/bc_aws_secrets_2#cli-command",
"CLI": "https://docs.bridgecrew.io/docs/bc_aws_secrets_2#cli-command",
"NativeIaC": "",
"Other": "",
"Terraform": ""

View File

@@ -9,7 +9,7 @@
"Severity": "medium",
"ResourceType": "AwsCloudFormationStack",
"Description": "Enable termination protection for Cloudformation Stacks",
"Risk": "Without termination protection enabled, a critical cloudformation stack can be accidently deleted.",
"Risk": "Without termination protection enabled; a critical cloudformation stack can be accidently deleted.",
"RelatedUrl": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-protect-stacks.html",
"Remediation": {
"Code": {

View File

@@ -9,7 +9,7 @@
"Severity": "low",
"ResourceType": "AwsCloudFrontDistribution",
"Description": "Check if Geo restrictions are enabled in CloudFront distributions.",
"Risk": "Consider countries where service should not be accessed, by legal or compliance requirements. Additionally if not restricted the attack vector is increased.",
"Risk": "Consider countries where service should not be accessed; by legal or compliance requirements. Additionally if not restricted the attack vector is increased.",
"RelatedUrl": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/georestrictions.html",
"Remediation": {
"Code": {
@@ -19,7 +19,7 @@
"Terraform": ""
},
"Recommendation": {
"Text": "If possible, define and enable Geo restrictions for this service.",
"Text": "If possible; define and enable Geo restrictions for this service.",
"Url": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/georestrictions.html"
}
},

View File

@@ -14,9 +14,9 @@
"Remediation": {
"Code": {
"CLI": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/CloudFront/security-policy.html",
"NativeIaC": "https://docs.prowler.com/checks/aws/networking-policies/networking_32#cloudformation",
"NativeIaC": "https://docs.bridgecrew.io/docs/networking_32#cloudformation",
"Other": "",
"Terraform": "https://docs.prowler.com/checks/aws/networking-policies/networking_32#terraform"
"Terraform": "https://docs.bridgecrew.io/docs/networking_32#terraform"
},
"Recommendation": {
"Text": "Use HTTPS everywhere possible. It will enforce privacy and protect against account hijacking and other threats.",

View File

@@ -13,10 +13,10 @@
"RelatedUrl": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/AccessLogs.html",
"Remediation": {
"Code": {
"CLI": "https://docs.prowler.com/checks/aws/logging-policies/logging_20#cli-command",
"NativeIaC": "https://docs.prowler.com/checks/aws/logging-policies/logging_20#cloudformation",
"CLI": "https://docs.bridgecrew.io/docs/logging_20#cli-command",
"NativeIaC": "https://docs.bridgecrew.io/docs/logging_20#cloudformation",
"Other": "",
"Terraform": "https://docs.prowler.com/checks/aws/logging-policies/logging_20#terraform"
"Terraform": "https://docs.bridgecrew.io/docs/logging_20#terraform"
},
"Recommendation": {
"Text": "Real-time monitoring can be achieved by directing CloudTrail Logs to CloudWatch Logs and establishing corresponding metric filters and alarms. Enable logging for services with defined log rotation. These logs are useful for Incident Response and forensics investigation among other use cases.",

View File

@@ -13,9 +13,9 @@
"RelatedUrl": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/secure-connections-supported-viewer-protocols-ciphers.html",
"Remediation": {
"Code": {
"CLI": "https://docs.prowler.com/checks/aws/networking-policies/networking_33#cli-command",
"CLI": "https://docs.bridgecrew.io/docs/networking_33#cli-command",
"NativeIaC": "",
"Other": "https://docs.prowler.com/checks/aws/networking-policies/networking_33#aws-cloudfront-console",
"Other": "https://docs.bridgecrew.io/docs/networking_33#aws-cloudfront-console",
"Terraform": ""
},
"Recommendation": {

View File

@@ -11,17 +11,17 @@
"Severity": "medium",
"ResourceType": "AwsCloudFrontDistribution",
"Description": "Check if CloudFront distributions are using WAF.",
"Risk": "Potential attacks and / or abuse of service, more even for even for internet reachable services.",
"Risk": "Potential attacks and / or abuse of service; more even for even for internet reachable services.",
"RelatedUrl": "https://docs.aws.amazon.com/waf/latest/developerguide/cloudfront-features.html",
"Remediation": {
"Code": {
"CLI": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/CloudFront/cloudfront-integrated-with-waf.html",
"NativeIaC": "https://docs.prowler.com/checks/aws/general-policies/bc_aws_general_27#cloudformation",
"Other": "https://docs.prowler.com/checks/aws/general-policies/bc_aws_general_27#cloudfront-console",
"Terraform": "https://docs.prowler.com/checks/aws/general-policies/bc_aws_general_27#terraform"
"NativeIaC": "https://docs.bridgecrew.io/docs/bc_aws_general_27#cloudformation",
"Other": "https://docs.bridgecrew.io/docs/bc_aws_general_27#cloudfront-console",
"Terraform": "https://docs.bridgecrew.io/docs/bc_aws_general_27#terraform"
},
"Recommendation": {
"Text": "Use AWS WAF to protect your service from common web exploits. These could affect availability and performance, compromise security, or consume excessive resources.",
"Text": "Use AWS WAF to protect your service from common web exploits. These could affect availability and performance; compromise security; or consume excessive resources.",
"Url": "https://docs.aws.amazon.com/waf/latest/developerguide/cloudfront-features.html"
}
},

View File

@@ -8,29 +8,28 @@ from prowler.providers.aws.services.s3.s3_client import s3_client
class cloudtrail_bucket_requires_mfa_delete(Check):
def execute(self):
findings = []
if cloudtrail_client.trails is not None:
for trail in cloudtrail_client.trails.values():
if trail.is_logging:
trail_bucket_is_in_account = False
trail_bucket = trail.s3_bucket
report = Check_Report_AWS(self.metadata())
report.region = trail.home_region
report.resource_id = trail.name
report.resource_arn = trail.arn
report.resource_tags = trail.tags
report.status = "FAIL"
report.status_extended = f"Trail {trail.name} bucket ({trail_bucket}) does not have MFA delete enabled."
for bucket in s3_client.buckets:
if trail_bucket == bucket.name:
trail_bucket_is_in_account = True
if bucket.mfa_delete:
report.status = "PASS"
report.status_extended = f"Trail {trail.name} bucket ({trail_bucket}) has MFA delete enabled."
# check if trail bucket is a cross account bucket
if not trail_bucket_is_in_account:
report.status = "INFO"
report.status_extended = f"Trail {trail.name} bucket ({trail_bucket}) is a cross-account bucket in another account out of Prowler's permissions scope, please check it manually."
for trail in cloudtrail_client.trails.values():
if trail.is_logging:
trail_bucket_is_in_account = False
trail_bucket = trail.s3_bucket
report = Check_Report_AWS(self.metadata())
report.region = trail.region
report.resource_id = trail.name
report.resource_arn = trail.arn
report.resource_tags = trail.tags
report.status = "FAIL"
report.status_extended = f"Trail {trail.name} bucket ({trail_bucket}) does not have MFA delete enabled."
for bucket in s3_client.buckets:
if trail_bucket == bucket.name:
trail_bucket_is_in_account = True
if bucket.mfa_delete:
report.status = "PASS"
report.status_extended = f"Trail {trail.name} bucket ({trail_bucket}) has MFA delete enabled."
# check if trail bucket is a cross account bucket
if not trail_bucket_is_in_account:
report.status = "INFO"
report.status_extended = f"Trail {trail.name} bucket ({trail_bucket}) is a cross-account bucket in another account out of Prowler's permissions scope, please check it manually."
findings.append(report)
findings.append(report)
return findings

View File

@@ -13,13 +13,13 @@
"Severity": "low",
"ResourceType": "AwsCloudTrailTrail",
"Description": "Ensure CloudTrail trails are integrated with CloudWatch Logs",
"Risk": "Sending CloudTrail logs to CloudWatch Logs will facilitate real-time and historic activity logging based on user, API, resource, and IP address, and provides opportunity to establish alarms and notifications for anomalous or sensitivity account activity.",
"Risk": "Sending CloudTrail logs to CloudWatch Logs will facilitate real-time and historic activity logging based on user; API; resource; and IP address; and provides opportunity to establish alarms and notifications for anomalous or sensitivity account activity.",
"RelatedUrl": "",
"Remediation": {
"Code": {
"CLI": "aws cloudtrail update-trail --name <trail_name> --cloudwatch-logs-log-group- arn <cloudtrail_log_group_arn> --cloudwatch-logs-role-arn <cloudtrail_cloudwatchLogs_role_arn>",
"NativeIaC": "",
"Other": "https://docs.prowler.com/checks/aws/logging-policies/logging_4#aws-console",
"Other": "https://docs.bridgecrew.io/docs/logging_4#aws-console",
"Terraform": ""
},
"Recommendation": {

View File

@@ -11,38 +11,37 @@ maximum_time_without_logging = 1
class cloudtrail_cloudwatch_logging_enabled(Check):
def execute(self):
findings = []
if cloudtrail_client.trails is not None:
for trail in cloudtrail_client.trails.values():
if trail.name:
report = Check_Report_AWS(self.metadata())
report.region = trail.home_region
report.resource_id = trail.name
report.resource_arn = trail.arn
report.resource_tags = trail.tags
report.status = "PASS"
if trail.is_multiregion:
report.status_extended = f"Multiregion trail {trail.name} has been logging the last 24h."
else:
report.status_extended = f"Single region trail {trail.name} has been logging the last 24h."
if trail.latest_cloudwatch_delivery_time:
last_log_delivery = (
datetime.now().replace(tzinfo=timezone.utc)
- trail.latest_cloudwatch_delivery_time
)
if last_log_delivery > timedelta(
days=maximum_time_without_logging
):
report.status = "FAIL"
if trail.is_multiregion:
report.status_extended = f"Multiregion trail {trail.name} is not logging in the last 24h."
else:
report.status_extended = f"Single region trail {trail.name} is not logging in the last 24h."
else:
for trail in cloudtrail_client.trails.values():
if trail.name:
report = Check_Report_AWS(self.metadata())
report.region = trail.region
report.resource_id = trail.name
report.resource_arn = trail.arn
report.resource_tags = trail.tags
report.status = "PASS"
if trail.is_multiregion:
report.status_extended = (
f"Multiregion trail {trail.name} has been logging the last 24h."
)
else:
report.status_extended = f"Single region trail {trail.name} has been logging the last 24h."
if trail.latest_cloudwatch_delivery_time:
last_log_delivery = (
datetime.now().replace(tzinfo=timezone.utc)
- trail.latest_cloudwatch_delivery_time
)
if last_log_delivery > timedelta(days=maximum_time_without_logging):
report.status = "FAIL"
if trail.is_multiregion:
report.status_extended = f"Multiregion trail {trail.name} is not logging in the last 24h or not configured to deliver logs."
report.status_extended = f"Multiregion trail {trail.name} is not logging in the last 24h."
else:
report.status_extended = f"Single region trail {trail.name} is not logging in the last 24h or not configured to deliver logs."
findings.append(report)
report.status_extended = f"Single region trail {trail.name} is not logging in the last 24h."
else:
report.status = "FAIL"
if trail.is_multiregion:
report.status_extended = f"Multiregion trail {trail.name} is not logging in the last 24h or not configured to deliver logs."
else:
report.status_extended = f"Single region trail {trail.name} is not logging in the last 24h or not configured to deliver logs."
findings.append(report)
return findings

View File

@@ -7,18 +7,19 @@ from prowler.providers.aws.services.cloudtrail.cloudtrail_client import (
class cloudtrail_insights_exist(Check):
def execute(self):
findings = []
if cloudtrail_client.trails is not None:
for trail in cloudtrail_client.trails.values():
if trail.is_logging:
report = Check_Report_AWS(self.metadata())
report.region = trail.home_region
report.resource_id = trail.name
report.resource_arn = trail.arn
report.resource_tags = trail.tags
report.status = "FAIL"
report.status_extended = f"Trail {trail.name} does not have insight selectors and it is logging."
if trail.has_insight_selectors:
report.status = "PASS"
report.status_extended = f"Trail {trail.name} has insight selectors and it is logging."
findings.append(report)
for trail in cloudtrail_client.trails.values():
if trail.is_logging:
report = Check_Report_AWS(self.metadata())
report.region = trail.region
report.resource_id = trail.name
report.resource_arn = trail.arn
report.resource_tags = trail.tags
report.status = "FAIL"
report.status_extended = f"Trail {trail.name} does not have insight selectors and it is logging."
if trail.has_insight_selectors:
report.status = "PASS"
report.status_extended = (
f"Trail {trail.name} has insight selectors and it is logging."
)
findings.append(report)
return findings

View File

@@ -13,12 +13,12 @@
"Severity": "medium",
"ResourceType": "AwsCloudTrailTrail",
"Description": "Ensure CloudTrail logs are encrypted at rest using KMS CMKs",
"Risk": "By default, the log files delivered by CloudTrail to your bucket are encrypted by Amazon server-side encryption with Amazon S3-managed encryption keys (SSE-S3). To provide a security layer that is directly manageable, you can instead use server-side encryption with AWS KMSmanaged keys (SSE-KMS) for your CloudTrail log files.",
"Risk": "By default; the log files delivered by CloudTrail to your bucket are encrypted by Amazon server-side encryption with Amazon S3-managed encryption keys (SSE-S3). To provide a security layer that is directly manageable; you can instead use server-side encryption with AWS KMSmanaged keys (SSE-KMS) for your CloudTrail log files.",
"RelatedUrl": "",
"Remediation": {
"Code": {
"CLI": "aws cloudtrail update-trail --name <trail_name> --kms-id <cloudtrail_kms_key> aws kms put-key-policy --key-id <cloudtrail_kms_key> --policy <cloudtrail_kms_key_policy>",
"NativeIaC": "https://docs.prowler.com/checks/aws/logging-policies/logging_7#fix---buildtime",
"NativeIaC": "https://docs.bridgecrew.io/docs/logging_7#fix---buildtime",
"Other": "",
"Terraform": ""
},

View File

@@ -7,29 +7,32 @@ from prowler.providers.aws.services.cloudtrail.cloudtrail_client import (
class cloudtrail_kms_encryption_enabled(Check):
def execute(self):
findings = []
if cloudtrail_client.trails is not None:
for trail in cloudtrail_client.trails.values():
if trail.name:
report = Check_Report_AWS(self.metadata())
report.region = trail.home_region
report.resource_id = trail.name
report.resource_arn = trail.arn
report.resource_tags = trail.tags
report.status = "FAIL"
for trail in cloudtrail_client.trails.values():
if trail.name:
report = Check_Report_AWS(self.metadata())
report.region = trail.region
report.resource_id = trail.name
report.resource_arn = trail.arn
report.resource_tags = trail.tags
report.status = "FAIL"
if trail.is_multiregion:
report.status_extended = (
f"Multiregion trail {trail.name} has encryption disabled."
)
else:
report.status_extended = (
f"Single region trail {trail.name} has encryption disabled."
)
if trail.kms_key:
report.status = "PASS"
if trail.is_multiregion:
report.status_extended = (
f"Multiregion trail {trail.name} has encryption disabled."
f"Multiregion trail {trail.name} has encryption enabled."
)
else:
report.status_extended = (
f"Single region trail {trail.name} has encryption disabled."
f"Single region trail {trail.name} has encryption enabled."
)
if trail.kms_key:
report.status = "PASS"
if trail.is_multiregion:
report.status_extended = f"Multiregion trail {trail.name} has encryption enabled."
else:
report.status_extended = f"Single region trail {trail.name} has encryption enabled."
findings.append(report)
findings.append(report)
return findings

View File

@@ -18,9 +18,9 @@
"Remediation": {
"Code": {
"CLI": "aws cloudtrail update-trail --name <trail_name> --enable-log-file-validation",
"NativeIaC": "https://docs.prowler.com/checks/aws/logging-policies/logging_2#cloudformation",
"NativeIaC": "https://docs.bridgecrew.io/docs/logging_2#cloudformation",
"Other": "",
"Terraform": "https://docs.prowler.com/checks/aws/logging-policies/logging_2#terraform"
"Terraform": "https://docs.bridgecrew.io/docs/logging_2#terraform"
},
"Recommendation": {
"Text": "Ensure LogFileValidationEnabled is set to true for each trail.",

View File

@@ -7,25 +7,26 @@ from prowler.providers.aws.services.cloudtrail.cloudtrail_client import (
class cloudtrail_log_file_validation_enabled(Check):
def execute(self):
findings = []
if cloudtrail_client.trails is not None:
for trail in cloudtrail_client.trails.values():
if trail.name:
report = Check_Report_AWS(self.metadata())
report.region = trail.home_region
report.resource_id = trail.name
report.resource_arn = trail.arn
report.resource_tags = trail.tags
report.status = "FAIL"
for trail in cloudtrail_client.trails.values():
if trail.name:
report = Check_Report_AWS(self.metadata())
report.region = trail.region
report.resource_id = trail.name
report.resource_arn = trail.arn
report.resource_tags = trail.tags
report.status = "FAIL"
if trail.is_multiregion:
report.status_extended = (
f"Multiregion trail {trail.name} log file validation disabled."
)
else:
report.status_extended = f"Single region trail {trail.name} log file validation disabled."
if trail.log_file_validation_enabled:
report.status = "PASS"
if trail.is_multiregion:
report.status_extended = f"Multiregion trail {trail.name} log file validation disabled."
report.status_extended = f"Multiregion trail {trail.name} log file validation enabled."
else:
report.status_extended = f"Single region trail {trail.name} log file validation disabled."
if trail.log_file_validation_enabled:
report.status = "PASS"
if trail.is_multiregion:
report.status_extended = f"Multiregion trail {trail.name} log file validation enabled."
else:
report.status_extended = f"Single region trail {trail.name} log file validation enabled."
findings.append(report)
report.status_extended = f"Single region trail {trail.name} log file validation enabled."
findings.append(report)
return findings

View File

@@ -13,17 +13,17 @@
"Severity": "medium",
"ResourceType": "AwsCloudTrailTrail",
"Description": "Ensure S3 bucket access logging is enabled on the CloudTrail S3 bucket",
"Risk": "Server access logs can assist you in security and access audits, help you learn about your customer base, and understand your Amazon S3 bill.",
"Risk": "Server access logs can assist you in security and access audits; help you learn about your customer base; and understand your Amazon S3 bill.",
"RelatedUrl": "",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "https://docs.prowler.com/checks/aws/logging-policies/logging_6#aws-console",
"Other": "https://docs.bridgecrew.io/docs/logging_6#aws-console",
"Terraform": ""
},
"Recommendation": {
"Text": "Ensure that S3 buckets have Logging enabled. CloudTrail data events can be used in place of S3 bucket logging. If that is the case, this finding can be considered a false positive.",
"Text": "Ensure that S3 buckets have Logging enabled. CloudTrail data events can be used in place of S3 bucket logging. If that is the case; this finding can be considered a false positive.",
"Url": "https://docs.aws.amazon.com/AmazonS3/latest/dev/security-best-practices.html"
}
},

View File

@@ -8,36 +8,35 @@ from prowler.providers.aws.services.s3.s3_client import s3_client
class cloudtrail_logs_s3_bucket_access_logging_enabled(Check):
def execute(self):
findings = []
if cloudtrail_client.trails is not None:
for trail in cloudtrail_client.trails.values():
if trail.name:
trail_bucket_is_in_account = False
trail_bucket = trail.s3_bucket
report = Check_Report_AWS(self.metadata())
report.region = trail.home_region
report.resource_id = trail.name
report.resource_arn = trail.arn
report.resource_tags = trail.tags
report.status = "FAIL"
if trail.is_multiregion:
report.status_extended = f"Multiregion Trail {trail.name} S3 bucket access logging is not enabled for bucket {trail_bucket}."
else:
report.status_extended = f"Single region Trail {trail.name} S3 bucket access logging is not enabled for bucket {trail_bucket}."
for bucket in s3_client.buckets:
if trail_bucket == bucket.name:
trail_bucket_is_in_account = True
if bucket.logging:
report.status = "PASS"
if trail.is_multiregion:
report.status_extended = f"Multiregion trail {trail.name} S3 bucket access logging is enabled for bucket {trail_bucket}."
else:
report.status_extended = f"Single region trail {trail.name} S3 bucket access logging is enabled for bucket {trail_bucket}."
break
for trail in cloudtrail_client.trails.values():
if trail.name:
trail_bucket_is_in_account = False
trail_bucket = trail.s3_bucket
report = Check_Report_AWS(self.metadata())
report.region = trail.region
report.resource_id = trail.name
report.resource_arn = trail.arn
report.resource_tags = trail.tags
report.status = "FAIL"
if trail.is_multiregion:
report.status_extended = f"Multiregion Trail {trail.name} S3 bucket access logging is not enabled for bucket {trail_bucket}."
else:
report.status_extended = f"Single region Trail {trail.name} S3 bucket access logging is not enabled for bucket {trail_bucket}."
for bucket in s3_client.buckets:
if trail_bucket == bucket.name:
trail_bucket_is_in_account = True
if bucket.logging:
report.status = "PASS"
if trail.is_multiregion:
report.status_extended = f"Multiregion trail {trail.name} S3 bucket access logging is enabled for bucket {trail_bucket}."
else:
report.status_extended = f"Single region trail {trail.name} S3 bucket access logging is enabled for bucket {trail_bucket}."
break
# check if trail is delivering logs in a cross account bucket
if not trail_bucket_is_in_account:
report.status = "INFO"
report.status_extended = f"Trail {trail.name} is delivering logs in a cross-account bucket {trail_bucket} in another account out of Prowler's permissions scope, please check it manually."
findings.append(report)
# check if trail is delivering logs in a cross account bucket
if not trail_bucket_is_in_account:
report.status = "INFO"
report.status_extended = f"Trail {trail.name} is delivering logs in a cross-account bucket {trail_bucket} in another account out of Prowler's permissions scope, please check it manually."
findings.append(report)
return findings

View File

@@ -19,7 +19,7 @@
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "https://docs.prowler.com/checks/aws/logging-policies/logging_3#aws-console",
"Other": "https://docs.bridgecrew.io/docs/logging_3#aws-console",
"Terraform": ""
},
"Recommendation": {

View File

@@ -8,42 +8,41 @@ from prowler.providers.aws.services.s3.s3_client import s3_client
class cloudtrail_logs_s3_bucket_is_not_publicly_accessible(Check):
def execute(self):
findings = []
if cloudtrail_client.trails is not None:
for trail in cloudtrail_client.trails.values():
if trail.name:
trail_bucket_is_in_account = False
trail_bucket = trail.s3_bucket
report = Check_Report_AWS(self.metadata())
report.region = trail.home_region
report.resource_id = trail.name
report.resource_arn = trail.arn
report.resource_tags = trail.tags
report.status = "PASS"
if trail.is_multiregion:
report.status_extended = f"S3 Bucket {trail_bucket} from multiregion trail {trail.name} is not publicly accessible."
else:
report.status_extended = f"S3 Bucket {trail_bucket} from single region trail {trail.name} is not publicly accessible."
for bucket in s3_client.buckets:
# Here we need to ensure that acl_grantee is filled since if we don't have permissions to query the api for a concrete region
# (for example due to a SCP) we are going to try access an attribute from a None type
if trail_bucket == bucket.name:
trail_bucket_is_in_account = True
if bucket.acl_grantees:
for grant in bucket.acl_grantees:
if (
grant.URI
== "http://acs.amazonaws.com/groups/global/AllUsers"
):
report.status = "FAIL"
if trail.is_multiregion:
report.status_extended = f"S3 Bucket {trail_bucket} from multiregion trail {trail.name} is publicly accessible."
else:
report.status_extended = f"S3 Bucket {trail_bucket} from single region trail {trail.name} is publicly accessible."
break
# check if trail bucket is a cross account bucket
if not trail_bucket_is_in_account:
report.status = "INFO"
report.status_extended = f"Trail {trail.name} bucket ({trail_bucket}) is a cross-account bucket in another account out of Prowler's permissions scope, please check it manually."
findings.append(report)
for trail in cloudtrail_client.trails.values():
if trail.name:
trail_bucket_is_in_account = False
trail_bucket = trail.s3_bucket
report = Check_Report_AWS(self.metadata())
report.region = trail.region
report.resource_id = trail.name
report.resource_arn = trail.arn
report.resource_tags = trail.tags
report.status = "PASS"
if trail.is_multiregion:
report.status_extended = f"S3 Bucket {trail_bucket} from multiregion trail {trail.name} is not publicly accessible."
else:
report.status_extended = f"S3 Bucket {trail_bucket} from single region trail {trail.name} is not publicly accessible."
for bucket in s3_client.buckets:
# Here we need to ensure that acl_grantee is filled since if we don't have permissions to query the api for a concrete region
# (for example due to a SCP) we are going to try access an attribute from a None type
if trail_bucket == bucket.name:
trail_bucket_is_in_account = True
if bucket.acl_grantees:
for grant in bucket.acl_grantees:
if (
grant.URI
== "http://acs.amazonaws.com/groups/global/AllUsers"
):
report.status = "FAIL"
if trail.is_multiregion:
report.status_extended = f"S3 Bucket {trail_bucket} from multiregion trail {trail.name} is publicly accessible."
else:
report.status_extended = f"S3 Bucket {trail_bucket} from single region trail {trail.name} is publicly accessible."
break
# check if trail bucket is a cross account bucket
if not trail_bucket_is_in_account:
report.status = "INFO"
report.status_extended = f"Trail {trail.name} bucket ({trail_bucket}) is a cross-account bucket in another account out of Prowler's permissions scope, please check it manually."
findings.append(report)
return findings

View File

@@ -13,14 +13,14 @@
"Severity": "high",
"ResourceType": "AwsCloudTrailTrail",
"Description": "Ensure CloudTrail is enabled in all regions",
"Risk": "AWS CloudTrail is a web service that records AWS API calls for your account and delivers log files to you. The recorded information includes the identity of the API caller, the time of the API call, the source IP address of the API caller, the request parameters, and the response elements returned by the AWS service.",
"Risk": "AWS CloudTrail is a web service that records AWS API calls for your account and delivers log files to you. The recorded information includes the identity of the API caller; the time of the API call; the source IP address of the API caller; the request parameters; and the response elements returned by the AWS service.",
"RelatedUrl": "",
"Remediation": {
"Code": {
"CLI": "aws cloudtrail create-trail --name <trail_name> --bucket-name <s3_bucket_for_cloudtrail> --is-multi-region-trail aws cloudtrail update-trail --name <trail_name> --is-multi-region-trail ",
"NativeIaC": "https://docs.prowler.com/checks/aws/logging-policies/logging_1#cloudformation",
"Other": "https://docs.prowler.com/checks/aws/logging-policies/logging_1#aws-console",
"Terraform": "https://docs.prowler.com/checks/aws/logging-policies/logging_1#terraform"
"NativeIaC": "https://docs.bridgecrew.io/docs/logging_1#cloudformation",
"Other": "https://docs.bridgecrew.io/docs/logging_1#aws-console",
"Terraform": "https://docs.bridgecrew.io/docs/logging_1#terraform"
},
"Recommendation": {
"Text": "Ensure Logging is set to ON on all regions (even if they are not being used at the moment.",

View File

@@ -7,35 +7,36 @@ from prowler.providers.aws.services.cloudtrail.cloudtrail_client import (
class cloudtrail_multi_region_enabled(Check):
def execute(self):
findings = []
if cloudtrail_client.trails is not None:
for region in cloudtrail_client.regional_clients.keys():
report = Check_Report_AWS(self.metadata())
report.region = region
for trail in cloudtrail_client.trails.values():
if trail.region == region or trail.is_multiregion:
if trail.is_logging:
report.status = "PASS"
report.resource_id = trail.name
report.resource_arn = trail.arn
report.resource_tags = trail.tags
if trail.is_multiregion:
report.status_extended = f"Trail {trail.name} is multiregion and it is logging."
else:
report.status_extended = f"Trail {trail.name} is not multiregion and it is logging."
# Since there exists a logging trail in that region there is no point in checking the remaining trails
# Store the finding and exit the loop
findings.append(report)
break
else:
report.status = "FAIL"
for region in cloudtrail_client.regional_clients.keys():
report = Check_Report_AWS(self.metadata())
report.region = region
for trail in cloudtrail_client.trails.values():
if trail.region == region or trail.is_multiregion:
if trail.is_logging:
report.status = "PASS"
report.resource_id = trail.name
report.resource_arn = trail.arn
report.resource_tags = trail.tags
if trail.is_multiregion:
report.status_extended = (
"No CloudTrail trails enabled and logging were found."
f"Trail {trail.name} is multiregion and it is logging."
)
report.resource_arn = (
cloudtrail_client.__get_trail_arn_template__(region)
)
report.resource_id = cloudtrail_client.audited_account
# If there are no trails logging it is needed to store the FAIL once all the trails have been checked
if report.status == "FAIL":
findings.append(report)
else:
report.status_extended = f"Trail {trail.name} is not multiregion and it is logging."
# Since there exists a logging trail in that region there is no point in checking the remaining trails
# Store the finding and exit the loop
findings.append(report)
break
else:
report.status = "FAIL"
report.status_extended = (
"No CloudTrail trails enabled and logging were found."
)
report.resource_arn = (
cloudtrail_client.__get_trail_arn_template__(region)
)
report.resource_id = cloudtrail_client.audited_account
# If there are no trails logging it is needed to store the FAIL once all the trails have been checked
if report.status == "FAIL":
findings.append(report)
return findings

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