mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 21:11:53 +00:00
chore(merge): update v4 with latest changes of v3 (#3653)
This commit is contained in:
@@ -3,6 +3,8 @@ name: build-lint-push-containers
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
# TODO: update it for v3 and v4
|
||||
# - "v3"
|
||||
- "master"
|
||||
paths-ignore:
|
||||
- ".github/**"
|
||||
@@ -13,15 +15,27 @@ 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
|
||||
TEMPORARY_TAG: temporary
|
||||
DOCKERFILE_PATH: ./Dockerfile
|
||||
PYTHON_VERSION: 3.9
|
||||
# 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.11
|
||||
|
||||
jobs:
|
||||
# Build Prowler OSS container
|
||||
@@ -30,26 +44,57 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
POETRY_VIRTUALENVS_CREATE: "false"
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup python (release)
|
||||
if: github.event_name == 'release'
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Install dependencies (release)
|
||||
if: github.event_name == 'release'
|
||||
- name: Install Poetry
|
||||
run: |
|
||||
pipx install poetry
|
||||
pipx inject poetry poetry-bumpversion
|
||||
|
||||
- name: 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}"
|
||||
|
||||
case ${PROWLER_VERSION_MAJOR} in
|
||||
3)
|
||||
# TODO: update it for v3 and v4
|
||||
# echo "LATEST=v3-latest" >> "${GITHUB_ENV}"
|
||||
# echo "STABLE_TAG=v3-stable" >> "${GITHUB_ENV}"
|
||||
echo "LATEST=latest" >> "${GITHUB_ENV}"
|
||||
echo "STABLE_TAG=stable" >> "${GITHUB_ENV}"
|
||||
;;
|
||||
|
||||
# TODO: uncomment for v3 and v4
|
||||
# 4)
|
||||
# echo "LATEST=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)
|
||||
if: github.event_name == 'release'
|
||||
run: |
|
||||
poetry version ${{ github.event.release.tag_name }}
|
||||
PROWLER_VERSION="${{ github.event.release.tag_name }}"
|
||||
poetry version "${PROWLER_VERSION}"
|
||||
echo "PROWLER_VERSION="${PROWLER_VERSION}" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
@@ -90,9 +135,9 @@ jobs:
|
||||
context: .
|
||||
push: true
|
||||
tags: |
|
||||
${{ secrets.DOCKER_HUB_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ github.event.release.tag_name }}
|
||||
${{ secrets.DOCKER_HUB_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ env.PROWLER_VERSION }}
|
||||
${{ secrets.DOCKER_HUB_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ env.STABLE_TAG }}
|
||||
${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ github.event.release.tag_name }}
|
||||
${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ env.PROWLER_VERSION }}
|
||||
${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ env.STABLE_TAG }}
|
||||
file: ${{ env.DOCKERFILE_PATH }}
|
||||
cache-from: type=gha
|
||||
@@ -102,16 +147,26 @@ jobs:
|
||||
needs: container-build-push
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get latest commit info
|
||||
- name: Get latest commit info (latest)
|
||||
if: github.event_name == 'push'
|
||||
run: |
|
||||
LATEST_COMMIT_HASH=$(echo ${{ github.event.after }} | cut -b -7)
|
||||
echo "LATEST_COMMIT_HASH=${LATEST_COMMIT_HASH}" >> $GITHUB_ENV
|
||||
- name: Dispatch event for latest
|
||||
if: github.event_name == 'push'
|
||||
|
||||
- name: Dispatch event (latest)
|
||||
if: github.event_name == 'push' && ${{ env. PROWLER_VERSION_MAJOR }} == '3'
|
||||
run: |
|
||||
curl https://api.github.com/repos/${{ secrets.DISPATCH_OWNER }}/${{ secrets.DISPATCH_REPO }}/dispatches -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.ACCESS_TOKEN }}" -H "X-GitHub-Api-Version: 2022-11-28" --data '{"event_type":"dispatch","client_payload":{"version":"latest", "tag": "${{ env.LATEST_COMMIT_HASH }}"}}'
|
||||
- name: Dispatch event for release
|
||||
if: github.event_name == 'release'
|
||||
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 (release)
|
||||
if: github.event_name == 'release' && ${{ env. PROWLER_VERSION_MAJOR }} == '3'
|
||||
run: |
|
||||
curl https://api.github.com/repos/${{ secrets.DISPATCH_OWNER }}/${{ secrets.DISPATCH_REPO }}/dispatches -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.ACCESS_TOKEN }}" -H "X-GitHub-Api-Version: 2022-11-28" --data '{"event_type":"dispatch","client_payload":{"version":"release", "tag":"${{ github.event.release.tag_name }}"}}'
|
||||
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":"${{ env.PROWLER_VERSION }}"}}'
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: TruffleHog OSS
|
||||
uses: trufflesecurity/trufflehog@v3.71.0
|
||||
uses: trufflesecurity/trufflehog@v3.71.2
|
||||
with:
|
||||
path: ./
|
||||
base: ${{ github.event.repository.default_branch }}
|
||||
|
||||
@@ -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@v43
|
||||
uses: tj-actions/changed-files@v44
|
||||
with:
|
||||
files: ./**
|
||||
files_ignore: |
|
||||
|
||||
@@ -8,10 +8,7 @@ env:
|
||||
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
||||
PYTHON_VERSION: 3.11
|
||||
CACHE: "poetry"
|
||||
# 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"
|
||||
# TODO: create a bot user for this kind of tasks, like prowler-bot
|
||||
GIT_COMMITTER_EMAIL: "sergio@prowler.com"
|
||||
|
||||
jobs:
|
||||
@@ -21,24 +18,16 @@ jobs:
|
||||
POETRY_VIRTUALENVS_CREATE: "false"
|
||||
name: Release Prowler to PyPI
|
||||
steps:
|
||||
- name: Get base branch regarding Prowler version
|
||||
- name: Get Prowler version
|
||||
run: |
|
||||
PROWLER_VERSION="${{ env.RELEASE_TAG }}"
|
||||
BASE_BRANCH=""
|
||||
|
||||
case ${PROWLER_VERSION%%.*} in
|
||||
3)
|
||||
echo "Releasing Prowler v3 with tag ${PROWLER_VERSION}"
|
||||
# TODO: modify it once v4 is ready
|
||||
# echo "BASE_BRANCH=3.0-dev" >> "${GITHUB_ENV}"
|
||||
echo "BASE_BRANCH=master" >> "${GITHUB_ENV}"
|
||||
;;
|
||||
4)
|
||||
echo "Releasing Prowler v4 with tag ${PROWLER_VERSION}"
|
||||
# TODO: modify it once v4 is ready
|
||||
# echo "BASE_BRANCH=master" >> "${GITHUB_ENV}"
|
||||
echo "Not available, aborting..."
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
echo "Releasing another Prowler major version, aborting..."
|
||||
@@ -87,11 +76,6 @@ 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
|
||||
@@ -101,23 +85,6 @@ 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.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
|
||||
|
||||
@@ -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 | 109 | 16 -> `prowler azure --list-services` | CIS soon | 2 -> `prowler azure --list-categories` |
|
||||
| Azure | 126 | 16 -> `prowler azure --list-services` | 2 -> `prowler azure --list-compliance` | 2 -> `prowler azure --list-categories` |
|
||||
| Kubernetes | 83 | 7 -> `prowler kubernetes --list-services` | 1 -> `prowler kubernetes --list-compliance` | 7 -> `prowler kubernetes --list-categories` |
|
||||
|
||||
# 📖 Documentation
|
||||
|
||||
@@ -64,16 +64,17 @@ 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:
|
||||
|
||||
- **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)
|
||||
- **Microsoft Entra ID permissions**: Used to retrieve metadata from the identity assumed by Prowler (not mandatory to have access to execute the tool).
|
||||
- **Subscription scope permissions**: Required to launch the checks against your resources, mandatory to launch the tool.
|
||||
|
||||
|
||||
#### Azure Active Directory scope
|
||||
#### Microsoft Entra ID 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:
|
||||
|
||||
@@ -86,9 +87,10 @@ 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" and "Policy" and select the following permissions:
|
||||
8. Finally, search for "Directory", "Policy" and "UserAuthenticationMethod" select the following permissions:
|
||||
- `Directory.Read.All`
|
||||
- `Policy.Read.All`
|
||||
- `UserAuthenticationMethod.Read.All`
|
||||

|
||||
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 358 KiB After Width: | Height: | Size: 376 KiB |
@@ -30,6 +30,8 @@ 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`
|
||||
- `cis_1.8_kubernetes`
|
||||
- `cisa_aws`
|
||||
|
||||
Generated
+70
-31
@@ -153,6 +153,17 @@ files = [
|
||||
about-time = "4.2.1"
|
||||
grapheme = "0.6.0"
|
||||
|
||||
[[package]]
|
||||
name = "antlr4-python3-runtime"
|
||||
version = "4.13.1"
|
||||
description = "ANTLR 4.13.1 runtime for Python 3"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "antlr4-python3-runtime-4.13.1.tar.gz", hash = "sha256:3cd282f5ea7cfb841537fe01f143350fdb1c0b1ce7981443a2fa8513fddb6d1a"},
|
||||
{file = "antlr4_python3_runtime-4.13.1-py3-none-any.whl", hash = "sha256:78ec57aad12c97ac039ca27403ad61cb98aaec8a3f9bb8144f889aa0fa28b943"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.3.0"
|
||||
@@ -1598,20 +1609,20 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"]
|
||||
|
||||
[[package]]
|
||||
name = "google-api-python-client"
|
||||
version = "2.123.0"
|
||||
version = "2.124.0"
|
||||
description = "Google API Client Library for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "google-api-python-client-2.123.0.tar.gz", hash = "sha256:a17226b02f71de581afe045437b441844110a9cd91580b73549d41108cf1b9f0"},
|
||||
{file = "google_api_python_client-2.123.0-py2.py3-none-any.whl", hash = "sha256:1c2bcaa846acf5bac4d6f244d8373d4de9de73d64eb6e77b56767ab4cf681419"},
|
||||
{file = "google-api-python-client-2.124.0.tar.gz", hash = "sha256:f6d3258420f7c76b0f5266b5e402e6f804e30351b018a10083f4a46c3ec33773"},
|
||||
{file = "google_api_python_client-2.124.0-py2.py3-none-any.whl", hash = "sha256:07dc674449ed353704b1169fdee792f74438d024261dad71b6ce7bb9c683d51f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
google-api-core = ">=1.31.5,<2.0.dev0 || >2.3.0,<3.0.0.dev0"
|
||||
google-auth = ">=1.19.0,<3.0.0.dev0"
|
||||
google-auth-httplib2 = ">=0.1.0"
|
||||
httplib2 = ">=0.15.0,<1.dev0"
|
||||
google-auth = ">=1.32.0,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0.dev0"
|
||||
google-auth-httplib2 = ">=0.2.0,<1.0.0"
|
||||
httplib2 = ">=0.19.0,<1.dev0"
|
||||
uritemplate = ">=3.0.1,<5"
|
||||
|
||||
[[package]]
|
||||
@@ -1967,6 +1978,20 @@ files = [
|
||||
[package.dependencies]
|
||||
jsonpointer = ">=1.9"
|
||||
|
||||
[[package]]
|
||||
name = "jsonpath-ng"
|
||||
version = "1.6.1"
|
||||
description = "A final implementation of JSONPath for Python that aims to be standard compliant, including arithmetic and binary comparison operators and providing clear AST for metaprogramming."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "jsonpath-ng-1.6.1.tar.gz", hash = "sha256:086c37ba4917304850bd837aeab806670224d3f038fe2833ff593a672ef0a5fa"},
|
||||
{file = "jsonpath_ng-1.6.1-py3-none-any.whl", hash = "sha256:8f22cd8273d7772eea9aaa84d922e0841aa36fdb8a2c6b7f6c3791a16a9bc0be"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
ply = "*"
|
||||
|
||||
[[package]]
|
||||
name = "jsonpickle"
|
||||
version = "3.0.3"
|
||||
@@ -2425,13 +2450,13 @@ pytz = "*"
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-material"
|
||||
version = "9.5.15"
|
||||
version = "9.5.17"
|
||||
description = "Documentation that simply works"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "mkdocs_material-9.5.15-py3-none-any.whl", hash = "sha256:e5c96dec3d19491de49ca643fc1dbb92b278e43cdb816c775bc47db77d9b62fb"},
|
||||
{file = "mkdocs_material-9.5.15.tar.gz", hash = "sha256:39f03cca45e82bf54eb7456b5a18bd252eabfdd67f237a229471484a0a4d4635"},
|
||||
{file = "mkdocs_material-9.5.17-py3-none-any.whl", hash = "sha256:14a2a60119a785e70e765dd033e6211367aca9fc70230e577c1cf6a326949571"},
|
||||
{file = "mkdocs_material-9.5.17.tar.gz", hash = "sha256:06ae1275a72db1989cf6209de9e9ecdfbcfdbc24c58353877b2bb927dbe413e4"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2481,16 +2506,17 @@ test = ["pytest", "pytest-cov"]
|
||||
|
||||
[[package]]
|
||||
name = "moto"
|
||||
version = "5.0.3"
|
||||
version = "5.0.4"
|
||||
description = ""
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "moto-5.0.3-py2.py3-none-any.whl", hash = "sha256:261d312d1d69c2afccb450a0566666d7b75d76ed6a7d00aac278a9633b073ff0"},
|
||||
{file = "moto-5.0.3.tar.gz", hash = "sha256:070ac2edf89ad7aee28534481ce68e2f344c8a6a8fefec5427eea0d599bfdbdb"},
|
||||
{file = "moto-5.0.4-py2.py3-none-any.whl", hash = "sha256:4054360b882b6e7bab25d52d057e196b978b8d15f1921333f534c4d8f6510bbb"},
|
||||
{file = "moto-5.0.4.tar.gz", hash = "sha256:8d19125d40c919cb40df62f4576904c2647c4e9a0e1ebc42491dd7787d09e107"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
antlr4-python3-runtime = {version = "*", optional = true, markers = "extra == \"all\""}
|
||||
aws-xray-sdk = {version = ">=0.93,<0.96 || >0.96", optional = true, markers = "extra == \"all\""}
|
||||
boto3 = ">=1.9.201"
|
||||
botocore = ">=1.14.0"
|
||||
@@ -2501,9 +2527,10 @@ graphql-core = {version = "*", optional = true, markers = "extra == \"all\""}
|
||||
Jinja2 = ">=2.10.1"
|
||||
joserfc = {version = ">=0.9.0", optional = true, markers = "extra == \"all\""}
|
||||
jsondiff = {version = ">=1.1.2", optional = true, markers = "extra == \"all\""}
|
||||
jsonpath-ng = {version = "*", optional = true, markers = "extra == \"all\""}
|
||||
multipart = {version = "*", optional = true, markers = "extra == \"all\""}
|
||||
openapi-spec-validator = {version = ">=0.5.0", optional = true, markers = "extra == \"all\""}
|
||||
py-partiql-parser = {version = "0.5.1", optional = true, markers = "extra == \"all\""}
|
||||
py-partiql-parser = {version = "0.5.2", optional = true, markers = "extra == \"all\""}
|
||||
pyparsing = {version = ">=3.0.7", optional = true, markers = "extra == \"all\""}
|
||||
python-dateutil = ">=2.1,<3.0.0"
|
||||
PyYAML = {version = ">=5.1", optional = true, markers = "extra == \"all\""}
|
||||
@@ -2514,24 +2541,25 @@ werkzeug = ">=0.5,<2.2.0 || >2.2.0,<2.2.1 || >2.2.1"
|
||||
xmltodict = "*"
|
||||
|
||||
[package.extras]
|
||||
all = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "graphql-core", "joserfc (>=0.9.0)", "jsondiff (>=1.1.2)", "multipart", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.1)", "pyparsing (>=3.0.7)", "setuptools"]
|
||||
all = ["PyYAML (>=5.1)", "antlr4-python3-runtime", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "graphql-core", "joserfc (>=0.9.0)", "jsondiff (>=1.1.2)", "jsonpath-ng", "multipart", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.2)", "pyparsing (>=3.0.7)", "setuptools"]
|
||||
apigateway = ["PyYAML (>=5.1)", "joserfc (>=0.9.0)", "openapi-spec-validator (>=0.5.0)"]
|
||||
apigatewayv2 = ["PyYAML (>=5.1)", "openapi-spec-validator (>=0.5.0)"]
|
||||
appsync = ["graphql-core"]
|
||||
awslambda = ["docker (>=3.0.0)"]
|
||||
batch = ["docker (>=3.0.0)"]
|
||||
cloudformation = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "graphql-core", "joserfc (>=0.9.0)", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.1)", "pyparsing (>=3.0.7)", "setuptools"]
|
||||
cloudformation = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "graphql-core", "joserfc (>=0.9.0)", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.2)", "pyparsing (>=3.0.7)", "setuptools"]
|
||||
cognitoidp = ["joserfc (>=0.9.0)"]
|
||||
dynamodb = ["docker (>=3.0.0)", "py-partiql-parser (==0.5.1)"]
|
||||
dynamodbstreams = ["docker (>=3.0.0)", "py-partiql-parser (==0.5.1)"]
|
||||
dynamodb = ["docker (>=3.0.0)", "py-partiql-parser (==0.5.2)"]
|
||||
dynamodbstreams = ["docker (>=3.0.0)", "py-partiql-parser (==0.5.2)"]
|
||||
glue = ["pyparsing (>=3.0.7)"]
|
||||
iotdata = ["jsondiff (>=1.1.2)"]
|
||||
proxy = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=2.5.1)", "graphql-core", "joserfc (>=0.9.0)", "jsondiff (>=1.1.2)", "multipart", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.1)", "pyparsing (>=3.0.7)", "setuptools"]
|
||||
resourcegroupstaggingapi = ["PyYAML (>=5.1)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "graphql-core", "joserfc (>=0.9.0)", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.1)", "pyparsing (>=3.0.7)"]
|
||||
s3 = ["PyYAML (>=5.1)", "py-partiql-parser (==0.5.1)"]
|
||||
s3crc32c = ["PyYAML (>=5.1)", "crc32c", "py-partiql-parser (==0.5.1)"]
|
||||
server = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "flask (!=2.2.0,!=2.2.1)", "flask-cors", "graphql-core", "joserfc (>=0.9.0)", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.1)", "pyparsing (>=3.0.7)", "setuptools"]
|
||||
proxy = ["PyYAML (>=5.1)", "antlr4-python3-runtime", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=2.5.1)", "graphql-core", "joserfc (>=0.9.0)", "jsondiff (>=1.1.2)", "jsonpath-ng", "multipart", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.2)", "pyparsing (>=3.0.7)", "setuptools"]
|
||||
resourcegroupstaggingapi = ["PyYAML (>=5.1)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "graphql-core", "joserfc (>=0.9.0)", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.2)", "pyparsing (>=3.0.7)"]
|
||||
s3 = ["PyYAML (>=5.1)", "py-partiql-parser (==0.5.2)"]
|
||||
s3crc32c = ["PyYAML (>=5.1)", "crc32c", "py-partiql-parser (==0.5.2)"]
|
||||
server = ["PyYAML (>=5.1)", "antlr4-python3-runtime", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "flask (!=2.2.0,!=2.2.1)", "flask-cors", "graphql-core", "joserfc (>=0.9.0)", "jsondiff (>=1.1.2)", "jsonpath-ng", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.2)", "pyparsing (>=3.0.7)", "setuptools"]
|
||||
ssm = ["PyYAML (>=5.1)"]
|
||||
stepfunctions = ["antlr4-python3-runtime", "jsonpath-ng"]
|
||||
xray = ["aws-xray-sdk (>=0.93,!=0.96)", "setuptools"]
|
||||
|
||||
[[package]]
|
||||
@@ -3226,6 +3254,17 @@ files = [
|
||||
dev = ["pre-commit", "tox"]
|
||||
testing = ["pytest", "pytest-benchmark"]
|
||||
|
||||
[[package]]
|
||||
name = "ply"
|
||||
version = "3.11"
|
||||
description = "Python Lex & Yacc"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce"},
|
||||
{file = "ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portalocker"
|
||||
version = "2.8.2"
|
||||
@@ -3282,13 +3321,13 @@ pydantic = "1.10.14"
|
||||
|
||||
[[package]]
|
||||
name = "py-partiql-parser"
|
||||
version = "0.5.1"
|
||||
version = "0.5.2"
|
||||
description = "Pure Python PartiQL Parser"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "py-partiql-parser-0.5.1.tar.gz", hash = "sha256:aeac8f46529d8651bbae88a1a6c14dc3aa38ebc4bc6bd1eb975044c0564246c6"},
|
||||
{file = "py_partiql_parser-0.5.1-py3-none-any.whl", hash = "sha256:53053e70987dea2983e1990ad85f87a7d8cec13dd4a4b065a740bcfd661f5a6b"},
|
||||
{file = "py-partiql-parser-0.5.2.tar.gz", hash = "sha256:bdec65fe17d6093c05e9bc1742a99a041ef810b50a71cc0d9e74a88218d938cf"},
|
||||
{file = "py_partiql_parser-0.5.2-py3-none-any.whl", hash = "sha256:9c79b59bbe0cb50daa8090020f2e7f3e5a0e33f7846b48924f19a8f7704f4877"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -4158,13 +4197,13 @@ crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "safety"
|
||||
version = "3.0.1"
|
||||
version = "3.1.0"
|
||||
description = "Checks installed dependencies for known vulnerabilities and licenses."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "safety-3.0.1-py3-none-any.whl", hash = "sha256:1ed058bc4bef132b974e58d7fcad020fb897cd255328016f8a5a194b94ca91d2"},
|
||||
{file = "safety-3.0.1.tar.gz", hash = "sha256:1f2000f03652f3a0bfc67f8fd1e98bc5723ccb76e15cb1bdd68545c3d803df01"},
|
||||
{file = "safety-3.1.0-py3-none-any.whl", hash = "sha256:f2ba2d36f15ac1e24751547a73b854509a7d6db31efd30b57f64ffdf9d021934"},
|
||||
{file = "safety-3.1.0.tar.gz", hash = "sha256:71f47b82ece153ec2f240e277f7cbfa70d5da2e0d143162c67f63b2f7459a1aa"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -4174,11 +4213,11 @@ dparse = ">=0.6.4b0"
|
||||
jinja2 = ">=3.1.0"
|
||||
marshmallow = ">=3.15.0"
|
||||
packaging = ">=21.0"
|
||||
pydantic = ">=1.10.12,<2.0"
|
||||
pydantic = ">=1.10.12"
|
||||
requests = "*"
|
||||
rich = "*"
|
||||
"ruamel.yaml" = ">=0.17.21"
|
||||
safety-schemas = ">=0.0.1"
|
||||
safety-schemas = ">=0.0.2"
|
||||
setuptools = ">=65.5.1"
|
||||
typer = "*"
|
||||
typing-extensions = ">=4.7.1"
|
||||
@@ -4806,4 +4845,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.9,<3.13"
|
||||
content-hash = "e414b353bd5fb303fc5b109fe908c3ac556e507c01dcc789b85aa7f8f9463fa0"
|
||||
content-hash = "cd517148e98cb2d0e98f86a3aa335e5f2a718ec7a32e6ad907bbcf00efe7957a"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -87,6 +87,7 @@ class CIS_Requirement_Attribute(BaseModel):
|
||||
RemediationProcedure: str
|
||||
AuditProcedure: str
|
||||
AdditionalInformation: str
|
||||
DefaultValue: Optional[str]
|
||||
References: str
|
||||
DefaultValue: Optional[str]
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.lib.outputs.compliance.cis_aws import generate_compliance_row_cis_aws
|
||||
from prowler.lib.outputs.compliance.cis_azure import generate_compliance_row_cis_azure
|
||||
from prowler.lib.outputs.compliance.cis_gcp import generate_compliance_row_cis_gcp
|
||||
from prowler.lib.outputs.compliance.cis_kubernetes import (
|
||||
generate_compliance_row_cis_kubernetes,
|
||||
@@ -14,28 +16,17 @@ def write_compliance_row_cis(
|
||||
provider,
|
||||
input_compliance_frameworks,
|
||||
):
|
||||
compliance_output = "cis_" + compliance.Version + "_" + compliance.Provider.lower()
|
||||
try:
|
||||
compliance_output = (
|
||||
"cis_" + compliance.Version + "_" + compliance.Provider.lower()
|
||||
)
|
||||
|
||||
# Only with the version of CIS that was selected
|
||||
if compliance_output in str(input_compliance_frameworks):
|
||||
for requirement in compliance.Requirements:
|
||||
for attribute in requirement.Attributes:
|
||||
if compliance.Provider == "AWS":
|
||||
(compliance_row, csv_header) = generate_compliance_row_cis_aws(
|
||||
finding,
|
||||
compliance,
|
||||
requirement,
|
||||
attribute,
|
||||
output_options,
|
||||
provider,
|
||||
)
|
||||
elif compliance.Provider == "GCP":
|
||||
(compliance_row, csv_header) = generate_compliance_row_cis_gcp(
|
||||
finding, compliance, requirement, attribute, output_options
|
||||
)
|
||||
elif compliance.Provider == "Kubernetes":
|
||||
(compliance_row, csv_header) = (
|
||||
generate_compliance_row_cis_kubernetes(
|
||||
# Only with the version of CIS that was selected
|
||||
if compliance_output in str(input_compliance_frameworks):
|
||||
for requirement in compliance.Requirements:
|
||||
for attribute in requirement.Attributes:
|
||||
if compliance.Provider == "AWS":
|
||||
(compliance_row, csv_header) = generate_compliance_row_cis_aws(
|
||||
finding,
|
||||
compliance,
|
||||
requirement,
|
||||
@@ -43,8 +34,36 @@ def write_compliance_row_cis(
|
||||
output_options,
|
||||
provider,
|
||||
)
|
||||
)
|
||||
elif compliance.Provider == "Azure":
|
||||
(compliance_row, csv_header) = (
|
||||
generate_compliance_row_cis_azure(
|
||||
finding,
|
||||
compliance,
|
||||
requirement,
|
||||
attribute,
|
||||
output_options,
|
||||
)
|
||||
)
|
||||
elif compliance.Provider == "GCP":
|
||||
(compliance_row, csv_header) = generate_compliance_row_cis_gcp(
|
||||
finding, compliance, requirement, attribute, output_options
|
||||
)
|
||||
elif compliance.Provider == "Kubernetes":
|
||||
(compliance_row, csv_header) = (
|
||||
generate_compliance_row_cis_kubernetes(
|
||||
finding,
|
||||
compliance,
|
||||
requirement,
|
||||
attribute,
|
||||
output_options,
|
||||
provider,
|
||||
)
|
||||
)
|
||||
|
||||
write_csv(
|
||||
file_descriptors[compliance_output], csv_header, compliance_row
|
||||
)
|
||||
write_csv(
|
||||
file_descriptors[compliance_output], csv_header, compliance_row
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
from prowler.config.config import timestamp
|
||||
from prowler.lib.outputs.compliance.models import Check_Output_CSV_AZURE_CIS
|
||||
from prowler.lib.outputs.csv.csv import generate_csv_fields
|
||||
from prowler.lib.utils.utils import outputs_unix_timestamp
|
||||
|
||||
|
||||
def generate_compliance_row_cis_azure(
|
||||
finding, compliance, requirement, attribute, output_options
|
||||
):
|
||||
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)
|
||||
|
||||
return compliance_row, csv_header
|
||||
@@ -38,6 +38,7 @@ def add_manual_controls(
|
||||
manual_finding.region = ""
|
||||
manual_finding.location = ""
|
||||
manual_finding.project_id = ""
|
||||
manual_finding.subscription = ""
|
||||
manual_finding.namespace = ""
|
||||
fill_compliance(
|
||||
output_options,
|
||||
|
||||
@@ -84,6 +84,35 @@ class Check_Output_CSV_AWS_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_GCP_CIS(BaseModel):
|
||||
"""
|
||||
Check_Output_CSV_CIS generates a finding's output in CSV CIS format.
|
||||
|
||||
@@ -13,6 +13,7 @@ from prowler.lib.outputs.compliance.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,
|
||||
@@ -107,6 +108,23 @@ def fill_file_descriptors(output_modes, output_directory, output_filename, provi
|
||||
)
|
||||
file_descriptors.update({output_mode: file_descriptor})
|
||||
|
||||
elif provider.type == "azure":
|
||||
filename = f"{output_directory}/compliance/{output_filename}_{output_mode}{csv_file_suffix}"
|
||||
if "cis_" in output_mode:
|
||||
file_descriptor = initialize_file_descriptor(
|
||||
filename,
|
||||
output_mode,
|
||||
Check_Output_CSV_AZURE_CIS,
|
||||
)
|
||||
file_descriptors.update({output_mode: file_descriptor})
|
||||
else:
|
||||
file_descriptor = initialize_file_descriptor(
|
||||
filename,
|
||||
output_mode,
|
||||
Check_Output_CSV_Generic_Compliance,
|
||||
)
|
||||
file_descriptors.update({output_mode: file_descriptor})
|
||||
|
||||
elif provider.type == "aws":
|
||||
if output_mode == "json-asff":
|
||||
filename = f"{output_directory}/{output_filename}{json_asff_file_suffix}"
|
||||
|
||||
@@ -2306,6 +2306,7 @@
|
||||
"ap-southeast-1",
|
||||
"ap-southeast-2",
|
||||
"ap-southeast-3",
|
||||
"ap-southeast-4",
|
||||
"ca-central-1",
|
||||
"eu-central-1",
|
||||
"eu-central-2",
|
||||
@@ -2836,6 +2837,22 @@
|
||||
"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": [
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
from uuid import UUID
|
||||
|
||||
# Service management API
|
||||
WINDOWS_AZURE_SERVICE_MANAGEMENT_API = "797f4846-ba00-4fd7-ba43-dac1f8f63013"
|
||||
|
||||
# Authorization policy roles
|
||||
GUEST_USER_ACCESS_NO_RESTRICTICTED = UUID("a0b1b346-4d3e-4e8b-98f8-753987be4970")
|
||||
GUEST_USER_ACCESS_RESTRICTICTED = UUID("2af84b1e-32c8-42b7-82bc-daa82404023b")
|
||||
|
||||
# General built-in roles
|
||||
CONTRIBUTOR_ROLE_ID = "b24988ac-6180-42a0-ab88-20f7382dd24c"
|
||||
OWNER_ROLE_ID = "8e3af657-a8ff-443c-a75c-2fe8c4bcb635"
|
||||
|
||||
# Compute roles
|
||||
VIRTUAL_MACHINE_CONTRIBUTOR_ROLE_ID = "9980e02c-c2be-4d73-94e8-173b1dc7cf3c"
|
||||
VIRTUAL_MACHINE_ADMINISTRATOR_LOGIN_ROLE_ID = "1c0163c0-47e6-4577-8991-ea5c82e286e4"
|
||||
VIRTUAL_MACHINE_USER_LOGIN_ROLE_ID = "fb879df8-f326-4884-b1cf-06f3ad86be52"
|
||||
VIRTUAL_MACHINE_LOCAL_USER_LOGIN_ROLE_ID = "602da2ba-a5c2-41da-b01d-5360126ab525"
|
||||
WINDOWS_ADMIN_CENTER_ADMINISTRATOR_LOGIN_ROLE_ID = (
|
||||
"a6333a3e-0164-44c3-b281-7a577aff287f"
|
||||
)
|
||||
+1
-1
@@ -1,11 +1,11 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_Azure
|
||||
from prowler.providers.azure.config import WINDOWS_AZURE_SERVICE_MANAGEMENT_API
|
||||
from prowler.providers.azure.services.entra.entra_client import entra_client
|
||||
|
||||
|
||||
class entra_conditional_access_policy_require_mfa_for_management_api(Check):
|
||||
def execute(self) -> Check_Report_Azure:
|
||||
findings = []
|
||||
WINDOWS_AZURE_SERVICE_MANAGEMENT_API = "797f4846-ba00-4fd7-ba43-dac1f8f63013"
|
||||
|
||||
for (
|
||||
tenant_name,
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"Provider": "azure",
|
||||
"CheckID": "entra_users_less_than_five_global_admins",
|
||||
"CheckID": "entra_global_admin_in_less_than_five_users",
|
||||
"CheckTitle": "Ensure fewer than 5 users have global administrator assignment",
|
||||
"CheckType": [],
|
||||
"ServiceName": "entra",
|
||||
+2
-2
@@ -2,14 +2,14 @@ from prowler.lib.check.models import Check, Check_Report_Azure
|
||||
from prowler.providers.azure.services.entra.entra_client import entra_client
|
||||
|
||||
|
||||
class entra_users_less_than_five_global_admins(Check):
|
||||
class entra_global_admin_in_less_than_five_users(Check):
|
||||
def execute(self) -> Check_Report_Azure:
|
||||
findings = []
|
||||
|
||||
for tenant_domain, directory_roles in entra_client.directory_roles.items():
|
||||
report = Check_Report_Azure(self.metadata())
|
||||
report.status = "FAIL"
|
||||
report.subscription = f"Tenant: '{tenant_domain}'"
|
||||
report.subscription = f"Tenant: {tenant_domain}"
|
||||
report.resource_name = "Global Administrator"
|
||||
|
||||
if "Global Administrator" in directory_roles:
|
||||
+4
-2
@@ -20,12 +20,14 @@ class entra_non_privileged_user_has_mfa(Check):
|
||||
report.resource_name = user_domain_name
|
||||
report.resource_id = user.id
|
||||
report.status_extended = (
|
||||
f"User '{user.name}' does not have MFA enabled."
|
||||
f"Non-privileged user {user.name} does not have MFA."
|
||||
)
|
||||
|
||||
if len(user.authentication_methods) > 1:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"User '{user.name}' has MFA enabled."
|
||||
report.status_extended = (
|
||||
f"Non-privileged user {user.name} has MFA."
|
||||
)
|
||||
|
||||
findings.append(report)
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ class entra_policy_default_users_cannot_create_security_groups(Check):
|
||||
|
||||
report = Check_Report_Azure(self.metadata())
|
||||
report.status = "FAIL"
|
||||
report.subscription = f"Tenant: '{tenant_domain}'"
|
||||
report.subscription = f"Tenant: {tenant_domain}"
|
||||
report.resource_name = getattr(auth_policy, "name", "Authorization Policy")
|
||||
report.resource_id = getattr(auth_policy, "id", "authorizationPolicy")
|
||||
report.status_extended = "Non-privileged users are able to create security groups via the Access Panel and the Azure administration portal."
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ class entra_policy_ensure_default_user_cannot_create_apps(Check):
|
||||
|
||||
report = Check_Report_Azure(self.metadata())
|
||||
report.status = "FAIL"
|
||||
report.subscription = f"Tenant: '{tenant_domain}'"
|
||||
report.subscription = f"Tenant: {tenant_domain}"
|
||||
report.resource_name = getattr(auth_policy, "name", "Authorization Policy")
|
||||
report.resource_id = getattr(auth_policy, "id", "authorizationPolicy")
|
||||
report.status_extended = "App creation is not disabled for non-admin users."
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ class entra_policy_ensure_default_user_cannot_create_tenants(Check):
|
||||
for tenant_domain, auth_policy in entra_client.authorization_policy.items():
|
||||
report = Check_Report_Azure(self.metadata())
|
||||
report.status = "FAIL"
|
||||
report.subscription = f"Tenant: '{tenant_domain}'"
|
||||
report.subscription = f"Tenant: {tenant_domain}"
|
||||
report.resource_name = getattr(auth_policy, "name", "Authorization Policy")
|
||||
report.resource_id = getattr(auth_policy, "id", "authorizationPolicy")
|
||||
report.status_extended = (
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ class entra_policy_guest_invite_only_for_admin_roles(Check):
|
||||
for tenant_domain, auth_policy in entra_client.authorization_policy.items():
|
||||
report = Check_Report_Azure(self.metadata())
|
||||
report.status = "FAIL"
|
||||
report.subscription = f"Tenant: '{tenant_domain}'"
|
||||
report.subscription = f"Tenant: {tenant_domain}"
|
||||
report.resource_name = getattr(auth_policy, "name", "Authorization Policy")
|
||||
report.resource_id = getattr(auth_policy, "id", "authorizationPolicy")
|
||||
report.status_extended = "Guest invitations are not restricted to users with specific administrative roles only."
|
||||
|
||||
+2
-4
@@ -1,18 +1,16 @@
|
||||
from uuid import UUID
|
||||
|
||||
from prowler.lib.check.models import Check, Check_Report_Azure
|
||||
from prowler.providers.azure.config import GUEST_USER_ACCESS_RESTRICTICTED
|
||||
from prowler.providers.azure.services.entra.entra_client import entra_client
|
||||
|
||||
|
||||
class entra_policy_guest_users_access_restrictions(Check):
|
||||
def execute(self) -> Check_Report_Azure:
|
||||
GUEST_USER_ACCESS_RESTRICTICTED = UUID("2af84b1e-32c8-42b7-82bc-daa82404023b")
|
||||
findings = []
|
||||
|
||||
for tenant_domain, auth_policy in entra_client.authorization_policy.items():
|
||||
report = Check_Report_Azure(self.metadata())
|
||||
report.status = "FAIL"
|
||||
report.subscription = f"Tenant: '{tenant_domain}'"
|
||||
report.subscription = f"Tenant: {tenant_domain}"
|
||||
report.resource_name = getattr(auth_policy, "name", "Authorization Policy")
|
||||
report.resource_id = getattr(auth_policy, "id", "authorizationPolicy")
|
||||
report.status_extended = "Guest user access is not restricted to properties and memberships of their own directory objects"
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ class entra_policy_restricts_user_consent_for_apps(Check):
|
||||
for tenant_domain, auth_policy in entra_client.authorization_policy.items():
|
||||
report = Check_Report_Azure(self.metadata())
|
||||
report.status = "FAIL"
|
||||
report.subscription = f"Tenant: '{tenant_domain}'"
|
||||
report.subscription = f"Tenant: {tenant_domain}"
|
||||
report.resource_name = getattr(auth_policy, "name", "Authorization Policy")
|
||||
report.resource_id = getattr(auth_policy, "id", "authorizationPolicy")
|
||||
report.status_extended = "Entra allows users to consent apps accessing company data on their behalf"
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ class entra_policy_user_consent_for_verified_apps(Check):
|
||||
for tenant_domain, auth_policy in entra_client.authorization_policy.items():
|
||||
report = Check_Report_Azure(self.metadata())
|
||||
report.status = "PASS"
|
||||
report.subscription = f"Tenant: '{tenant_domain}'"
|
||||
report.subscription = f"Tenant: {tenant_domain}"
|
||||
report.resource_name = getattr(auth_policy, "name", "Authorization Policy")
|
||||
report.resource_id = getattr(auth_policy, "id", "authorizationPolicy")
|
||||
report.status_extended = "Entra does not allow users to consent non-verified apps accessing company data on their behalf."
|
||||
|
||||
+2
-2
@@ -20,12 +20,12 @@ class entra_privileged_user_has_mfa(Check):
|
||||
report.resource_name = user_domain_name
|
||||
report.resource_id = user.id
|
||||
report.status_extended = (
|
||||
f"User '{user.name}' does not have MFA enabled."
|
||||
f"Privileged user {user.name} does not have MFA."
|
||||
)
|
||||
|
||||
if len(user.authentication_methods) > 1:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"User '{user.name}' has MFA enabled."
|
||||
report.status_extended = f"Privileged user {user.name} has MFA."
|
||||
|
||||
findings.append(report)
|
||||
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ class entra_security_defaults_enabled(Check):
|
||||
) in entra_client.security_default.items():
|
||||
report = Check_Report_Azure(self.metadata())
|
||||
report.status = "FAIL"
|
||||
report.subscription = f"Tenant: '{tenant}'"
|
||||
report.subscription = f"Tenant: {tenant}"
|
||||
report.resource_name = getattr(security_default, "name", "Security Default")
|
||||
report.resource_id = getattr(security_default, "id", "Security Default")
|
||||
report.status_extended = "Entra security defaults is diabled."
|
||||
|
||||
@@ -12,6 +12,7 @@ from pydantic import BaseModel
|
||||
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.providers.azure.azure_provider import AzureProvider
|
||||
from prowler.providers.azure.config import GUEST_USER_ACCESS_NO_RESTRICTICTED
|
||||
from prowler.providers.azure.lib.service.service import AzureService
|
||||
|
||||
|
||||
@@ -76,9 +77,7 @@ class Entra(AzureService):
|
||||
|
||||
async def __get_authorization_policy__(self):
|
||||
logger.info("Entra - Getting authorization policy...")
|
||||
GUEST_USER_ACCESS_NO_RESTRICTICTED = UUID(
|
||||
"a0b1b346-4d3e-4e8b-98f8-753987be4970"
|
||||
)
|
||||
|
||||
authorization_policy = {}
|
||||
try:
|
||||
for tenant, client in self.clients.items():
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ class entra_trusted_named_locations_exists(Check):
|
||||
for tenant, named_locations in entra_client.named_locations.items():
|
||||
report = Check_Report_Azure(self.metadata())
|
||||
report.status = "FAIL"
|
||||
report.subscription = f"Tenant: '{tenant}'"
|
||||
report.subscription = f"Tenant: {tenant}"
|
||||
report.resource_name = "Named Locations"
|
||||
report.resource_id = "Named Locations"
|
||||
report.status_extended = (
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"Provider": "azure",
|
||||
"CheckID": "entra_user_with_vm_access_has_mfa",
|
||||
"CheckTitle": "Ensure only MFA enabled identities can access privileged Virtual Machine",
|
||||
"CheckType": [],
|
||||
"ServiceName": "iam",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "#microsoft.graph.users",
|
||||
"Description": "Verify identities without MFA that can log in to a privileged virtual machine using separate login credentials. An adversary can leverage the access to move laterally and perform actions with the virtual machine's managed identity. Make sure the virtual machine only has necessary permissions, and revoke the admin-level permissions according to the least privileges principal",
|
||||
"Risk": "Managed disks are by default encrypted on the underlying hardware, so no additional encryption is required for basic protection. It is available if additional encryption is required. Managed disks are by design more resilient that storage accounts. For ARM-deployed Virtual Machines, Azure Adviser will at some point recommend moving VHDs to managed disks both from a security and cost management perspective.",
|
||||
"RelatedUrl": "",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "1. Log in to the Azure portal. Reducing access of managed identities attached to virtual machines. 2. This can be remediated by enabling MFA for user, Removing user access or • Case I : Enable MFA for users having access on virtual machines. 1. Navigate to Azure AD from the left pane and select Users from the Manage section. 2. Click on Per-User MFA from the top menu options and select each user with MULTI-FACTOR AUTH STATUS as Disabled and can login to virtual machines: From quick steps on the right side select enable. Click on enable multi-factor auth and share the link with the user to setup MFA as required. • Case II : Removing user access on a virtual machine. 1. Select the Subscription, then click on Access control (IAM). 2. Select Role assignments and search for Virtual Machine Administrator Login or Virtual Machine User Login or any role that provides access to log into virtual machines. 3. Click on Role Name, Select Assignments, and remove identities with no MFA configured. • Case III : Reducing access of managed identities attached to virtual machines. 1. Select the Subscription, then click on Access control (IAM). 2. Select Role Assignments from the top menu and apply filters on Assignment type as Privileged administrator roles and Type as Virtual Machines. 3. Click on Role Name, Select Assignments, and remove identities access make sure this follows the least privileges principal.",
|
||||
"Url": ""
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": "This recommendation requires an Azure AD P2 License to implement. Ensure that identities that are provisioned to a virtual machine utilizes an RBAC/ABAC group and is allocated a role using Azure PIM, and the Role settings require MFA or use another PAM solution (like CyberArk) for accessing Virtual Machines."
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_Azure
|
||||
from prowler.providers.azure.config import (
|
||||
CONTRIBUTOR_ROLE_ID,
|
||||
OWNER_ROLE_ID,
|
||||
VIRTUAL_MACHINE_ADMINISTRATOR_LOGIN_ROLE_ID,
|
||||
VIRTUAL_MACHINE_CONTRIBUTOR_ROLE_ID,
|
||||
VIRTUAL_MACHINE_LOCAL_USER_LOGIN_ROLE_ID,
|
||||
VIRTUAL_MACHINE_USER_LOGIN_ROLE_ID,
|
||||
WINDOWS_ADMIN_CENTER_ADMINISTRATOR_LOGIN_ROLE_ID,
|
||||
)
|
||||
from prowler.providers.azure.services.entra.entra_client import entra_client
|
||||
from prowler.providers.azure.services.iam.iam_client import iam_client
|
||||
|
||||
|
||||
class entra_user_with_vm_access_has_mfa(Check):
|
||||
def execute(self) -> Check_Report_Azure:
|
||||
findings = []
|
||||
|
||||
for users in entra_client.users.values():
|
||||
for user_domain_name, user in users.items():
|
||||
for (
|
||||
subscription_name,
|
||||
role_assigns,
|
||||
) in iam_client.role_assignments.items():
|
||||
for assignment in role_assigns.values():
|
||||
if (
|
||||
assignment.agent_type == "User"
|
||||
and assignment.role_id
|
||||
in [
|
||||
CONTRIBUTOR_ROLE_ID,
|
||||
OWNER_ROLE_ID,
|
||||
VIRTUAL_MACHINE_CONTRIBUTOR_ROLE_ID,
|
||||
VIRTUAL_MACHINE_ADMINISTRATOR_LOGIN_ROLE_ID,
|
||||
VIRTUAL_MACHINE_USER_LOGIN_ROLE_ID,
|
||||
VIRTUAL_MACHINE_LOCAL_USER_LOGIN_ROLE_ID,
|
||||
WINDOWS_ADMIN_CENTER_ADMINISTRATOR_LOGIN_ROLE_ID,
|
||||
]
|
||||
and assignment.agent_id == user.id
|
||||
):
|
||||
report = Check_Report_Azure(self.metadata())
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"User {user.name} without MFA can access VMs in subscription {subscription_name}"
|
||||
report.subscription = subscription_name
|
||||
report.resource_name = user_domain_name
|
||||
report.resource_id = user.id
|
||||
|
||||
if len(user.authentication_methods) > 1:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"User {user.name} can access VMs in subscription {subscription_name} but it has MFA."
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
+1
-1
@@ -9,7 +9,7 @@ class entra_users_cannot_create_microsoft_365_groups(Check):
|
||||
for tenant_domain, group_settings in entra_client.group_settings.items():
|
||||
report = Check_Report_Azure(self.metadata())
|
||||
report.status = "FAIL"
|
||||
report.subscription = f"Tenant: '{tenant_domain}'"
|
||||
report.subscription = f"Tenant: {tenant_domain}"
|
||||
report.resource_name = "Microsoft365 Groups"
|
||||
report.resource_id = "Microsoft365 Groups"
|
||||
report.status_extended = "Users can create Microsoft 365 groups."
|
||||
|
||||
@@ -13,6 +13,7 @@ class IAM(AzureService):
|
||||
def __init__(self, provider: AzureProvider):
|
||||
super().__init__(AuthorizationManagementClient, provider)
|
||||
self.roles, self.custom_roles = self.__get_roles__()
|
||||
self.role_assignments = self.__get_role_assignments__()
|
||||
|
||||
def __get_roles__(self):
|
||||
logger.info("IAM - Getting roles...")
|
||||
@@ -53,6 +54,34 @@ class IAM(AzureService):
|
||||
)
|
||||
return builtin_roles, custom_roles
|
||||
|
||||
def __get_role_assignments__(self):
|
||||
logger.info("IAM - Getting role assignments...")
|
||||
role_assignments = {}
|
||||
for subscription, client in self.clients.items():
|
||||
try:
|
||||
role_assignments.update({subscription: {}})
|
||||
all_role_assignments = client.role_assignments.list_for_subscription(
|
||||
filter="atScope()"
|
||||
)
|
||||
for role_assignment in all_role_assignments:
|
||||
role_assignments[subscription].update(
|
||||
{
|
||||
role_assignment.id: RoleAssignment(
|
||||
agent_id=role_assignment.principal_id,
|
||||
agent_type=role_assignment.principal_type,
|
||||
role_id=role_assignment.role_definition_id.split("/")[
|
||||
-1
|
||||
],
|
||||
)
|
||||
}
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(f"Subscription name: {subscription}")
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
return role_assignments
|
||||
|
||||
|
||||
@dataclass
|
||||
class Role:
|
||||
@@ -61,3 +90,10 @@ class Role:
|
||||
type: str
|
||||
assignable_scopes: list[str]
|
||||
permissions: list[Permission]
|
||||
|
||||
|
||||
@dataclass
|
||||
class RoleAssignment:
|
||||
agent_id: str
|
||||
agent_type: str
|
||||
role_id: str
|
||||
|
||||
-1
@@ -29,5 +29,4 @@ class monitor_alert_create_update_public_ip_address_rule(Check):
|
||||
break
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
|
||||
+6
-4
@@ -50,10 +50,12 @@ boto3 = "1.26.165"
|
||||
botocore = "1.29.165"
|
||||
colorama = "0.4.6"
|
||||
detect-secrets = "1.4.0"
|
||||
google-api-python-client = "2.123.0"
|
||||
google-api-python-client = "2.124.0"
|
||||
google-auth-httplib2 = ">=0.1,<0.3"
|
||||
jsonschema = "4.21.1"
|
||||
kubernetes = "28.1.0"
|
||||
mkdocs-git-revision-date-localized-plugin = "1.2.4"
|
||||
mkdocs-material = "9.5.17"
|
||||
msgraph-sdk = "1.0.0"
|
||||
msrestazure = "0.6.4"
|
||||
py-ocsf-models = "0.0.3"
|
||||
@@ -81,7 +83,7 @@ docker = "7.0.0"
|
||||
flake8 = "7.0.0"
|
||||
freezegun = "1.4.0"
|
||||
mock = "5.1.0"
|
||||
moto = {extras = ["all"], version = "5.0.3"}
|
||||
moto = {extras = ["all"], version = "5.0.4"}
|
||||
openapi-schema-validator = "0.6.2"
|
||||
openapi-spec-validator = "0.7.1"
|
||||
pylint = "3.1.0"
|
||||
@@ -90,7 +92,7 @@ pytest-cov = "5.0.0"
|
||||
pytest-env = "1.1.3"
|
||||
pytest-randomly = "3.15.0"
|
||||
pytest-xdist = "3.5.0"
|
||||
safety = "3.0.1"
|
||||
safety = "3.1.0"
|
||||
vulture = "2.11"
|
||||
|
||||
[tool.poetry.group.docs]
|
||||
@@ -99,7 +101,7 @@ optional = true
|
||||
[tool.poetry.group.docs.dependencies]
|
||||
mkdocs = "1.5.3"
|
||||
mkdocs-git-revision-date-localized-plugin = "1.2.4"
|
||||
mkdocs-material = "9.5.15"
|
||||
mkdocs-material = "9.5.17"
|
||||
mkdocs-material-extensions = "1.3.1"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
|
||||
@@ -232,11 +232,7 @@ class Test_Lambda_Service:
|
||||
}
|
||||
assert awslambda.functions[lambda_arn_2].region == AWS_REGION_US_EAST_1
|
||||
# Emtpy policy
|
||||
assert awslambda.functions[lambda_arn_2].policy == {
|
||||
"Id": "default",
|
||||
"Statement": [],
|
||||
"Version": "2012-10-17",
|
||||
}
|
||||
assert awslambda.functions[lambda_arn_2].policy == {}
|
||||
|
||||
# Lambda Code
|
||||
with tempfile.TemporaryDirectory() as tmp_dir_name:
|
||||
|
||||
+27
-27
@@ -4,7 +4,7 @@ from uuid import uuid4
|
||||
from tests.providers.azure.azure_fixtures import DOMAIN, set_mocked_azure_provider
|
||||
|
||||
|
||||
class Test_entra_users_less_than_five_global_admins:
|
||||
class Test_entra_global_admin_in_less_than_five_users:
|
||||
def test_entra_no_tenants(self):
|
||||
entra_client = mock.MagicMock
|
||||
|
||||
@@ -12,16 +12,16 @@ class Test_entra_users_less_than_five_global_admins:
|
||||
"prowler.providers.common.common.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.azure.services.entra.entra_users_less_than_five_global_admins.entra_users_less_than_five_global_admins.entra_client",
|
||||
"prowler.providers.azure.services.entra.entra_global_admin_in_less_than_five_users.entra_global_admin_in_less_than_five_users.entra_client",
|
||||
new=entra_client,
|
||||
):
|
||||
from prowler.providers.azure.services.entra.entra_users_less_than_five_global_admins.entra_users_less_than_five_global_admins import (
|
||||
entra_users_less_than_five_global_admins,
|
||||
from prowler.providers.azure.services.entra.entra_global_admin_in_less_than_five_users.entra_global_admin_in_less_than_five_users import (
|
||||
entra_global_admin_in_less_than_five_users,
|
||||
)
|
||||
|
||||
entra_client.directory_roles = {}
|
||||
|
||||
check = entra_users_less_than_five_global_admins()
|
||||
check = entra_global_admin_in_less_than_five_users()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
@@ -32,16 +32,16 @@ class Test_entra_users_less_than_five_global_admins:
|
||||
"prowler.providers.common.common.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.azure.services.entra.entra_users_less_than_five_global_admins.entra_users_less_than_five_global_admins.entra_client",
|
||||
"prowler.providers.azure.services.entra.entra_global_admin_in_less_than_five_users.entra_global_admin_in_less_than_five_users.entra_client",
|
||||
new=entra_client,
|
||||
):
|
||||
from prowler.providers.azure.services.entra.entra_users_less_than_five_global_admins.entra_users_less_than_five_global_admins import (
|
||||
entra_users_less_than_five_global_admins,
|
||||
from prowler.providers.azure.services.entra.entra_global_admin_in_less_than_five_users.entra_global_admin_in_less_than_five_users import (
|
||||
entra_global_admin_in_less_than_five_users,
|
||||
)
|
||||
|
||||
entra_client.directory_roles = {DOMAIN: {}}
|
||||
|
||||
check = entra_users_less_than_five_global_admins()
|
||||
check = entra_global_admin_in_less_than_five_users()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
@@ -52,16 +52,16 @@ class Test_entra_users_less_than_five_global_admins:
|
||||
"prowler.providers.common.common.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.azure.services.entra.entra_users_less_than_five_global_admins.entra_users_less_than_five_global_admins.entra_client",
|
||||
"prowler.providers.azure.services.entra.entra_global_admin_in_less_than_five_users.entra_global_admin_in_less_than_five_users.entra_client",
|
||||
new=entra_client,
|
||||
):
|
||||
from prowler.providers.azure.services.entra.entra_global_admin_in_less_than_five_users.entra_global_admin_in_less_than_five_users import (
|
||||
entra_global_admin_in_less_than_five_users,
|
||||
)
|
||||
from prowler.providers.azure.services.entra.entra_service import (
|
||||
DirectoryRole,
|
||||
User,
|
||||
)
|
||||
from prowler.providers.azure.services.entra.entra_users_less_than_five_global_admins.entra_users_less_than_five_global_admins import (
|
||||
entra_users_less_than_five_global_admins,
|
||||
)
|
||||
|
||||
id = str(uuid4())
|
||||
id_user1 = str(uuid4())
|
||||
@@ -79,12 +79,12 @@ class Test_entra_users_less_than_five_global_admins:
|
||||
}
|
||||
}
|
||||
|
||||
check = entra_users_less_than_five_global_admins()
|
||||
check = entra_global_admin_in_less_than_five_users()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert result[0].status_extended == "There are 2 global administrators."
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Global Administrator"
|
||||
assert result[0].resource_id == id
|
||||
|
||||
@@ -95,16 +95,16 @@ class Test_entra_users_less_than_five_global_admins:
|
||||
"prowler.providers.common.common.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.azure.services.entra.entra_users_less_than_five_global_admins.entra_users_less_than_five_global_admins.entra_client",
|
||||
"prowler.providers.azure.services.entra.entra_global_admin_in_less_than_five_users.entra_global_admin_in_less_than_five_users.entra_client",
|
||||
new=entra_client,
|
||||
):
|
||||
from prowler.providers.azure.services.entra.entra_global_admin_in_less_than_five_users.entra_global_admin_in_less_than_five_users import (
|
||||
entra_global_admin_in_less_than_five_users,
|
||||
)
|
||||
from prowler.providers.azure.services.entra.entra_service import (
|
||||
DirectoryRole,
|
||||
User,
|
||||
)
|
||||
from prowler.providers.azure.services.entra.entra_users_less_than_five_global_admins.entra_users_less_than_five_global_admins import (
|
||||
entra_users_less_than_five_global_admins,
|
||||
)
|
||||
|
||||
id = str(uuid4())
|
||||
id_user1 = str(uuid4())
|
||||
@@ -130,7 +130,7 @@ class Test_entra_users_less_than_five_global_admins:
|
||||
}
|
||||
}
|
||||
|
||||
check = entra_users_less_than_five_global_admins()
|
||||
check = entra_global_admin_in_less_than_five_users()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
@@ -138,7 +138,7 @@ class Test_entra_users_less_than_five_global_admins:
|
||||
result[0].status_extended
|
||||
== "There are 6 global administrators. It should be less than five."
|
||||
)
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Global Administrator"
|
||||
assert result[0].resource_id == id
|
||||
|
||||
@@ -149,16 +149,16 @@ class Test_entra_users_less_than_five_global_admins:
|
||||
"prowler.providers.common.common.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.azure.services.entra.entra_users_less_than_five_global_admins.entra_users_less_than_five_global_admins.entra_client",
|
||||
"prowler.providers.azure.services.entra.entra_global_admin_in_less_than_five_users.entra_global_admin_in_less_than_five_users.entra_client",
|
||||
new=entra_client,
|
||||
):
|
||||
from prowler.providers.azure.services.entra.entra_global_admin_in_less_than_five_users.entra_global_admin_in_less_than_five_users import (
|
||||
entra_global_admin_in_less_than_five_users,
|
||||
)
|
||||
from prowler.providers.azure.services.entra.entra_service import (
|
||||
DirectoryRole,
|
||||
User,
|
||||
)
|
||||
from prowler.providers.azure.services.entra.entra_users_less_than_five_global_admins.entra_users_less_than_five_global_admins import (
|
||||
entra_users_less_than_five_global_admins,
|
||||
)
|
||||
|
||||
id = str(uuid4())
|
||||
id_user1 = str(uuid4())
|
||||
@@ -182,7 +182,7 @@ class Test_entra_users_less_than_five_global_admins:
|
||||
}
|
||||
}
|
||||
|
||||
check = entra_users_less_than_five_global_admins()
|
||||
check = entra_global_admin_in_less_than_five_users()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
@@ -190,6 +190,6 @@ class Test_entra_users_less_than_five_global_admins:
|
||||
result[0].status_extended
|
||||
== "There are 5 global administrators. It should be less than five."
|
||||
)
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Global Administrator"
|
||||
assert result[0].resource_id == id
|
||||
+5
-2
@@ -77,7 +77,10 @@ class Test_entra_non_privileged_user_has_mfa:
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].status_extended == "User 'foo' does not have MFA enabled."
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Non-privileged user foo does not have MFA."
|
||||
)
|
||||
assert result[0].resource_name == f"foo@{DOMAIN}"
|
||||
assert result[0].resource_id == user_id
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
@@ -114,7 +117,7 @@ class Test_entra_non_privileged_user_has_mfa:
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert result[0].status_extended == "User 'foo' has MFA enabled."
|
||||
assert result[0].status_extended == "Non-privileged user foo has MFA."
|
||||
assert result[0].resource_name == f"foo@{DOMAIN}"
|
||||
assert result[0].resource_id == user_id
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
|
||||
+3
-3
@@ -43,7 +43,7 @@ class Test_entra_policy_default_users_cannot_create_security_groups:
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Authorization Policy"
|
||||
assert result[0].resource_id == "authorizationPolicy"
|
||||
assert (
|
||||
@@ -94,7 +94,7 @@ class Test_entra_policy_default_users_cannot_create_security_groups:
|
||||
)
|
||||
assert result[0].resource_name == "Test"
|
||||
assert result[0].resource_id == id
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
|
||||
def test_entra_default_user_role_permissions_not_allowed_to_create_security_groups(
|
||||
self,
|
||||
@@ -139,4 +139,4 @@ class Test_entra_policy_default_users_cannot_create_security_groups:
|
||||
)
|
||||
assert result[0].resource_name == "Test"
|
||||
assert result[0].resource_id == id
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
|
||||
+3
-3
@@ -45,7 +45,7 @@ class Test_entra_policy_ensure_default_user_cannot_create_apps:
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Authorization Policy"
|
||||
assert result[0].resource_id == "authorizationPolicy"
|
||||
assert (
|
||||
@@ -94,7 +94,7 @@ class Test_entra_policy_ensure_default_user_cannot_create_apps:
|
||||
)
|
||||
assert result[0].resource_name == "Test"
|
||||
assert result[0].resource_id == id
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
|
||||
def test_entra_default_user_role_permissions_allowed_to_create_apps(self):
|
||||
id = str(uuid4())
|
||||
@@ -137,4 +137,4 @@ class Test_entra_policy_ensure_default_user_cannot_create_apps:
|
||||
)
|
||||
assert result[0].resource_name == "Test"
|
||||
assert result[0].resource_id == id
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
|
||||
+3
-3
@@ -43,7 +43,7 @@ class Test_entra_policy_ensure_default_user_cannot_create_tenants:
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Authorization Policy"
|
||||
assert result[0].resource_id == "authorizationPolicy"
|
||||
assert (
|
||||
@@ -92,7 +92,7 @@ class Test_entra_policy_ensure_default_user_cannot_create_tenants:
|
||||
)
|
||||
assert result[0].resource_name == "Test"
|
||||
assert result[0].resource_id == id
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
|
||||
def test_entra_default_user_role_permissions_allowed_to_create_tenants(self):
|
||||
id = str(uuid4())
|
||||
@@ -135,4 +135,4 @@ class Test_entra_policy_ensure_default_user_cannot_create_tenants:
|
||||
)
|
||||
assert result[0].resource_name == "Test"
|
||||
assert result[0].resource_id == id
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
|
||||
+4
-4
@@ -45,7 +45,7 @@ class Test_entra_policy_guest_invite_only_for_admin_roles:
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Authorization Policy"
|
||||
assert result[0].resource_id == "authorizationPolicy"
|
||||
assert (
|
||||
@@ -91,7 +91,7 @@ class Test_entra_policy_guest_invite_only_for_admin_roles:
|
||||
)
|
||||
assert result[0].resource_name == "TestPolicy"
|
||||
assert result[0].resource_id == id
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
|
||||
def test_entra_tenant_policy_allow_invites_from_admins(self):
|
||||
entra_client = mock.MagicMock
|
||||
@@ -131,7 +131,7 @@ class Test_entra_policy_guest_invite_only_for_admin_roles:
|
||||
)
|
||||
assert result[0].resource_name == "TestPolicy"
|
||||
assert result[0].resource_id == id
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
|
||||
def test_entra_tenant_policy_allow_invites_from_none(self):
|
||||
entra_client = mock.MagicMock
|
||||
@@ -171,4 +171,4 @@ class Test_entra_policy_guest_invite_only_for_admin_roles:
|
||||
)
|
||||
assert result[0].resource_name == "TestPolicy"
|
||||
assert result[0].resource_id == id
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
|
||||
+4
-4
@@ -45,7 +45,7 @@ class Test_entra_policy_guest_users_access_restrictions:
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Authorization Policy"
|
||||
assert result[0].resource_id == "authorizationPolicy"
|
||||
assert (
|
||||
@@ -86,7 +86,7 @@ class Test_entra_policy_guest_users_access_restrictions:
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Authorization Policy"
|
||||
assert result[0].resource_id == id
|
||||
assert (
|
||||
@@ -127,7 +127,7 @@ class Test_entra_policy_guest_users_access_restrictions:
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Authorization Policy"
|
||||
assert result[0].resource_id == id
|
||||
assert (
|
||||
@@ -168,7 +168,7 @@ class Test_entra_policy_guest_users_access_restrictions:
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Authorization Policy"
|
||||
assert result[0].resource_id == id
|
||||
assert (
|
||||
|
||||
+4
-4
@@ -45,7 +45,7 @@ class Test_entra_policy_restricts_user_consent_for_apps:
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Authorization Policy"
|
||||
assert result[0].resource_id == "authorizationPolicy"
|
||||
assert (
|
||||
@@ -85,7 +85,7 @@ class Test_entra_policy_restricts_user_consent_for_apps:
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Authorization Policy"
|
||||
assert result[0].resource_id == auth_policy.id
|
||||
assert (
|
||||
@@ -128,7 +128,7 @@ class Test_entra_policy_restricts_user_consent_for_apps:
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Authorization Policy"
|
||||
assert result[0].resource_id == auth_policy.id
|
||||
assert (
|
||||
@@ -173,7 +173,7 @@ class Test_entra_policy_restricts_user_consent_for_apps:
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Authorization Policy"
|
||||
assert result[0].resource_id == auth_policy.id
|
||||
assert (
|
||||
|
||||
+2
-2
@@ -60,7 +60,7 @@ class Test_entra_policy_user_consent_for_verified_apps:
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Authorization Policy"
|
||||
assert result[0].resource_id == auth_policy.id
|
||||
assert (
|
||||
@@ -105,7 +105,7 @@ class Test_entra_policy_user_consent_for_verified_apps:
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Authorization Policy"
|
||||
assert result[0].resource_id == auth_policy.id
|
||||
assert (
|
||||
|
||||
+2
-2
@@ -143,7 +143,7 @@ class Test_entra_privileged_user_has_mfa:
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].status_extended == "User 'foo' does not have MFA enabled."
|
||||
assert result[0].status_extended == "Privileged user foo does not have MFA."
|
||||
assert result[0].resource_name == f"foo@{DOMAIN}"
|
||||
assert result[0].resource_id == user_id
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
@@ -182,7 +182,7 @@ class Test_entra_privileged_user_has_mfa:
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert result[0].status_extended == "User 'foo' has MFA enabled."
|
||||
assert result[0].status_extended == "Privileged user foo has MFA."
|
||||
assert result[0].resource_name == f"foo@{DOMAIN}"
|
||||
assert result[0].resource_id == user_id
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
|
||||
+3
-3
@@ -46,7 +46,7 @@ class Test_entra_security_defaults_enabled:
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].status_extended == "Entra security defaults is diabled."
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Security Default"
|
||||
assert result[0].resource_id == "Security Default"
|
||||
|
||||
@@ -78,7 +78,7 @@ class Test_entra_security_defaults_enabled:
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert result[0].status_extended == "Entra security defaults is enabled."
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Sec Default"
|
||||
assert result[0].resource_id == id
|
||||
|
||||
@@ -110,6 +110,6 @@ class Test_entra_security_defaults_enabled:
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].status_extended == "Entra security defaults is diabled."
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Sec Default"
|
||||
assert result[0].resource_id == id
|
||||
|
||||
+4
-4
@@ -48,7 +48,7 @@ class Test_entra_trusted_named_locations_exists:
|
||||
result[0].status_extended
|
||||
== "There is no trusted location with IP ranges defined."
|
||||
)
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Named Locations"
|
||||
assert result[0].resource_id == "Named Locations"
|
||||
|
||||
@@ -87,7 +87,7 @@ class Test_entra_trusted_named_locations_exists:
|
||||
result[0].status_extended
|
||||
== "Exits trusted location with trusted IP ranges, this IPs ranges are: ['192.168.0.1/24']"
|
||||
)
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Test Location"
|
||||
assert result[0].resource_id == "location_id"
|
||||
|
||||
@@ -124,7 +124,7 @@ class Test_entra_trusted_named_locations_exists:
|
||||
result[0].status_extended
|
||||
== "There is no trusted location with IP ranges defined."
|
||||
)
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Test Location"
|
||||
assert result[0].resource_id == "location_id"
|
||||
|
||||
@@ -163,6 +163,6 @@ class Test_entra_trusted_named_locations_exists:
|
||||
result[0].status_extended
|
||||
== "There is no trusted location with IP ranges defined."
|
||||
)
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Test Location"
|
||||
assert result[0].resource_id == "location_id"
|
||||
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
from unittest import mock
|
||||
from uuid import uuid4
|
||||
|
||||
from prowler.providers.azure.config import VIRTUAL_MACHINE_ADMINISTRATOR_LOGIN_ROLE_ID
|
||||
from tests.providers.azure.azure_fixtures import (
|
||||
AZURE_SUBSCRIPTION_ID,
|
||||
DOMAIN,
|
||||
set_mocked_azure_provider,
|
||||
)
|
||||
|
||||
|
||||
class Test_iam_assignment_priviledge_access_vm_has_mfa:
|
||||
def test_iam_no_roles(self):
|
||||
iam_client = mock.MagicMock
|
||||
entra_client = mock.MagicMock
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.common.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.azure.services.entra.entra_user_with_vm_access_has_mfa.entra_user_with_vm_access_has_mfa.iam_client",
|
||||
new=iam_client,
|
||||
):
|
||||
from prowler.providers.azure.services.entra.entra_user_with_vm_access_has_mfa.entra_user_with_vm_access_has_mfa import (
|
||||
entra_user_with_vm_access_has_mfa,
|
||||
)
|
||||
|
||||
iam_client.role_assignments = {}
|
||||
entra_client.users = {}
|
||||
|
||||
check = entra_user_with_vm_access_has_mfa()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_entra_user_with_vm_access_has_mfa(self):
|
||||
iam_client = mock.MagicMock
|
||||
role_assigment_id = str(uuid4())
|
||||
entra_client = mock.MagicMock
|
||||
user_id = str(uuid4())
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.common.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.azure.services.entra.entra_user_with_vm_access_has_mfa.entra_user_with_vm_access_has_mfa.iam_client",
|
||||
new=iam_client,
|
||||
):
|
||||
with mock.patch(
|
||||
"prowler.providers.common.common.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.azure.services.entra.entra_user_with_vm_access_has_mfa.entra_user_with_vm_access_has_mfa.entra_client",
|
||||
new=entra_client,
|
||||
):
|
||||
from prowler.providers.azure.services.entra.entra_service import User
|
||||
from prowler.providers.azure.services.entra.entra_user_with_vm_access_has_mfa.entra_user_with_vm_access_has_mfa import (
|
||||
entra_user_with_vm_access_has_mfa,
|
||||
)
|
||||
from prowler.providers.azure.services.iam.iam_service import (
|
||||
RoleAssignment,
|
||||
)
|
||||
|
||||
iam_client.role_assignments = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
role_assigment_id: RoleAssignment(
|
||||
role_id=VIRTUAL_MACHINE_ADMINISTRATOR_LOGIN_ROLE_ID,
|
||||
agent_type="User",
|
||||
agent_id=user_id,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
entra_client.users = {
|
||||
DOMAIN: {
|
||||
f"test@{DOMAIN}": User(
|
||||
id=user_id,
|
||||
name="test",
|
||||
authentication_methods=[
|
||||
"Password",
|
||||
"MicrosoftAuthenticator",
|
||||
],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
check = entra_user_with_vm_access_has_mfa()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"User test can access VMs in subscription {AZURE_SUBSCRIPTION_ID} but it has MFA."
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == f"test@{DOMAIN}"
|
||||
assert result[0].resource_id == user_id
|
||||
|
||||
def test_entra_user_with_vm_access_has_mfa_no_mfa(self):
|
||||
iam_client = mock.MagicMock
|
||||
role_assigment_id = str(uuid4())
|
||||
entra_client = mock.MagicMock
|
||||
user_id = str(uuid4())
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.common.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.azure.services.entra.entra_user_with_vm_access_has_mfa.entra_user_with_vm_access_has_mfa.iam_client",
|
||||
new=iam_client,
|
||||
):
|
||||
with mock.patch(
|
||||
"prowler.providers.common.common.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.azure.services.entra.entra_user_with_vm_access_has_mfa.entra_user_with_vm_access_has_mfa.entra_client",
|
||||
new=entra_client,
|
||||
):
|
||||
from prowler.providers.azure.services.entra.entra_service import User
|
||||
from prowler.providers.azure.services.entra.entra_user_with_vm_access_has_mfa.entra_user_with_vm_access_has_mfa import (
|
||||
entra_user_with_vm_access_has_mfa,
|
||||
)
|
||||
from prowler.providers.azure.services.iam.iam_service import (
|
||||
RoleAssignment,
|
||||
)
|
||||
|
||||
iam_client.role_assignments = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
role_assigment_id: RoleAssignment(
|
||||
role_id=VIRTUAL_MACHINE_ADMINISTRATOR_LOGIN_ROLE_ID,
|
||||
agent_type="User",
|
||||
agent_id=user_id,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
entra_client.users = {
|
||||
DOMAIN: {
|
||||
f"test@{DOMAIN}": User(
|
||||
id=user_id, name="test", authentication_methods=["Password"]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
check = entra_user_with_vm_access_has_mfa()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"User test without MFA can access VMs in subscription {AZURE_SUBSCRIPTION_ID}"
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
assert result[0].resource_name == f"test@{DOMAIN}"
|
||||
assert result[0].resource_id == user_id
|
||||
|
||||
def test_entra_user_with_vm_access_has_mfa_no_user(self):
|
||||
iam_client = mock.MagicMock
|
||||
role_assigment_id = str(uuid4())
|
||||
entra_client = mock.MagicMock
|
||||
user_id = str(uuid4())
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.common.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.azure.services.entra.entra_user_with_vm_access_has_mfa.entra_user_with_vm_access_has_mfa.iam_client",
|
||||
new=iam_client,
|
||||
):
|
||||
with mock.patch(
|
||||
"prowler.providers.common.common.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.azure.services.entra.entra_user_with_vm_access_has_mfa.entra_user_with_vm_access_has_mfa.entra_client",
|
||||
new=entra_client,
|
||||
):
|
||||
from prowler.providers.azure.services.entra.entra_user_with_vm_access_has_mfa.entra_user_with_vm_access_has_mfa import (
|
||||
entra_user_with_vm_access_has_mfa,
|
||||
)
|
||||
from prowler.providers.azure.services.iam.iam_service import (
|
||||
RoleAssignment,
|
||||
)
|
||||
|
||||
iam_client.role_assignments = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
role_assigment_id: RoleAssignment(
|
||||
role_id=VIRTUAL_MACHINE_ADMINISTRATOR_LOGIN_ROLE_ID,
|
||||
agent_type="User",
|
||||
agent_id=user_id,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
entra_client.users = {DOMAIN: {}}
|
||||
|
||||
check = entra_user_with_vm_access_has_mfa()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_entra_user_with_vm_access_has_mfa_no_role(self):
|
||||
iam_client = mock.MagicMock
|
||||
role_assigment_id = str(uuid4())
|
||||
entra_client = mock.MagicMock
|
||||
user_id = str(uuid4())
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.common.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.azure.services.entra.entra_user_with_vm_access_has_mfa.entra_user_with_vm_access_has_mfa.iam_client",
|
||||
new=iam_client,
|
||||
):
|
||||
with mock.patch(
|
||||
"prowler.providers.common.common.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.azure.services.entra.entra_user_with_vm_access_has_mfa.entra_user_with_vm_access_has_mfa.entra_client",
|
||||
new=entra_client,
|
||||
):
|
||||
from prowler.providers.azure.services.entra.entra_service import User
|
||||
from prowler.providers.azure.services.entra.entra_user_with_vm_access_has_mfa.entra_user_with_vm_access_has_mfa import (
|
||||
entra_user_with_vm_access_has_mfa,
|
||||
)
|
||||
from prowler.providers.azure.services.iam.iam_service import (
|
||||
RoleAssignment,
|
||||
)
|
||||
|
||||
iam_client.role_assignments = {
|
||||
AZURE_SUBSCRIPTION_ID: {
|
||||
role_assigment_id: RoleAssignment(
|
||||
role_id=str(uuid4()),
|
||||
agent_type="User",
|
||||
agent_id=user_id,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
entra_client.users = {
|
||||
DOMAIN: {
|
||||
f"test@{DOMAIN}": User(
|
||||
id=user_id,
|
||||
name="test",
|
||||
authentication_methods=[
|
||||
"Password",
|
||||
"MicrosoftAuthenticator",
|
||||
],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
check = entra_user_with_vm_access_has_mfa()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
+4
-4
@@ -46,7 +46,7 @@ class Test_entra_users_cannot_create_microsoft_365_groups:
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].status_extended == "Users can create Microsoft 365 groups."
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Microsoft365 Groups"
|
||||
assert result[0].resource_id == "Microsoft365 Groups"
|
||||
|
||||
@@ -91,7 +91,7 @@ class Test_entra_users_cannot_create_microsoft_365_groups:
|
||||
assert (
|
||||
result[0].status_extended == "Users cannot create Microsoft 365 groups."
|
||||
)
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Microsoft365 Groups"
|
||||
assert result[0].resource_id == "Microsoft365 Groups"
|
||||
|
||||
@@ -134,7 +134,7 @@ class Test_entra_users_cannot_create_microsoft_365_groups:
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].status_extended == "Users can create Microsoft 365 groups."
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Microsoft365 Groups"
|
||||
assert result[0].resource_id == "Microsoft365 Groups"
|
||||
|
||||
@@ -173,6 +173,6 @@ class Test_entra_users_cannot_create_microsoft_365_groups:
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].status_extended == "Users can create Microsoft 365 groups."
|
||||
assert result[0].subscription == f"Tenant: '{DOMAIN}'"
|
||||
assert result[0].subscription == f"Tenant: {DOMAIN}"
|
||||
assert result[0].resource_name == "Microsoft365 Groups"
|
||||
assert result[0].resource_id == "Microsoft365 Groups"
|
||||
|
||||
Reference in New Issue
Block a user