diff --git a/.github/codeql/api-codeql-config.yml b/.github/codeql/api-codeql-config.yml index ac8ca6beb5..9ce26a3651 100644 --- a/.github/codeql/api-codeql-config.yml +++ b/.github/codeql/api-codeql-config.yml @@ -1,3 +1,3 @@ -name: "Custom CodeQL Config for API" +name: "API - CodeQL Config" paths: - - 'api/' \ No newline at end of file + - "api/" diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml deleted file mode 100644 index f31f68cba8..0000000000 --- a/.github/codeql/codeql-config.yml +++ /dev/null @@ -1,4 +0,0 @@ -name: "Custom CodeQL Config" -paths-ignore: - - 'api/' - - 'ui/' \ No newline at end of file diff --git a/.github/codeql/sdk-codeql-config.yml b/.github/codeql/sdk-codeql-config.yml new file mode 100644 index 0000000000..7982398f42 --- /dev/null +++ b/.github/codeql/sdk-codeql-config.yml @@ -0,0 +1,4 @@ +name: "SDK - CodeQL Config" +paths-ignore: + - "api/" + - "ui/" diff --git a/.github/codeql/ui-codeql-config.yml b/.github/codeql/ui-codeql-config.yml index 62ebee5617..fa4f80cae5 100644 --- a/.github/codeql/ui-codeql-config.yml +++ b/.github/codeql/ui-codeql-config.yml @@ -1,3 +1,3 @@ -name: "Custom CodeQL Config for UI" +name: "UI - CodeQL Config" paths: - "ui/" diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7b06e0da7a..facc227ae6 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,6 +5,7 @@ version: 2 updates: + # v5 - package-ecosystem: "pip" directory: "/" schedule: @@ -14,6 +15,7 @@ updates: labels: - "dependencies" - "pip" + - package-ecosystem: "github-actions" directory: "/" schedule: @@ -24,20 +26,55 @@ updates: - "dependencies" - "github_actions" - - package-ecosystem: "pip" + - package-ecosystem: "npm" directory: "/" schedule: interval: "daily" open-pull-requests-limit: 10 + target-branch: master + labels: + - "dependencies" + - "npm" + + # v4.6 + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 + target-branch: v4.6 + labels: + - "dependencies" + - "pip" + - "v4" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 + target-branch: v4.6 + labels: + - "dependencies" + - "github_actions" + - "v4" + + # v3 + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "monthly" + open-pull-requests-limit: 10 target-branch: v3 labels: - "dependencies" - "pip" - "v3" + - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "daily" + interval: "monthly" open-pull-requests-limit: 10 target-branch: v3 labels: diff --git a/.github/workflows/api-build-lint-push-containers.yml b/.github/workflows/api-build-lint-push-containers.yml new file mode 100644 index 0000000000..0951f22eca --- /dev/null +++ b/.github/workflows/api-build-lint-push-containers.yml @@ -0,0 +1,81 @@ +name: API - Build and Push containers + +on: + push: + branches: + - "master" + paths: + - "api/**" + - ".github/workflows/api-build-lint-push-containers.yml" + + # Uncomment the code below to test this action on PRs + # pull_request: + # branches: + # - "master" + # paths: + # - "api/**" + # - ".github/workflows/api-build-lint-push-containers.yml" + + release: + types: [published] + +env: + # Tags + LATEST_TAG: latest + RELEASE_TAG: ${{ github.event.release.tag_name }} + + WORKING_DIRECTORY: ./api + + # Container Registries + PROWLERCLOUD_DOCKERHUB_REPOSITORY: prowlercloud + PROWLERCLOUD_DOCKERHUB_IMAGE: prowler-api + +jobs: + # Build Prowler OSS container + container-build-push: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ${{ env.WORKING_DIRECTORY }} + + steps: + - name: Repository check + working-directory: /tmp + run: | + [[ ${{ github.repository }} != "prowler-cloud/prowler" ]] && echo "This action only runs for prowler-cloud/prowler"; exit 0 + + - name: Checkout + uses: actions/checkout@v4 + + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build and push container image (latest) + # Comment the following line for testing + if: github.event_name == 'push' + uses: docker/build-push-action@v6 + with: + context: ${{ env.WORKING_DIRECTORY }} + # Set push: false for testing + push: true + tags: | + ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ env.LATEST_TAG }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Build and push container image (release) + if: github.event_name == 'release' + uses: docker/build-push-action@v6 + with: + context: ${{ env.WORKING_DIRECTORY }} + push: true + tags: | + ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ env.RELEASE_TAG }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/api-codeql.yml b/.github/workflows/api-codeql.yml index ed9e0c3fd4..75d12109a6 100644 --- a/.github/workflows/api-codeql.yml +++ b/.github/workflows/api-codeql.yml @@ -9,11 +9,11 @@ # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # -name: "API - CodeQL" +name: API - CodeQL on: push: - branches: + branches: - "master" - "v3" - "v4.*" @@ -21,7 +21,7 @@ on: paths: - "api/**" pull_request: - branches: + branches: - "master" - "v3" - "v4.*" diff --git a/.github/workflows/api-pull-request.yml b/.github/workflows/api-pull-request.yml index 8143ca7f41..896be36f7a 100644 --- a/.github/workflows/api-pull-request.yml +++ b/.github/workflows/api-pull-request.yml @@ -1,4 +1,4 @@ -name: "API - Pull Request" +name: API - Pull Request on: push: @@ -69,6 +69,7 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Test if changes are in not ignored paths id: are-non-ignored-files-changed uses: tj-actions/changed-files@v45 @@ -80,18 +81,21 @@ jobs: api/permissions/** api/README.md api/mkdocs.yml + - name: Install poetry working-directory: ./api if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' run: | python -m pip install --upgrade pip pipx install poetry + - name: Set up Python ${{ matrix.python-version }} if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: "poetry" + - name: Install dependencies working-directory: ./api if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' @@ -109,48 +113,59 @@ jobs: if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' run: | poetry lock --check + - name: Lint with ruff working-directory: ./api if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' run: | poetry run ruff check . --exclude contrib + - name: Check Format with ruff working-directory: ./api if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' run: | poetry run ruff format --check . --exclude contrib + - name: Lint with pylint working-directory: ./api if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' run: | poetry run pylint --disable=W,C,R,E -j 0 -rn -sn src/ + - name: Bandit working-directory: ./api if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' run: | poetry run bandit -q -lll -x '*_test.py,./contrib/' -r . + - name: Safety working-directory: ./api if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' run: | poetry run safety check --ignore 70612,66963 + - name: Vulture working-directory: ./api if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' run: | poetry run vulture --exclude "contrib,tests,conftest.py" --min-confidence 100 . + - name: Hadolint working-directory: ./api if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' run: | /tmp/hadolint Dockerfile --ignore=DL3013 + - name: Test with pytest working-directory: ./api if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' run: | poetry run pytest --cov=./src/backend --cov-report=xml src/backend + - name: Upload coverage reports to Codecov if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' uses: codecov/codecov-action@v5 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + with: + flags: api diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 534f7d6d46..d74b96f3f8 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -1,42 +1,47 @@ -name: Automatic Backport +name: Prowler - Automatic Backport on: pull_request_target: branches: ['master'] types: ['labeled', 'closed'] +env: + # The prefix of the label that triggers the backport must not contain the branch name + # so, for example, if the branch is 'master', the label should be 'backport-to-' + BACKPORT_LABEL_PREFIX: backport-to- + BACKPORT_LABEL_IGNORE: was-backported + jobs: backport: name: Backport PR - if: github.event.pull_request.merged == true && !(contains(github.event.pull_request.labels.*.name, 'backport')) + if: github.event.pull_request.merged == true && !(contains(github.event.pull_request.labels.*.name, 'backport')) && !(contains(github.event.pull_request.labels.*.name, 'was-backported')) runs-on: ubuntu-latest permissions: id-token: write pull-requests: write contents: write steps: - # Workaround not to fail the workflow if the PR does not need a backport - # https://github.com/sorenlouv/backport-github-action/issues/127#issuecomment-2258561266 - - name: Check for backport labels - id: check_labels - run: |- - labels='${{ toJSON(github.event.pull_request.labels.*.name) }}' - echo "$labels" - matched=$(echo "${labels}" | jq '. | map(select(startswith("backport-to-"))) | length') - echo "matched=$matched" - echo "matched=$matched" >> $GITHUB_OUTPUT + - name: Check labels + id: preview_label_check + uses: docker://agilepathway/pull-request-label-checker:v1.6.55 + with: + allow_failure: true + prefix_mode: true + any_of: ${{ env.BACKPORT_LABEL_PREFIX }} + none_of: ${{ env.BACKPORT_LABEL_IGNORE }} + repo_token: ${{ secrets.GITHUB_TOKEN }} - name: Backport Action - if: fromJSON(steps.check_labels.outputs.matched) > 0 + if: steps.preview_label_check.outputs.label_check == 'success' uses: sorenlouv/backport-github-action@v9.5.1 with: github_token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }} - auto_backport_label_prefix: backport-to- + auto_backport_label_prefix: ${{ env.BACKPORT_LABEL_PREFIX }} - name: Info log - if: ${{ success() && fromJSON(steps.check_labels.outputs.matched) > 0 }} + if: ${{ success() && steps.preview_label_check.outputs.label_check == 'success' }} run: cat ~/.backport/backport.info.log - name: Debug log - if: ${{ failure() && fromJSON(steps.check_labels.outputs.matched) > 0 }} + if: ${{ failure() && steps.preview_label_check.outputs.label_check == 'success' }} run: cat ~/.backport/backport.debug.log diff --git a/.github/workflows/build-documentation-on-pr.yml b/.github/workflows/build-documentation-on-pr.yml index a1ed22a071..7ae58b9c85 100644 --- a/.github/workflows/build-documentation-on-pr.yml +++ b/.github/workflows/build-documentation-on-pr.yml @@ -1,4 +1,4 @@ -name: Pull Request Documentation Link +name: Prowler - Pull Request Documentation Link on: pull_request: diff --git a/.github/workflows/find-secrets.yml b/.github/workflows/find-secrets.yml index e73e874f26..372cdf3f16 100644 --- a/.github/workflows/find-secrets.yml +++ b/.github/workflows/find-secrets.yml @@ -1,4 +1,4 @@ -name: Find secrets +name: Prowler - Find secrets on: pull_request @@ -11,9 +11,9 @@ jobs: with: fetch-depth: 0 - name: TruffleHog OSS - uses: trufflesecurity/trufflehog@v3.84.1 + uses: trufflesecurity/trufflehog@v3.86.1 with: path: ./ base: ${{ github.event.repository.default_branch }} head: HEAD - extra_args: --only-verified \ No newline at end of file + extra_args: --only-verified diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index d7162a0058..199b17962d 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -1,4 +1,4 @@ -name: "Pull Request Labeler" +name: Prowler - PR Labeler on: pull_request_target: diff --git a/.github/workflows/build-lint-push-containers.yml b/.github/workflows/sdk-build-lint-push-containers.yml similarity index 97% rename from .github/workflows/build-lint-push-containers.yml rename to .github/workflows/sdk-build-lint-push-containers.yml index af17eeb193..98460b3214 100644 --- a/.github/workflows/build-lint-push-containers.yml +++ b/.github/workflows/sdk-build-lint-push-containers.yml @@ -1,9 +1,13 @@ -name: Build and Push containers +name: SDK - Build and Push containers on: push: branches: + # For `v3-latest` - "v3" + # For `v4-latest` + - "v4.6" + # For `latest` - "master" paths-ignore: - ".github/**" @@ -85,8 +89,8 @@ jobs: echo "STABLE_TAG=v3-stable" >> "${GITHUB_ENV}" ;; - - 4) + + 4) echo "LATEST_TAG=v4-latest" >> "${GITHUB_ENV}" echo "STABLE_TAG=v4-stable" >> "${GITHUB_ENV}" ;; diff --git a/.github/workflows/codeql.yml b/.github/workflows/sdk-codeql.yml similarity index 93% rename from .github/workflows/codeql.yml rename to .github/workflows/sdk-codeql.yml index c0e319e07e..043aeb041b 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/sdk-codeql.yml @@ -9,11 +9,11 @@ # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # -name: "CodeQL" +name: SDK - CodeQL on: push: - branches: + branches: - "master" - "v3" - "v4.*" @@ -21,7 +21,7 @@ on: - 'ui/**' - 'api/**' pull_request: - branches: + branches: - "master" - "v3" - "v4.*" @@ -55,7 +55,7 @@ jobs: uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} - config-file: ./.github/codeql/codeql-config.yml + config-file: ./.github/codeql/sdk-codeql-config.yml - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/pull-request.yml b/.github/workflows/sdk-pull-request.yml similarity index 97% rename from .github/workflows/pull-request.yml rename to .github/workflows/sdk-pull-request.yml index cdb564838b..fc49b8311b 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/sdk-pull-request.yml @@ -1,4 +1,4 @@ -name: "Pull Request" +name: SDK - Pull Request on: push: @@ -22,6 +22,7 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Test if changes are in not ignored paths id: are-non-ignored-files-changed uses: tj-actions/changed-files@v45 @@ -36,17 +37,20 @@ jobs: README.md mkdocs.yml .backportrc.json + - name: Install poetry if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' run: | python -m pip install --upgrade pip pipx install poetry + - name: Set up Python ${{ matrix.python-version }} if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: "poetry" + - name: Install dependencies if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' run: | @@ -57,44 +61,56 @@ jobs: sed -E 's/.*"v([^"]+)".*/\1/' \ ) && curl -L -o /tmp/hadolint "https://github.com/hadolint/hadolint/releases/download/v${VERSION}/hadolint-Linux-x86_64" \ && chmod +x /tmp/hadolint + - name: Poetry check if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' run: | poetry lock --check + - name: Lint with flake8 if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' run: | poetry run flake8 . --ignore=E266,W503,E203,E501,W605,E128 --exclude contrib,ui,api + - name: Checking format with black if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' run: | poetry run black --exclude api ui --check . + - name: Lint with pylint if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' run: | poetry run pylint --disable=W,C,R,E -j 0 -rn -sn prowler/ + - name: Bandit if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' run: | poetry run bandit -q -lll -x '*_test.py,./contrib/,./api/,./ui' -r . + - name: Safety if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' run: | poetry run safety check --ignore 70612 -r pyproject.toml + - name: Vulture if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' run: | poetry run vulture --exclude "contrib,api,ui" --min-confidence 100 . + - name: Hadolint if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' run: | /tmp/hadolint Dockerfile --ignore=DL3013 + - name: Test with pytest if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' run: | poetry run pytest -n auto --cov=./prowler --cov-report=xml tests + - name: Upload coverage reports to Codecov if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true' uses: codecov/codecov-action@v5 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + with: + flags: prowler diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/sdk-pypi-release.yml similarity index 98% rename from .github/workflows/pypi-release.yml rename to .github/workflows/sdk-pypi-release.yml index 2c8ae5e659..4d9c91d690 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/sdk-pypi-release.yml @@ -1,4 +1,4 @@ -name: PyPI release +name: SDK - PyPI release on: release: diff --git a/.github/workflows/refresh_aws_services_regions.yml b/.github/workflows/sdk-refresh-aws-services-regions.yml similarity index 98% rename from .github/workflows/refresh_aws_services_regions.yml rename to .github/workflows/sdk-refresh-aws-services-regions.yml index 548f8d5600..bf7af302e2 100644 --- a/.github/workflows/refresh_aws_services_regions.yml +++ b/.github/workflows/sdk-refresh-aws-services-regions.yml @@ -1,6 +1,6 @@ # This is a basic workflow to help you get started with Actions -name: Refresh regions of AWS services +name: SDK - Refresh AWS services' regions on: schedule: diff --git a/.github/workflows/ui-build-lint-push-containers.yml b/.github/workflows/ui-build-lint-push-containers.yml new file mode 100644 index 0000000000..41c0a11017 --- /dev/null +++ b/.github/workflows/ui-build-lint-push-containers.yml @@ -0,0 +1,81 @@ +name: UI - Build and Push containers + +on: + push: + branches: + - "master" + paths: + - "ui/**" + - ".github/workflows/ui-build-lint-push-containers.yml" + + # Uncomment the below code to test this action on PRs + # pull_request: + # branches: + # - "master" + # paths: + # - "ui/**" + # - ".github/workflows/ui-build-lint-push-containers.yml" + + release: + types: [published] + +env: + # Tags + LATEST_TAG: latest + RELEASE_TAG: ${{ github.event.release.tag_name }} + + WORKING_DIRECTORY: ./ui + + # Container Registries + PROWLERCLOUD_DOCKERHUB_REPOSITORY: prowlercloud + PROWLERCLOUD_DOCKERHUB_IMAGE: prowler-ui + +jobs: + # Build Prowler OSS container + container-build-push: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ${{ env.WORKING_DIRECTORY }} + + steps: + - name: Repository check + working-directory: /tmp + run: | + [[ ${{ github.repository }} != "prowler-cloud/prowler" ]] && echo "This action only runs for prowler-cloud/prowler"; exit 0 + + - name: Checkout + uses: actions/checkout@v4 + + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build and push container image (latest) + # Comment the following line for testing + if: github.event_name == 'push' + uses: docker/build-push-action@v6 + with: + context: ${{ env.WORKING_DIRECTORY }} + # Set push: false for testing + push: true + tags: | + ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ env.LATEST_TAG }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Build and push container image (release) + if: github.event_name == 'release' + uses: docker/build-push-action@v6 + with: + context: ${{ env.WORKING_DIRECTORY }} + push: true + tags: | + ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ env.RELEASE_TAG }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/ui-codeql.yml b/.github/workflows/ui-codeql.yml index 2765921cf6..30586f43e7 100644 --- a/.github/workflows/ui-codeql.yml +++ b/.github/workflows/ui-codeql.yml @@ -9,7 +9,7 @@ # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # -name: "UI - CodeQL" +name: UI - CodeQL on: push: diff --git a/.github/workflows/ui-checks.yml b/.github/workflows/ui-pull-request.yml similarity index 93% rename from .github/workflows/ui-checks.yml rename to .github/workflows/ui-pull-request.yml index f0556fc61f..28e5d32e0a 100644 --- a/.github/workflows/ui-checks.yml +++ b/.github/workflows/ui-pull-request.yml @@ -1,4 +1,4 @@ -name: "UI - Pull Request" +name: UI - Pull Request on: pull_request: @@ -31,4 +31,4 @@ jobs: run: npm run healthcheck - name: Build the application working-directory: ./ui - run: npm run build \ No newline at end of file + run: npm run build diff --git a/Dockerfile b/Dockerfile index 613bd790eb..d8472d3492 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.12-alpine +FROM python:3.12.8-alpine3.20 LABEL maintainer="https://github.com/prowler-cloud/prowler" diff --git a/README.md b/README.md index 1a0944fb96..98d5cd2476 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@

- Prowler SaaS and Prowler Open Source are as dynamic and adaptable as the environment they’re meant to protect. Trusted by the leaders in security. + Prowler Open Source is as dynamic and adaptable as the environment they’re meant to protect. Trusted by the leaders in security.

Learn more at prowler.com @@ -29,7 +29,7 @@

Repo size Issues - Version + Version Version Contributors License @@ -43,7 +43,7 @@ # Description -**Prowler** is an Open Source security tool to perform AWS, Azure, Google Cloud and Kubernetes security best practices assessments, audits, incident response, continuous monitoring, hardening and forensics readiness, and also remediations! We have Prowler CLI (Command Line Interface) that we call Prowler Open Source and a service on top of it that we call Prowler SaaS. +**Prowler** is an Open Source security tool to perform AWS, Azure, Google Cloud and Kubernetes security best practices assessments, audits, incident response, continuous monitoring, hardening and forensics readiness, and also remediations! We have Prowler CLI (Command Line Interface) that we call Prowler Open Source and a service on top of it that we call Prowler Cloud. ## Prowler App @@ -139,6 +139,19 @@ cd src/backend python -m celery -A config.celery worker -l info -E ``` +**Commands to run the API Scheduler** + +``` console +git clone https://github.com/prowler-cloud/prowler +cd prowler/api +poetry install +poetry shell +set -a +source .env +cd src/backend +python -m celery -A config.celery beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler +``` + **Commands to run the UI** ``` console diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000000..ca31ca8dd4 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,11 @@ +component_management: + individual_components: + - component_id: "prowler" + paths: + - "prowler/**" + - component_id: "api" + paths: + - "api/**" + +comment: + layout: "header, diff, flags, components" diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 2c39e2d9d2..d49af9dfbb 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -37,7 +37,7 @@ services: - 3000:3000 postgres: - image: postgres:16.3-alpine + image: postgres:16.3-alpine3.20 hostname: "postgres-db" volumes: - ./_data/postgres:/var/lib/postgresql/data diff --git a/docker-compose.yml b/docker-compose.yml index 281c79ddf8..004e82e2fd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,7 +25,7 @@ services: - ${UI_PORT:-3000}:${UI_PORT:-3000} postgres: - image: postgres:16.3-alpine + image: postgres:16.3-alpine3.20 hostname: "postgres-db" volumes: - ./_data/postgres:/var/lib/postgresql/data diff --git a/docs/developer-guide/unit-testing.md b/docs/developer-guide/unit-testing.md index 0f8c0cfe23..27c0110a0f 100644 --- a/docs/developer-guide/unit-testing.md +++ b/docs/developer-guide/unit-testing.md @@ -51,14 +51,14 @@ For the AWS provider we have ways to test a Prowler check based on the following We use and contribute to the [Moto](https://github.com/getmoto/moto) library which allows us to easily mock out tests based on AWS infrastructure. **It's awesome!** - AWS API calls covered by [Moto](https://github.com/getmoto/moto): - - Service tests with `@mock_` - - Checks tests with `@mock_` + - Service tests with `@mock_aws` + - Checks tests with `@mock_aws` - AWS API calls not covered by Moto: - Service test with `mock_make_api_call` - Checks tests with [MagicMock](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.MagicMock) - AWS API calls partially covered by Moto: - - Service test with `@mock_` and `mock_make_api_call` - - Checks tests with `@mock_` and `mock_make_api_call` + - Service test with `@mock_aws` and `mock_make_api_call` + - Checks tests with `@mock_aws` and `mock_make_api_call` In the following section we are going to explain all of the above scenarios with examples. The main difference between those scenarios comes from if the [Moto](https://github.com/getmoto/moto) library covers the AWS API calls made by the service. You can check the covered API calls [here](https://github.com/getmoto/moto/blob/master/IMPLEMENTATION_COVERAGE.md). @@ -70,7 +70,7 @@ This section is going to be divided based on the API coverage of the [Moto](http #### API calls covered -If the [Moto](https://github.com/getmoto/moto) library covers the API calls we want to test, we can use the `@mock_` decorator. This will mocked out all the API calls made to AWS keeping the state within the code decorated, in this case the test function. +If the [Moto](https://github.com/getmoto/moto) library covers the API calls we want to test, we can use the `@mock_aws` decorator. This will mocked out all the API calls made to AWS keeping the state within the code decorated, in this case the test function. ```python # We need to import the unittest.mock to allow us to patch some objects @@ -80,8 +80,8 @@ from unittest import mock # Boto3 client and session to call the AWS APIs from boto3 import client, session -# Moto decorator for the IAM service we want to mock -from moto import mock_iam +# Moto decorator +from moto import mock_aws # Constants used AWS_ACCOUNT_NUMBER = "123456789012" @@ -91,10 +91,8 @@ AWS_REGION = "us-east-1" # We always name the test classes like Test_ class Test_iam_password_policy_uppercase: - # We include the Moto decorator for the service we want to use - # You can include more than one if two or more services are - # involved in test - @mock_iam + # We include the Moto decorator + @mock_aws # We name the tests with test___ def test_iam_password_policy_no_uppercase_flag(self): # First, we have to create an IAM client @@ -238,7 +236,7 @@ To do so, you need to mock the `botocore.client.BaseClient._make_api_call` funct import boto3 import botocore from unittest.mock import patch -from moto import mock_iam +from moto import mock_aws # Original botocore _make_api_call function orig = botocore.client.BaseClient._make_api_call diff --git a/docs/img/compliance.png b/docs/img/compliance.png new file mode 100644 index 0000000000..b08fcc3651 Binary files /dev/null and b/docs/img/compliance.png differ diff --git a/docs/index.md b/docs/index.md index 6f21f75023..3ab1b4cfd6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,4 +1,4 @@ -**Prowler** is an Open Source security tool to perform AWS, Azure, Google Cloud and Kubernetes security best practices assessments, audits, incident response, continuous monitoring, hardening and forensics readiness, and also remediations! We have Prowler CLI (Command Line Interface) that we call Prowler Open Source and a service on top of it that we call Prowler SaaS. +**Prowler** is an Open Source security tool to perform AWS, Azure, Google Cloud and Kubernetes security best practices assessments, audits, incident response, continuous monitoring, hardening and forensics readiness, and also remediations! We have Prowler CLI (Command Line Interface) that we call Prowler Open Source and a service on top of it that we call Prowler Cloud. ## Prowler App @@ -29,7 +29,7 @@ It contains hundreds of controls covering CIS, NIST 800, NIST CSF, CISA, RBI, Fe Prowler App can be installed in different ways, depending on your environment: -> See how to use Prowler App in the [Prowler App](tutorials/prowler-app.md) section. +> See how to use Prowler App in the [Prowler App Tutorial](tutorials/prowler-app.md) section. === "Docker Compose" @@ -65,6 +65,9 @@ Prowler App can be installed in different ways, depending on your environment: * `npm` installed: [npm installation](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm). * `Docker Compose` installed: https://docs.docker.com/compose/install/. + ???+ warning + Make sure to have `api/.env` and `ui/.env.local` files with the required environment variables. You can find the required environment variables in the [`api/.env.template`](https://github.com/prowler-cloud/prowler/blob/master/api/.env.example) and [`ui/.env.template`](https://github.com/prowler-cloud/prowler/blob/master/ui/.env.template) files. + _Commands to run the API_: ``` bash @@ -95,6 +98,19 @@ Prowler App can be installed in different ways, depending on your environment: python -m celery -A config.celery worker -l info -E ``` + _Commands to run the API Scheduler_: + + ``` bash + git clone https://github.com/prowler-cloud/prowler \ + cd prowler/api \ + poetry install \ + poetry shell \ + set -a \ + source .env \ + cd src/backend \ + python -m celery -A config.celery beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler + ``` + _Commands to run the UI_: ``` bash @@ -107,9 +123,6 @@ Prowler App can be installed in different ways, depending on your environment: > Enjoy Prowler App at http://localhost:3000 by signing up with your email and password. - ???+ warning - Make sure to have `api/.env` and `ui/.env.local` files with the required environment variables. You can find the required environment variables in the [`api/.env.template`](https://github.com/prowler-cloud/prowler/blob/master/api/.env.example) and [`ui/.env.template`](https://github.com/prowler-cloud/prowler/blob/master/ui/.env.template) files. - ???+ warning Google and GitHub authentication is only available in [Prowler Cloud](https://prowler.com). @@ -373,8 +386,8 @@ After successfully adding and testing your credentials, Prowler will start scann #### **View Results** While the scan is running, start exploring the findings in these sections: -- **Overview**: High-level summary of the scans. Overview -- **Compliance**: Insights into compliance status. Compliance +- **Overview**: High-level summary of the scans. Overview +- **Compliance**: Insights into compliance status. Compliance > See more details about the Prowler App usage in the [Prowler App](tutorials/prowler-app.md) section. diff --git a/docs/security.md b/docs/security.md index ef85c357a8..207b565378 100644 --- a/docs/security.md +++ b/docs/security.md @@ -13,7 +13,7 @@ As an **AWS Partner** and we have passed the [AWS Foundation Technical Review (F ## Reporting Vulnerabilities -If you would like to report a vulnerability or have a security concern regarding Prowler Open Source or Prowler SaaS service, please submit the information by contacting to us via [**support.prowler.com**](http://support.prowler.com). +If you would like to report a vulnerability or have a security concern regarding Prowler Open Source or Prowler Cloud service, please submit the information by contacting to us via [**support.prowler.com**](http://support.prowler.com). The information you share with the Prowler team as part of this process is kept confidential within Prowler. We will only share this information with a third party if the vulnerability you report is found to affect a third-party product, in which case we will share this information with the third-party product's author or manufacturer. Otherwise, we will only share this information as permitted by you. diff --git a/docs/tutorials/compliance.md b/docs/tutorials/compliance.md index 629aec9b06..b3424dbf27 100644 --- a/docs/tutorials/compliance.md +++ b/docs/tutorials/compliance.md @@ -22,32 +22,31 @@ In order to see which compliance frameworks are cover by Prowler, you can use op ```sh prowler --list-compliance ``` -Currently, the available frameworks are: + +### AWS - `aws_account_security_onboarding_aws` - `aws_audit_manager_control_tower_guardrails_aws` - `aws_foundational_security_best_practices_aws` +- `aws_foundational_technical_review_aws` - `aws_well_architected_framework_reliability_pillar_aws` - `aws_well_architected_framework_security_pillar_aws` - `cis_1.4_aws` - `cis_1.5_aws` - `cis_2.0_aws` -- `cis_2.0_gcp` -- `cis_2.0_azure` -- `cis_2.1_azure` - `cis_3.0_aws` -- `cis_1.8_kubernetes` - `cisa_aws` - `ens_rd2022_aws` - `fedramp_low_revision_4_aws` - `fedramp_moderate_revision_4_aws` - `ffiec_aws` -- `aws_foundational_technical_review_aws` - `gdpr_aws` - `gxp_21_cfr_part_11_aws` - `gxp_eu_annex_11_aws` - `hipaa_aws` - `iso27001_2013_aws` +- `kisa_isms_p_2023_aws` +- `kisa_isms_p_2023_korean_aws` - `mitre_attack_aws` - `nist_800_171_revision_2_aws` - `nist_800_53_revision_4_aws` @@ -57,6 +56,23 @@ Currently, the available frameworks are: - `rbi_cyber_security_framework_aws` - `soc2_aws` +### Azure + +- `cis_2.0_azure` +- `cis_2.1_azure` +- `ens_rd2022_azure` +- `mitre_attack_azure` + +### GCP + +- `cis_2.0_gcp` +- `ens_rd2022_gcp` +- `mitre_attack_gcp` + +### Kubernetes + +- `cis_1.8_kubernetes` + ## List Requirements of Compliance Frameworks For each compliance framework, you can use option `--list-compliance-requirements` to list its requirements: ```sh diff --git a/docs/tutorials/configuration_file.md b/docs/tutorials/configuration_file.md index 866c7f8dff..b638728b27 100644 --- a/docs/tutorials/configuration_file.md +++ b/docs/tutorials/configuration_file.md @@ -75,6 +75,7 @@ The following list includes all the Azure checks with configurable variables tha | `app_ensure_php_version_is_latest` | `php_latest_version` | String | | `app_ensure_python_version_is_latest` | `python_latest_version` | String | | `app_ensure_java_version_is_latest` | `java_latest_version` | String | +| `sqlserver_recommended_minimal_tls_version` | `recommended_minimal_tls_versions` | List of Strings | ## GCP @@ -447,6 +448,14 @@ azure: # azure.app_ensure_java_version_is_latest java_latest_version: "17" + # Azure SQL Server + # azure.sqlserver_minimal_tls_version + recommended_minimal_tls_versions: + [ + "1.2", + "1.3" + ] + # GCP Configuration gcp: # GCP Compute Configuration diff --git a/docs/tutorials/prowler-app.md b/docs/tutorials/prowler-app.md index 9133c8fe7b..e08fceb29b 100644 --- a/docs/tutorials/prowler-app.md +++ b/docs/tutorials/prowler-app.md @@ -5,6 +5,9 @@ The **Prowler App** is a user-friendly interface for the Prowler CLI, providing After [installing](../index.md#prowler-app-installation) the **Prowler App**, access it at [http://localhost:3000](http://localhost:3000). You can also access to the auto-generated **Prowler API** documentation at [http://localhost:8080/api/v1/docs](http://localhost:8080/api/v1/docs) to see all the available endpoints, parameters and responses. +???+ note + If you are a [Prowler Cloud](https://cloud.prowler.com/sign-in) user you can see API docs at [https://api.prowler.com/api/v1/docs](https://api.prowler.com/api/v1/docs) + ## **Step 1: Sign Up** To get started, sign up using your email and password: diff --git a/mkdocs.yml b/mkdocs.yml index e311146609..dd04e16881 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -34,7 +34,6 @@ theme: icon: material/weather-sunny name: Switch to light mode - plugins: - search - git-revision-date-localized: @@ -112,7 +111,7 @@ nav: - Contact Us: contact.md - Troubleshooting: troubleshooting.md - About: about.md - - Prowler SaaS: https://prowler.com + - Prowler Cloud: https://prowler.com # Customization extra: diff --git a/poetry.lock b/poetry.lock index 3261d5eaa4..c48363bcb1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -775,17 +775,17 @@ files = [ [[package]] name = "boto3" -version = "1.35.71" +version = "1.35.78" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" files = [ - {file = "boto3-1.35.71-py3-none-any.whl", hash = "sha256:e2969a246bb3208122b3c349c49cc6604c6fc3fc2b2f65d99d3e8ccd745b0c16"}, - {file = "boto3-1.35.71.tar.gz", hash = "sha256:3ed7172b3d4fceb6218bb0ec3668c4d40c03690939c2fca4f22bb875d741a07f"}, + {file = "boto3-1.35.78-py3-none-any.whl", hash = "sha256:5ef7166fe5060637b92af8dc152cd7acecf96b3fc9c5456706a886cadb534391"}, + {file = "boto3-1.35.78.tar.gz", hash = "sha256:fc8001519c8842e766ad3793bde3fbd0bb39e821a582fc12cf67876b8f3cf7f1"}, ] [package.dependencies] -botocore = ">=1.35.71,<1.36.0" +botocore = ">=1.35.78,<1.36.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -794,13 +794,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.35.71" +version = "1.35.79" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" files = [ - {file = "botocore-1.35.71-py3-none-any.whl", hash = "sha256:fc46e7ab1df3cef66dfba1633f4da77c75e07365b36f03bd64a3793634be8fc1"}, - {file = "botocore-1.35.71.tar.gz", hash = "sha256:f9fa058e0393660c3fe53c1e044751beb64b586def0bd2212448a7c328b0cbba"}, + {file = "botocore-1.35.79-py3-none-any.whl", hash = "sha256:e6b10bb9a357e3f5ca2e60f6dd15a85d311b9a476eb21b3c0c2a3b364a2897c8"}, + {file = "botocore-1.35.79.tar.gz", hash = "sha256:245bfdda1b1508539ddd1819c67a8a2cc81780adf0715d3de418d64c4247f346"}, ] [package.dependencies] @@ -1099,73 +1099,73 @@ files = [ [[package]] name = "coverage" -version = "7.6.8" +version = "7.6.9" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" files = [ - {file = "coverage-7.6.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b39e6011cd06822eb964d038d5dff5da5d98652b81f5ecd439277b32361a3a50"}, - {file = "coverage-7.6.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:63c19702db10ad79151a059d2d6336fe0c470f2e18d0d4d1a57f7f9713875dcf"}, - {file = "coverage-7.6.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3985b9be361d8fb6b2d1adc9924d01dec575a1d7453a14cccd73225cb79243ee"}, - {file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:644ec81edec0f4ad17d51c838a7d01e42811054543b76d4ba2c5d6af741ce2a6"}, - {file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f188a2402f8359cf0c4b1fe89eea40dc13b52e7b4fd4812450da9fcd210181d"}, - {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e19122296822deafce89a0c5e8685704c067ae65d45e79718c92df7b3ec3d331"}, - {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:13618bed0c38acc418896005732e565b317aa9e98d855a0e9f211a7ffc2d6638"}, - {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:193e3bffca48ad74b8c764fb4492dd875038a2f9925530cb094db92bb5e47bed"}, - {file = "coverage-7.6.8-cp310-cp310-win32.whl", hash = "sha256:3988665ee376abce49613701336544041f2117de7b7fbfe91b93d8ff8b151c8e"}, - {file = "coverage-7.6.8-cp310-cp310-win_amd64.whl", hash = "sha256:f56f49b2553d7dd85fd86e029515a221e5c1f8cb3d9c38b470bc38bde7b8445a"}, - {file = "coverage-7.6.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:86cffe9c6dfcfe22e28027069725c7f57f4b868a3f86e81d1c62462764dc46d4"}, - {file = "coverage-7.6.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d82ab6816c3277dc962cfcdc85b1efa0e5f50fb2c449432deaf2398a2928ab94"}, - {file = "coverage-7.6.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13690e923a3932e4fad4c0ebfb9cb5988e03d9dcb4c5150b5fcbf58fd8bddfc4"}, - {file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4be32da0c3827ac9132bb488d331cb32e8d9638dd41a0557c5569d57cf22c9c1"}, - {file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44e6c85bbdc809383b509d732b06419fb4544dca29ebe18480379633623baafb"}, - {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:768939f7c4353c0fac2f7c37897e10b1414b571fd85dd9fc49e6a87e37a2e0d8"}, - {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e44961e36cb13c495806d4cac67640ac2866cb99044e210895b506c26ee63d3a"}, - {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ea8bb1ab9558374c0ab591783808511d135a833c3ca64a18ec927f20c4030f0"}, - {file = "coverage-7.6.8-cp311-cp311-win32.whl", hash = "sha256:629a1ba2115dce8bf75a5cce9f2486ae483cb89c0145795603d6554bdc83e801"}, - {file = "coverage-7.6.8-cp311-cp311-win_amd64.whl", hash = "sha256:fb9fc32399dca861584d96eccd6c980b69bbcd7c228d06fb74fe53e007aa8ef9"}, - {file = "coverage-7.6.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e683e6ecc587643f8cde8f5da6768e9d165cd31edf39ee90ed7034f9ca0eefee"}, - {file = "coverage-7.6.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1defe91d41ce1bd44b40fabf071e6a01a5aa14de4a31b986aa9dfd1b3e3e414a"}, - {file = "coverage-7.6.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7ad66e8e50225ebf4236368cc43c37f59d5e6728f15f6e258c8639fa0dd8e6d"}, - {file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fe47da3e4fda5f1abb5709c156eca207eacf8007304ce3019eb001e7a7204cb"}, - {file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:202a2d645c5a46b84992f55b0a3affe4f0ba6b4c611abec32ee88358db4bb649"}, - {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4674f0daa1823c295845b6a740d98a840d7a1c11df00d1fd62614545c1583787"}, - {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:74610105ebd6f33d7c10f8907afed696e79c59e3043c5f20eaa3a46fddf33b4c"}, - {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37cda8712145917105e07aab96388ae76e787270ec04bcb9d5cc786d7cbb8443"}, - {file = "coverage-7.6.8-cp312-cp312-win32.whl", hash = "sha256:9e89d5c8509fbd6c03d0dd1972925b22f50db0792ce06324ba069f10787429ad"}, - {file = "coverage-7.6.8-cp312-cp312-win_amd64.whl", hash = "sha256:379c111d3558272a2cae3d8e57e6b6e6f4fe652905692d54bad5ea0ca37c5ad4"}, - {file = "coverage-7.6.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb"}, - {file = "coverage-7.6.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63"}, - {file = "coverage-7.6.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365"}, - {file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002"}, - {file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3"}, - {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022"}, - {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e"}, - {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b"}, - {file = "coverage-7.6.8-cp313-cp313-win32.whl", hash = "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146"}, - {file = "coverage-7.6.8-cp313-cp313-win_amd64.whl", hash = "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28"}, - {file = "coverage-7.6.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d"}, - {file = "coverage-7.6.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451"}, - {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764"}, - {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf"}, - {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5"}, - {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4"}, - {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83"}, - {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b"}, - {file = "coverage-7.6.8-cp313-cp313t-win32.whl", hash = "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71"}, - {file = "coverage-7.6.8-cp313-cp313t-win_amd64.whl", hash = "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc"}, - {file = "coverage-7.6.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ac47fa29d8d41059ea3df65bd3ade92f97ee4910ed638e87075b8e8ce69599e"}, - {file = "coverage-7.6.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:24eda3a24a38157eee639ca9afe45eefa8d2420d49468819ac5f88b10de84f4c"}, - {file = "coverage-7.6.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4c81ed2820b9023a9a90717020315e63b17b18c274a332e3b6437d7ff70abe0"}, - {file = "coverage-7.6.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd55f8fc8fa494958772a2a7302b0354ab16e0b9272b3c3d83cdb5bec5bd1779"}, - {file = "coverage-7.6.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f39e2f3530ed1626c66e7493be7a8423b023ca852aacdc91fb30162c350d2a92"}, - {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:716a78a342679cd1177bc8c2fe957e0ab91405bd43a17094324845200b2fddf4"}, - {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:177f01eeaa3aee4a5ffb0d1439c5952b53d5010f86e9d2667963e632e30082cc"}, - {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:912e95017ff51dc3d7b6e2be158dedc889d9a5cc3382445589ce554f1a34c0ea"}, - {file = "coverage-7.6.8-cp39-cp39-win32.whl", hash = "sha256:4db3ed6a907b555e57cc2e6f14dc3a4c2458cdad8919e40b5357ab9b6db6c43e"}, - {file = "coverage-7.6.8-cp39-cp39-win_amd64.whl", hash = "sha256:428ac484592f780e8cd7b6b14eb568f7c85460c92e2a37cb0c0e5186e1a0d076"}, - {file = "coverage-7.6.8-pp39.pp310-none-any.whl", hash = "sha256:5c52a036535d12590c32c49209e79cabaad9f9ad8aa4cbd875b68c4d67a9cbce"}, - {file = "coverage-7.6.8.tar.gz", hash = "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc"}, + {file = "coverage-7.6.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85d9636f72e8991a1706b2b55b06c27545448baf9f6dbf51c4004609aacd7dcb"}, + {file = "coverage-7.6.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:608a7fd78c67bee8936378299a6cb9f5149bb80238c7a566fc3e6717a4e68710"}, + {file = "coverage-7.6.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96d636c77af18b5cb664ddf12dab9b15a0cfe9c0bde715da38698c8cea748bfa"}, + {file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75cded8a3cff93da9edc31446872d2997e327921d8eed86641efafd350e1df1"}, + {file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7b15f589593110ae767ce997775d645b47e5cbbf54fd322f8ebea6277466cec"}, + {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:44349150f6811b44b25574839b39ae35291f6496eb795b7366fef3bd3cf112d3"}, + {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d891c136b5b310d0e702e186d70cd16d1119ea8927347045124cb286b29297e5"}, + {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:db1dab894cc139f67822a92910466531de5ea6034ddfd2b11c0d4c6257168073"}, + {file = "coverage-7.6.9-cp310-cp310-win32.whl", hash = "sha256:41ff7b0da5af71a51b53f501a3bac65fb0ec311ebed1632e58fc6107f03b9198"}, + {file = "coverage-7.6.9-cp310-cp310-win_amd64.whl", hash = "sha256:35371f8438028fdccfaf3570b31d98e8d9eda8bb1d6ab9473f5a390969e98717"}, + {file = "coverage-7.6.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:932fc826442132dde42ee52cf66d941f581c685a6313feebed358411238f60f9"}, + {file = "coverage-7.6.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:085161be5f3b30fd9b3e7b9a8c301f935c8313dcf928a07b116324abea2c1c2c"}, + {file = "coverage-7.6.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccc660a77e1c2bf24ddbce969af9447a9474790160cfb23de6be4fa88e3951c7"}, + {file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c69e42c892c018cd3c8d90da61d845f50a8243062b19d228189b0224150018a9"}, + {file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0824a28ec542a0be22f60c6ac36d679e0e262e5353203bea81d44ee81fe9c6d4"}, + {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4401ae5fc52ad8d26d2a5d8a7428b0f0c72431683f8e63e42e70606374c311a1"}, + {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98caba4476a6c8d59ec1eb00c7dd862ba9beca34085642d46ed503cc2d440d4b"}, + {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ee5defd1733fd6ec08b168bd4f5387d5b322f45ca9e0e6c817ea6c4cd36313e3"}, + {file = "coverage-7.6.9-cp311-cp311-win32.whl", hash = "sha256:f2d1ec60d6d256bdf298cb86b78dd715980828f50c46701abc3b0a2b3f8a0dc0"}, + {file = "coverage-7.6.9-cp311-cp311-win_amd64.whl", hash = "sha256:0d59fd927b1f04de57a2ba0137166d31c1a6dd9e764ad4af552912d70428c92b"}, + {file = "coverage-7.6.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:99e266ae0b5d15f1ca8d278a668df6f51cc4b854513daab5cae695ed7b721cf8"}, + {file = "coverage-7.6.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9901d36492009a0a9b94b20e52ebfc8453bf49bb2b27bca2c9706f8b4f5a554a"}, + {file = "coverage-7.6.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abd3e72dd5b97e3af4246cdada7738ef0e608168de952b837b8dd7e90341f015"}, + {file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff74026a461eb0660366fb01c650c1d00f833a086b336bdad7ab00cc952072b3"}, + {file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65dad5a248823a4996724a88eb51d4b31587aa7aa428562dbe459c684e5787ae"}, + {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:22be16571504c9ccea919fcedb459d5ab20d41172056206eb2994e2ff06118a4"}, + {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f957943bc718b87144ecaee70762bc2bc3f1a7a53c7b861103546d3a403f0a6"}, + {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ae1387db4aecb1f485fb70a6c0148c6cdaebb6038f1d40089b1fc84a5db556f"}, + {file = "coverage-7.6.9-cp312-cp312-win32.whl", hash = "sha256:1a330812d9cc7ac2182586f6d41b4d0fadf9be9049f350e0efb275c8ee8eb692"}, + {file = "coverage-7.6.9-cp312-cp312-win_amd64.whl", hash = "sha256:b12c6b18269ca471eedd41c1b6a1065b2f7827508edb9a7ed5555e9a56dcfc97"}, + {file = "coverage-7.6.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:899b8cd4781c400454f2f64f7776a5d87bbd7b3e7f7bda0cb18f857bb1334664"}, + {file = "coverage-7.6.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:61f70dc68bd36810972e55bbbe83674ea073dd1dcc121040a08cdf3416c5349c"}, + {file = "coverage-7.6.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a289d23d4c46f1a82d5db4abeb40b9b5be91731ee19a379d15790e53031c014"}, + {file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e216d8044a356fc0337c7a2a0536d6de07888d7bcda76febcb8adc50bdbbd00"}, + {file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c026eb44f744acaa2bda7493dad903aa5bf5fc4f2554293a798d5606710055d"}, + {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e77363e8425325384f9d49272c54045bbed2f478e9dd698dbc65dbc37860eb0a"}, + {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:777abfab476cf83b5177b84d7486497e034eb9eaea0d746ce0c1268c71652077"}, + {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:447af20e25fdbe16f26e84eb714ba21d98868705cb138252d28bc400381f6ffb"}, + {file = "coverage-7.6.9-cp313-cp313-win32.whl", hash = "sha256:d872ec5aeb086cbea771c573600d47944eea2dcba8be5f3ee649bfe3cb8dc9ba"}, + {file = "coverage-7.6.9-cp313-cp313-win_amd64.whl", hash = "sha256:fd1213c86e48dfdc5a0cc676551db467495a95a662d2396ecd58e719191446e1"}, + {file = "coverage-7.6.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ba9e7484d286cd5a43744e5f47b0b3fb457865baf07bafc6bee91896364e1419"}, + {file = "coverage-7.6.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e5ea1cf0872ee455c03e5674b5bca5e3e68e159379c1af0903e89f5eba9ccc3a"}, + {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d10e07aa2b91835d6abec555ec8b2733347956991901eea6ffac295f83a30e4"}, + {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13a9e2d3ee855db3dd6ea1ba5203316a1b1fd8eaeffc37c5b54987e61e4194ae"}, + {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c38bf15a40ccf5619fa2fe8f26106c7e8e080d7760aeccb3722664c8656b030"}, + {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d5275455b3e4627c8e7154feaf7ee0743c2e7af82f6e3b561967b1cca755a0be"}, + {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8f8770dfc6e2c6a2d4569f411015c8d751c980d17a14b0530da2d7f27ffdd88e"}, + {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8d2dfa71665a29b153a9681edb1c8d9c1ea50dfc2375fb4dac99ea7e21a0bcd9"}, + {file = "coverage-7.6.9-cp313-cp313t-win32.whl", hash = "sha256:5e6b86b5847a016d0fbd31ffe1001b63355ed309651851295315031ea7eb5a9b"}, + {file = "coverage-7.6.9-cp313-cp313t-win_amd64.whl", hash = "sha256:97ddc94d46088304772d21b060041c97fc16bdda13c6c7f9d8fcd8d5ae0d8611"}, + {file = "coverage-7.6.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:adb697c0bd35100dc690de83154627fbab1f4f3c0386df266dded865fc50a902"}, + {file = "coverage-7.6.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:be57b6d56e49c2739cdf776839a92330e933dd5e5d929966fbbd380c77f060be"}, + {file = "coverage-7.6.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1592791f8204ae9166de22ba7e6705fa4ebd02936c09436a1bb85aabca3e599"}, + {file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e12ae8cc979cf83d258acb5e1f1cf2f3f83524d1564a49d20b8bec14b637f08"}, + {file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb5555cff66c4d3d6213a296b360f9e1a8e323e74e0426b6c10ed7f4d021e464"}, + {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b9389a429e0e5142e69d5bf4a435dd688c14478a19bb901735cdf75e57b13845"}, + {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:592ac539812e9b46046620341498caf09ca21023c41c893e1eb9dbda00a70cbf"}, + {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a27801adef24cc30871da98a105f77995e13a25a505a0161911f6aafbd66e678"}, + {file = "coverage-7.6.9-cp39-cp39-win32.whl", hash = "sha256:8e3c3e38930cfb729cb8137d7f055e5a473ddaf1217966aa6238c88bd9fd50e6"}, + {file = "coverage-7.6.9-cp39-cp39-win_amd64.whl", hash = "sha256:e28bf44afa2b187cc9f41749138a64435bf340adfcacb5b2290c070ce99839d4"}, + {file = "coverage-7.6.9-pp39.pp310-none-any.whl", hash = "sha256:f3ca78518bc6bc92828cd11867b121891d75cae4ea9e908d72030609b996db1b"}, + {file = "coverage-7.6.9.tar.gz", hash = "sha256:4a8d8977b0c6ef5aeadcb644da9e69ae0dcfe66ec7f368c89c72e058bd71164d"}, ] [package.dependencies] @@ -1719,13 +1719,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] [[package]] name = "google-api-python-client" -version = "2.154.0" +version = "2.155.0" description = "Google API Client Library for Python" optional = false python-versions = ">=3.7" files = [ - {file = "google_api_python_client-2.154.0-py2.py3-none-any.whl", hash = "sha256:a521bbbb2ec0ba9d6f307cdd64ed6e21eeac372d1bd7493a4ab5022941f784ad"}, - {file = "google_api_python_client-2.154.0.tar.gz", hash = "sha256:1b420062e03bfcaa1c79e2e00a612d29a6a934151ceb3d272fe150a656dc8f17"}, + {file = "google_api_python_client-2.155.0-py2.py3-none-any.whl", hash = "sha256:83fe9b5aa4160899079d7c93a37be306546a17e6686e2549bcc9584f1a229747"}, + {file = "google_api_python_client-2.155.0.tar.gz", hash = "sha256:25529f89f0d13abcf3c05c089c423fb2858ac16e0b3727543393468d0d7af67c"}, ] [package.dependencies] @@ -2403,13 +2403,13 @@ files = [ [[package]] name = "microsoft-kiota-abstractions" -version = "1.6.2" +version = "1.6.6" description = "Core abstractions for kiota generated libraries in Python" optional = false python-versions = "<4.0,>=3.8" files = [ - {file = "microsoft_kiota_abstractions-1.6.2-py3-none-any.whl", hash = "sha256:8c2c777748e80f17dba3809b5d149585d9918198f0f94125e87432f7165ba80e"}, - {file = "microsoft_kiota_abstractions-1.6.2.tar.gz", hash = "sha256:dec30f0fb427a051003e94b5c6fcf266f4702ecbd9d6961e3966124b9cbe41bf"}, + {file = "microsoft_kiota_abstractions-1.6.6-py3-none-any.whl", hash = "sha256:29071715baf0d604c381c5d17be47f35e6e63a441dcfb5e9141963406b469d50"}, + {file = "microsoft_kiota_abstractions-1.6.6.tar.gz", hash = "sha256:2554495b00c9c25b43f6964a71b65c89a277bd6b50f4d0028a7febcec6c4fd67"}, ] [package.dependencies] @@ -2583,13 +2583,13 @@ dev = ["click", "codecov", "mkdocs-gen-files", "mkdocs-git-authors-plugin", "mkd [[package]] name = "mkdocs-material" -version = "9.5.46" +version = "9.5.48" description = "Documentation that simply works" optional = false python-versions = ">=3.8" files = [ - {file = "mkdocs_material-9.5.46-py3-none-any.whl", hash = "sha256:98f0a2039c62e551a68aad0791a8d41324ff90c03a6e6cea381a384b84908b83"}, - {file = "mkdocs_material-9.5.46.tar.gz", hash = "sha256:ae2043f4238e572f9a40e0b577f50400d6fc31e2fef8ea141800aebf3bd273d7"}, + {file = "mkdocs_material-9.5.48-py3-none-any.whl", hash = "sha256:b695c998f4b939ce748adbc0d3bff73fa886a670ece948cf27818fa115dc16f8"}, + {file = "mkdocs_material-9.5.48.tar.gz", hash = "sha256:a582531e8b34f4c7ed38c29d5c44763053832cf2a32f7409567e0c74749a47db"}, ] [package.dependencies] @@ -2769,13 +2769,13 @@ dev = ["bumpver", "isort", "mypy", "pylint", "pytest", "yapf"] [[package]] name = "msgraph-sdk" -version = "1.12.0" +version = "1.14.0" description = "The Microsoft Graph Python SDK" optional = false python-versions = ">=3.8" files = [ - {file = "msgraph_sdk-1.12.0-py3-none-any.whl", hash = "sha256:ac298b546b240391b0e407379d039db32862a56d6fe15cf7c5f7e77631fc6771"}, - {file = "msgraph_sdk-1.12.0.tar.gz", hash = "sha256:fbb5a8a9f6eed89b496f207eb35b6b4cfc7fefa75608aeef07477a3b2276d4fa"}, + {file = "msgraph_sdk-1.14.0-py3-none-any.whl", hash = "sha256:1a2f327dc8fbe5a5e6d0d84cf71d605e7b118b3066b1e16f011ccd8fd927bb03"}, + {file = "msgraph_sdk-1.14.0.tar.gz", hash = "sha256:5bbda80941c5d1794682753b8b291bd2ebed719a43d6de949fd0cd613b6dfbbd"}, ] [package.dependencies] @@ -3796,17 +3796,17 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pylint" -version = "3.3.1" +version = "3.3.2" description = "python code static checker" optional = false python-versions = ">=3.9.0" files = [ - {file = "pylint-3.3.1-py3-none-any.whl", hash = "sha256:2f846a466dd023513240bc140ad2dd73bfc080a5d85a710afdb728c420a5a2b9"}, - {file = "pylint-3.3.1.tar.gz", hash = "sha256:9f3dcc87b1203e612b78d91a896407787e708b3f189b5fa0b307712d49ff0c6e"}, + {file = "pylint-3.3.2-py3-none-any.whl", hash = "sha256:77f068c287d49b8683cd7c6e624243c74f92890f767f106ffa1ddf3c0a54cb7a"}, + {file = "pylint-3.3.2.tar.gz", hash = "sha256:9ec054ec992cd05ad30a6df1676229739a73f8feeabf3912c995d17601052b01"}, ] [package.dependencies] -astroid = ">=3.3.4,<=3.4.0-dev0" +astroid = ">=3.3.5,<=3.4.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ {version = ">=0.2", markers = "python_version < \"3.11\""}, @@ -3858,13 +3858,13 @@ diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pytest" -version = "8.3.3" +version = "8.3.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, - {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, + {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, + {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, ] [package.dependencies] @@ -4458,6 +4458,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6"}, @@ -4466,6 +4467,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632"}, @@ -4474,6 +4476,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a"}, @@ -4482,6 +4485,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987"}, @@ -4490,6 +4494,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2c59aa6170b990d8d2719323e628aaf36f3bfbc1c26279c0eeeb24d05d2d11c7"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win32.whl", hash = "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b"}, {file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"}, @@ -4638,17 +4643,17 @@ files = [ [[package]] name = "slack-sdk" -version = "3.33.4" +version = "3.33.5" description = "The Slack API Platform SDK for Python" optional = false python-versions = ">=3.6" files = [ - {file = "slack_sdk-3.33.4-py2.py3-none-any.whl", hash = "sha256:9f30cb3c9c07b441c49d53fc27f9f1837ad1592a7e9d4ca431f53cdad8826cc6"}, - {file = "slack_sdk-3.33.4.tar.gz", hash = "sha256:5e109847f6b6a22d227609226ba4ed936109dc00675bddeb7e0bee502d3ee7e0"}, + {file = "slack_sdk-3.33.5-py2.py3-none-any.whl", hash = "sha256:b8cccadfa3d4005a5e6529f52000d25c583f46173fda8e9136fdd2bc58923ff6"}, + {file = "slack_sdk-3.33.5.tar.gz", hash = "sha256:a5e74c00c99dc844ad93e501ab764a20d86fa8184bbc9432af217496f632c4ee"}, ] [package.extras] -optional = ["SQLAlchemy (>=1.4,<3)", "aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "websocket-client (>=1,<2)", "websockets (>=9.1,<14)"] +optional = ["SQLAlchemy (>=1.4,<3)", "aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "websocket-client (>=1,<2)", "websockets (>=9.1,<15)"] [[package]] name = "smmap" @@ -4888,13 +4893,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "vulture" -version = "2.13" +version = "2.14" description = "Find dead code" optional = false python-versions = ">=3.8" files = [ - {file = "vulture-2.13-py2.py3-none-any.whl", hash = "sha256:34793ba60488e7cccbecdef3a7fe151656372ef94fdac9fe004c52a4000a6d44"}, - {file = "vulture-2.13.tar.gz", hash = "sha256:78248bf58f5eaffcc2ade306141ead73f437339950f80045dce7f8b078e5a1aa"}, + {file = "vulture-2.14-py2.py3-none-any.whl", hash = "sha256:d9a90dba89607489548a49d557f8bac8112bd25d3cbc8aeef23e860811bd5ed9"}, + {file = "vulture-2.14.tar.gz", hash = "sha256:cb8277902a1138deeab796ec5bef7076a6e0248ca3607a3f3dee0b6d9e9b8415"}, ] [package.dependencies] @@ -5194,4 +5199,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.13" -content-hash = "1acc901866ecfc2c0f3576b9e442d7a3b6e6522cac3d4d1b9301ed4232755cba" +content-hash = "e00da6013a01923ac8e79017e7fdb221e09a3dcf581ad8d74e39550be64cc2f3" diff --git a/prowler/config/config.py b/prowler/config/config.py index 7771c4940b..bf018abb01 100644 --- a/prowler/config/config.py +++ b/prowler/config/config.py @@ -12,7 +12,7 @@ from prowler.lib.logger import logger timestamp = datetime.today() timestamp_utc = datetime.now(timezone.utc).replace(tzinfo=timezone.utc) -prowler_version = "5.0.0" +prowler_version = "5.1.0" html_logo_url = "https://github.com/prowler-cloud/prowler/" square_logo_img = "https://prowler.com/wp-content/uploads/logo-html.png" aws_logo = "https://user-images.githubusercontent.com/38561120/235953920-3e3fba08-0795-41dc-b480-9bea57db9f2e.png" diff --git a/prowler/config/config.yaml b/prowler/config/config.yaml index 8b6947b458..f9641eba56 100644 --- a/prowler/config/config.yaml +++ b/prowler/config/config.yaml @@ -388,6 +388,14 @@ azure: # azure.app_ensure_java_version_is_latest java_latest_version: "17" + # Azure SQL Server + # azure.sqlserver_minimal_tls_version + recommended_minimal_tls_versions: + [ + "1.2", + "1.3", + ] + # GCP Configuration gcp: # GCP Compute Configuration diff --git a/prowler/lib/outputs/compliance/cis/cis.py b/prowler/lib/outputs/compliance/cis/cis.py index 7ab1a7d96c..30a68b2956 100644 --- a/prowler/lib/outputs/compliance/cis/cis.py +++ b/prowler/lib/outputs/compliance/cis/cis.py @@ -94,11 +94,12 @@ def get_cis_table( print( f"\nCompliance Status of {Fore.YELLOW}{compliance_framework.upper()}{Style.RESET_ALL} Framework:" ) + total_findings_count = len(fail_count) + len(pass_count) + len(muted_count) overview_table = [ [ - f"{Fore.RED}{round(len(fail_count) / len(findings) * 100, 2)}% ({len(fail_count)}) FAIL{Style.RESET_ALL}", - f"{Fore.GREEN}{round(len(pass_count) / len(findings) * 100, 2)}% ({len(pass_count)}) PASS{Style.RESET_ALL}", - f"{orange_color}{round(len(muted_count) / len(findings) * 100, 2)}% ({len(muted_count)}) MUTED{Style.RESET_ALL}", + f"{Fore.RED}{round(len(fail_count) / total_findings_count * 100, 2)}% ({len(fail_count)}) FAIL{Style.RESET_ALL}", + f"{Fore.GREEN}{round(len(pass_count) / total_findings_count * 100, 2)}% ({len(pass_count)}) PASS{Style.RESET_ALL}", + f"{orange_color}{round(len(muted_count) / total_findings_count * 100, 2)}% ({len(muted_count)}) MUTED{Style.RESET_ALL}", ] ] print(tabulate(overview_table, tablefmt="rounded_grid")) diff --git a/prowler/lib/outputs/compliance/ens/ens.py b/prowler/lib/outputs/compliance/ens/ens.py index 64107f7ff7..a414a0206d 100644 --- a/prowler/lib/outputs/compliance/ens/ens.py +++ b/prowler/lib/outputs/compliance/ens/ens.py @@ -95,11 +95,12 @@ def get_ens_table( print( f"\nEstado de Cumplimiento de {Fore.YELLOW}{compliance_framework.upper()}{Style.RESET_ALL}:" ) + total_findings_count = len(fail_count) + len(pass_count) + len(muted_count) overview_table = [ [ - f"{Fore.RED}{round(len(fail_count) / len(findings) * 100, 2)}% ({len(fail_count)}) NO CUMPLE{Style.RESET_ALL}", - f"{Fore.GREEN}{round(len(pass_count) / len(findings) * 100, 2)}% ({len(pass_count)}) CUMPLE{Style.RESET_ALL}", - f"{orange_color}{round(len(muted_count) / len(findings) * 100, 2)}% ({len(muted_count)}) MUTED{Style.RESET_ALL}", + f"{Fore.RED}{round(len(fail_count) / total_findings_count * 100, 2)}% ({len(fail_count)}) NO CUMPLE{Style.RESET_ALL}", + f"{Fore.GREEN}{round(len(pass_count) / total_findings_count * 100, 2)}% ({len(pass_count)}) CUMPLE{Style.RESET_ALL}", + f"{orange_color}{round(len(muted_count) / total_findings_count * 100, 2)}% ({len(muted_count)}) MUTED{Style.RESET_ALL}", ] ] print(tabulate(overview_table, tablefmt="rounded_grid")) diff --git a/prowler/lib/outputs/compliance/generic/generic_table.py b/prowler/lib/outputs/compliance/generic/generic_table.py index 570c6c9475..bede199eae 100644 --- a/prowler/lib/outputs/compliance/generic/generic_table.py +++ b/prowler/lib/outputs/compliance/generic/generic_table.py @@ -39,11 +39,12 @@ def get_generic_compliance_table( print( f"\nCompliance Status of {Fore.YELLOW}{compliance_framework.upper()}{Style.RESET_ALL} Framework:" ) + total_findings_count = len(fail_count) + len(pass_count) + len(muted_count) overview_table = [ [ - f"{Fore.RED}{round(len(fail_count) / len(findings) * 100, 2)}% ({len(fail_count)}) FAIL{Style.RESET_ALL}", - f"{Fore.GREEN}{round(len(pass_count) / len(findings) * 100, 2)}% ({len(pass_count)}) PASS{Style.RESET_ALL}", - f"{orange_color}{round(len(muted_count) / len(findings) * 100, 2)}% ({len(muted_count)}) MUTED{Style.RESET_ALL}", + f"{Fore.RED}{round(len(fail_count) / total_findings_count * 100, 2)}% ({len(fail_count)}) FAIL{Style.RESET_ALL}", + f"{Fore.GREEN}{round(len(pass_count) / total_findings_count * 100, 2)}% ({len(pass_count)}) PASS{Style.RESET_ALL}", + f"{orange_color}{round(len(muted_count) / total_findings_count * 100, 2)}% ({len(muted_count)}) MUTED{Style.RESET_ALL}", ] ] print(tabulate(overview_table, tablefmt="rounded_grid")) diff --git a/prowler/lib/outputs/compliance/kisa_ismsp/kisa_ismsp.py b/prowler/lib/outputs/compliance/kisa_ismsp/kisa_ismsp.py index 8bfbff5b54..bf0a1b6e6f 100644 --- a/prowler/lib/outputs/compliance/kisa_ismsp/kisa_ismsp.py +++ b/prowler/lib/outputs/compliance/kisa_ismsp/kisa_ismsp.py @@ -61,11 +61,12 @@ def get_kisa_ismsp_table( print( f"\nCompliance Status of {Fore.YELLOW}{compliance_framework.upper()}{Style.RESET_ALL} Framework:" ) + total_findings_count = len(fail_count) + len(pass_count) + len(muted_count) overview_table = [ [ - f"{Fore.RED}{round(len(fail_count) / len(findings) * 100, 2)}% ({len(fail_count)}) FAIL{Style.RESET_ALL}", - f"{Fore.GREEN}{round(len(pass_count) / len(findings) * 100, 2)}% ({len(pass_count)}) PASS{Style.RESET_ALL}", - f"{orange_color}{round(len(muted_count) / len(findings) * 100, 2)}% ({len(muted_count)}) MUTED{Style.RESET_ALL}", + f"{Fore.RED}{round(len(fail_count) / total_findings_count * 100, 2)}% ({len(fail_count)}) FAIL{Style.RESET_ALL}", + f"{Fore.GREEN}{round(len(pass_count) / total_findings_count * 100, 2)}% ({len(pass_count)}) PASS{Style.RESET_ALL}", + f"{orange_color}{round(len(muted_count) / total_findings_count * 100, 2)}% ({len(muted_count)}) MUTED{Style.RESET_ALL}", ] ] print(tabulate(overview_table, tablefmt="rounded_grid")) diff --git a/prowler/lib/outputs/compliance/mitre_attack/mitre_attack.py b/prowler/lib/outputs/compliance/mitre_attack/mitre_attack.py index 24c78e9469..bab3e4e31a 100644 --- a/prowler/lib/outputs/compliance/mitre_attack/mitre_attack.py +++ b/prowler/lib/outputs/compliance/mitre_attack/mitre_attack.py @@ -69,11 +69,12 @@ def get_mitre_attack_table( print( f"\nCompliance Status of {Fore.YELLOW}{compliance_framework.upper()}{Style.RESET_ALL} Framework:" ) + total_findings_count = len(fail_count) + len(pass_count) + len(muted_count) overview_table = [ [ - f"{Fore.RED}{round(len(fail_count) / len(findings) * 100, 2)}% ({len(fail_count)}) FAIL{Style.RESET_ALL}", - f"{Fore.GREEN}{round(len(pass_count) / len(findings) * 100, 2)}% ({len(pass_count)}) PASS{Style.RESET_ALL}", - f"{orange_color}{round(len(muted_count) / len(findings) * 100, 2)}% ({len(muted_count)}) MUTED{Style.RESET_ALL}", + f"{Fore.RED}{round(len(fail_count) / total_findings_count * 100, 2)}% ({len(fail_count)}) FAIL{Style.RESET_ALL}", + f"{Fore.GREEN}{round(len(pass_count) / total_findings_count * 100, 2)}% ({len(pass_count)}) PASS{Style.RESET_ALL}", + f"{orange_color}{round(len(muted_count) / total_findings_count * 100, 2)}% ({len(muted_count)}) MUTED{Style.RESET_ALL}", ] ] print(tabulate(overview_table, tablefmt="rounded_grid")) diff --git a/prowler/providers/aws/aws_regions_by_service.json b/prowler/providers/aws/aws_regions_by_service.json index cfc75a2e06..cea65047cd 100644 --- a/prowler/providers/aws/aws_regions_by_service.json +++ b/prowler/providers/aws/aws_regions_by_service.json @@ -7315,6 +7315,7 @@ "ap-southeast-2", "ap-southeast-3", "ap-southeast-4", + "ap-southeast-5", "ca-central-1", "ca-west-1", "eu-central-1", @@ -11097,6 +11098,7 @@ "ap-southeast-1", "ap-southeast-2", "ap-southeast-3", + "ap-southeast-4", "ca-central-1", "eu-central-1", "eu-central-2", diff --git a/prowler/providers/aws/services/autoscaling/autoscaling_group_launch_configuration_no_public_ip/autoscaling_group_launch_configuration_no_public_ip.py b/prowler/providers/aws/services/autoscaling/autoscaling_group_launch_configuration_no_public_ip/autoscaling_group_launch_configuration_no_public_ip.py index 3d2eefa3b7..178a6748a6 100644 --- a/prowler/providers/aws/services/autoscaling/autoscaling_group_launch_configuration_no_public_ip/autoscaling_group_launch_configuration_no_public_ip.py +++ b/prowler/providers/aws/services/autoscaling/autoscaling_group_launch_configuration_no_public_ip/autoscaling_group_launch_configuration_no_public_ip.py @@ -8,19 +8,20 @@ class autoscaling_group_launch_configuration_no_public_ip(Check): def execute(self): findings = [] for group in autoscaling_client.groups: - report = Check_Report_AWS(self.metadata()) - report.region = group.region - report.resource_id = group.name - report.resource_arn = group.arn - report.resource_tags = group.tags - report.status = "PASS" - report.status_extended = f"Autoscaling group {group.name} does not have an associated launch configuration assigning a public IP address." - for lc in autoscaling_client.launch_configurations.values(): - if lc.name == group.launch_configuration_name and lc.public_ip: - report.status = "FAIL" - report.status_extended = f"Autoscaling group {group.name} has an associated launch configuration assigning a public IP address." + if lc.name == group.launch_configuration_name: + report = Check_Report_AWS(self.metadata()) + report.region = group.region + report.resource_id = group.name + report.resource_arn = group.arn + report.resource_tags = group.tags + report.status = "PASS" + report.status_extended = f"Autoscaling group {group.name} does not have an associated launch configuration assigning a public IP address." - findings.append(report) + if lc.public_ip: + report.status = "FAIL" + report.status_extended = f"Autoscaling group {group.name} has an associated launch configuration assigning a public IP address." + + findings.append(report) return findings diff --git a/prowler/providers/aws/services/autoscaling/autoscaling_group_launch_configuration_requires_imdsv2/autoscaling_group_launch_configuration_requires_imdsv2.py b/prowler/providers/aws/services/autoscaling/autoscaling_group_launch_configuration_requires_imdsv2/autoscaling_group_launch_configuration_requires_imdsv2.py index b27e4c9a52..5d79ba42d9 100644 --- a/prowler/providers/aws/services/autoscaling/autoscaling_group_launch_configuration_requires_imdsv2/autoscaling_group_launch_configuration_requires_imdsv2.py +++ b/prowler/providers/aws/services/autoscaling/autoscaling_group_launch_configuration_requires_imdsv2/autoscaling_group_launch_configuration_requires_imdsv2.py @@ -8,20 +8,17 @@ class autoscaling_group_launch_configuration_requires_imdsv2(Check): def execute(self): findings = [] for group in autoscaling_client.groups: - report = Check_Report_AWS(self.metadata()) - report.region = group.region - report.resource_id = group.name - report.resource_arn = group.arn - report.resource_tags = group.tags - report.status = "FAIL" - report.status_extended = ( - f"Autoscaling group {group.name} has IMDSv2 disabled or not required." - ) - for ( launch_configuration ) in autoscaling_client.launch_configurations.values(): if launch_configuration.name == group.launch_configuration_name: + report = Check_Report_AWS(self.metadata()) + report.region = group.region + report.resource_id = group.name + report.resource_arn = group.arn + report.resource_tags = group.tags + report.status = "FAIL" + report.status_extended = f"Autoscaling group {group.name} has IMDSv2 disabled or not required." if ( launch_configuration.http_endpoint == "enabled" and launch_configuration.http_tokens == "required" @@ -32,6 +29,6 @@ class autoscaling_group_launch_configuration_requires_imdsv2(Check): report.status = "PASS" report.status_extended = f"Autoscaling group {group.name} has metadata service disabled." - findings.append(report) + findings.append(report) return findings diff --git a/prowler/providers/aws/services/awslambda/awslambda_function_inside_vpc/awslambda_function_inside_vpc.metadata.json b/prowler/providers/aws/services/awslambda/awslambda_function_inside_vpc/awslambda_function_inside_vpc.metadata.json index ed9b02372e..b25df32ee0 100644 --- a/prowler/providers/aws/services/awslambda/awslambda_function_inside_vpc/awslambda_function_inside_vpc.metadata.json +++ b/prowler/providers/aws/services/awslambda/awslambda_function_inside_vpc/awslambda_function_inside_vpc.metadata.json @@ -3,7 +3,7 @@ "CheckID": "awslambda_function_inside_vpc", "CheckTitle": "Ensure AWS Lambda Functions Are Deployed Inside a VPC", "CheckType": [], - "ServiceName": "lambda", + "ServiceName": "awslambda", "SubServiceName": "", "ResourceIdTemplate": "arn:partition:lambda:region:account-id:function/function-name", "Severity": "low", diff --git a/prowler/providers/aws/services/awslambda/awslambda_function_invoke_api_operations_cloudtrail_logging_enabled/awslambda_function_invoke_api_operations_cloudtrail_logging_enabled.metadata.json b/prowler/providers/aws/services/awslambda/awslambda_function_invoke_api_operations_cloudtrail_logging_enabled/awslambda_function_invoke_api_operations_cloudtrail_logging_enabled.metadata.json index 1782488f9c..4354b042c3 100644 --- a/prowler/providers/aws/services/awslambda/awslambda_function_invoke_api_operations_cloudtrail_logging_enabled/awslambda_function_invoke_api_operations_cloudtrail_logging_enabled.metadata.json +++ b/prowler/providers/aws/services/awslambda/awslambda_function_invoke_api_operations_cloudtrail_logging_enabled/awslambda_function_invoke_api_operations_cloudtrail_logging_enabled.metadata.json @@ -3,7 +3,7 @@ "CheckID": "awslambda_function_invoke_api_operations_cloudtrail_logging_enabled", "CheckTitle": "Check if Lambda functions invoke API operations are being recorded by CloudTrail.", "CheckType": [], - "ServiceName": "lambda", + "ServiceName": "awslambda", "SubServiceName": "", "ResourceIdTemplate": "arn:partition:lambda:region:account-id:function/function-name", "Severity": "low", diff --git a/prowler/providers/aws/services/awslambda/awslambda_function_no_secrets_in_code/awslambda_function_no_secrets_in_code.metadata.json b/prowler/providers/aws/services/awslambda/awslambda_function_no_secrets_in_code/awslambda_function_no_secrets_in_code.metadata.json index 4fd1e9dc57..43cb61491d 100644 --- a/prowler/providers/aws/services/awslambda/awslambda_function_no_secrets_in_code/awslambda_function_no_secrets_in_code.metadata.json +++ b/prowler/providers/aws/services/awslambda/awslambda_function_no_secrets_in_code/awslambda_function_no_secrets_in_code.metadata.json @@ -3,7 +3,7 @@ "CheckID": "awslambda_function_no_secrets_in_code", "CheckTitle": "Find secrets in Lambda functions code.", "CheckType": [], - "ServiceName": "lambda", + "ServiceName": "awslambda", "SubServiceName": "", "ResourceIdTemplate": "arn:partition:lambda:region:account-id:function/function-name", "Severity": "critical", diff --git a/prowler/providers/aws/services/awslambda/awslambda_function_no_secrets_in_variables/awslambda_function_no_secrets_in_variables.metadata.json b/prowler/providers/aws/services/awslambda/awslambda_function_no_secrets_in_variables/awslambda_function_no_secrets_in_variables.metadata.json index 39365275f1..640d977fea 100644 --- a/prowler/providers/aws/services/awslambda/awslambda_function_no_secrets_in_variables/awslambda_function_no_secrets_in_variables.metadata.json +++ b/prowler/providers/aws/services/awslambda/awslambda_function_no_secrets_in_variables/awslambda_function_no_secrets_in_variables.metadata.json @@ -3,7 +3,7 @@ "CheckID": "awslambda_function_no_secrets_in_variables", "CheckTitle": "Find secrets in Lambda functions variables.", "CheckType": [], - "ServiceName": "lambda", + "ServiceName": "awslambda", "SubServiceName": "", "ResourceIdTemplate": "arn:partition:lambda:region:account-id:function/function-name", "Severity": "critical", diff --git a/prowler/providers/aws/services/awslambda/awslambda_function_not_publicly_accessible/awslambda_function_not_publicly_accessible.metadata.json b/prowler/providers/aws/services/awslambda/awslambda_function_not_publicly_accessible/awslambda_function_not_publicly_accessible.metadata.json index 8bff12f44d..874d8aca1a 100644 --- a/prowler/providers/aws/services/awslambda/awslambda_function_not_publicly_accessible/awslambda_function_not_publicly_accessible.metadata.json +++ b/prowler/providers/aws/services/awslambda/awslambda_function_not_publicly_accessible/awslambda_function_not_publicly_accessible.metadata.json @@ -3,7 +3,7 @@ "CheckID": "awslambda_function_not_publicly_accessible", "CheckTitle": "Check if Lambda functions have resource-based policy set as Public.", "CheckType": [], - "ServiceName": "lambda", + "ServiceName": "awslambda", "SubServiceName": "", "ResourceIdTemplate": "arn:partition:lambda:region:account-id:function/function-name", "Severity": "critical", diff --git a/prowler/providers/aws/services/awslambda/awslambda_function_not_publicly_accessible/awslambda_function_not_publicly_accessible_fixer.py b/prowler/providers/aws/services/awslambda/awslambda_function_not_publicly_accessible/awslambda_function_not_publicly_accessible_fixer.py new file mode 100644 index 0000000000..3a74fb17d6 --- /dev/null +++ b/prowler/providers/aws/services/awslambda/awslambda_function_not_publicly_accessible/awslambda_function_not_publicly_accessible_fixer.py @@ -0,0 +1,61 @@ +import json + +from prowler.lib.logger import logger +from prowler.providers.aws.services.awslambda.awslambda_client import awslambda_client + + +def fixer(resource_id: str, region: str) -> bool: + """ + Remove the Lambda function's resource-based policy to prevent public access and add a new permission for the account. + Specifically, this fixer deletes all permission statements associated with the Lambda function's policy and then adds a new permission. + Requires the lambda:RemovePermission and lambda:AddPermission permissions. + Permissions: + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "lambda:RemovePermission", + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": "lambda:AddPermission", + "Resource": "*" + } + ] + } + Args: + resource_id (str): The Lambda function name or ARN. + region (str): AWS region where the Lambda function exists. + Returns: + bool: True if the operation is successful (policy removed and permission added), False otherwise. + """ + try: + account_id = awslambda_client.audited_account + + regional_client = awslambda_client.regional_clients[region] + policy_response = regional_client.get_policy(FunctionName=resource_id) + policy = json.loads(policy_response.get("Policy")) + + for statement in policy.get("Statement", []): + statement_id = statement.get("Sid") + if statement_id: + regional_client.remove_permission( + FunctionName=resource_id, StatementId=statement_id + ) + + regional_client.add_permission( + FunctionName=resource_id, + StatementId="ProwlerFixerStatement", + Principal=account_id, + Action="lambda:InvokeFunction", + ) + + except Exception as error: + logger.error( + f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return False + else: + return True diff --git a/prowler/providers/aws/services/awslambda/awslambda_function_url_cors_policy/awslambda_function_url_cors_policy.metadata.json b/prowler/providers/aws/services/awslambda/awslambda_function_url_cors_policy/awslambda_function_url_cors_policy.metadata.json index 582aed2f9d..77d0d0fa19 100644 --- a/prowler/providers/aws/services/awslambda/awslambda_function_url_cors_policy/awslambda_function_url_cors_policy.metadata.json +++ b/prowler/providers/aws/services/awslambda/awslambda_function_url_cors_policy/awslambda_function_url_cors_policy.metadata.json @@ -3,7 +3,7 @@ "CheckID": "awslambda_function_url_cors_policy", "CheckTitle": "Check Lambda Function URL CORS configuration.", "CheckType": [], - "ServiceName": "lambda", + "ServiceName": "awslambda", "SubServiceName": "", "ResourceIdTemplate": "arn:partition:lambda:region:account-id:function/function-name", "Severity": "medium", diff --git a/prowler/providers/aws/services/awslambda/awslambda_function_url_public/awslambda_function_url_public.metadata.json b/prowler/providers/aws/services/awslambda/awslambda_function_url_public/awslambda_function_url_public.metadata.json index 3e7f1a2eac..74fa75f1f7 100644 --- a/prowler/providers/aws/services/awslambda/awslambda_function_url_public/awslambda_function_url_public.metadata.json +++ b/prowler/providers/aws/services/awslambda/awslambda_function_url_public/awslambda_function_url_public.metadata.json @@ -3,7 +3,7 @@ "CheckID": "awslambda_function_url_public", "CheckTitle": "Check Public Lambda Function URL.", "CheckType": [], - "ServiceName": "lambda", + "ServiceName": "awslambda", "SubServiceName": "", "ResourceIdTemplate": "arn:partition:lambda:region:account-id:function/function-name", "Severity": "high", diff --git a/prowler/providers/aws/services/awslambda/awslambda_function_using_supported_runtimes/awslambda_function_using_supported_runtimes.metadata.json b/prowler/providers/aws/services/awslambda/awslambda_function_using_supported_runtimes/awslambda_function_using_supported_runtimes.metadata.json index b8a1c5a013..a3048e5b9e 100644 --- a/prowler/providers/aws/services/awslambda/awslambda_function_using_supported_runtimes/awslambda_function_using_supported_runtimes.metadata.json +++ b/prowler/providers/aws/services/awslambda/awslambda_function_using_supported_runtimes/awslambda_function_using_supported_runtimes.metadata.json @@ -3,7 +3,7 @@ "CheckID": "awslambda_function_using_supported_runtimes", "CheckTitle": "Find obsolete Lambda runtimes.", "CheckType": [], - "ServiceName": "lambda", + "ServiceName": "awslambda", "SubServiceName": "", "ResourceIdTemplate": "arn:partition:lambda:region:account-id:function/function-name", "Severity": "medium", diff --git a/prowler/providers/aws/services/awslambda/awslambda_function_vpc_multi_az/awslambda_function_vpc_multi_az.metadata.json b/prowler/providers/aws/services/awslambda/awslambda_function_vpc_multi_az/awslambda_function_vpc_multi_az.metadata.json index 312023e4da..6c6cfff74e 100644 --- a/prowler/providers/aws/services/awslambda/awslambda_function_vpc_multi_az/awslambda_function_vpc_multi_az.metadata.json +++ b/prowler/providers/aws/services/awslambda/awslambda_function_vpc_multi_az/awslambda_function_vpc_multi_az.metadata.json @@ -3,7 +3,7 @@ "CheckID": "awslambda_function_vpc_multi_az", "CheckTitle": "Check if AWS Lambda Function VPC is deployed Across Multiple Availability Zones", "CheckType": [], - "ServiceName": "lambda", + "ServiceName": "awslambda", "SubServiceName": "", "ResourceIdTemplate": "arn:partition:lambda:region:account-id:function/function-name", "Severity": "medium", diff --git a/prowler/providers/aws/services/backup/backup_service.py b/prowler/providers/aws/services/backup/backup_service.py index 4805e36259..8690ae4ecc 100644 --- a/prowler/providers/aws/services/backup/backup_service.py +++ b/prowler/providers/aws/services/backup/backup_service.py @@ -183,21 +183,26 @@ class Backup(AWSService): def _list_recovery_points(self, regional_client): logger.info("Backup - Listing Recovery Points...") try: - for backup_vault in self.backup_vaults: - paginator = regional_client.get_paginator( - "list_recovery_points_by_backup_vault" - ) - for page in paginator.paginate(BackupVaultName=backup_vault.name): - for recovery_point in page.get("RecoveryPoints", []): - self.recovery_points.append( - RecoveryPoint( - arn=recovery_point.get("RecoveryPointArn"), - backup_vault_name=backup_vault.name, - encrypted=recovery_point.get("IsEncrypted", False), - backup_vault_region=backup_vault.region, - tags=[], - ) - ) + if self.backup_vaults: + for backup_vault in self.backup_vaults: + paginator = regional_client.get_paginator( + "list_recovery_points_by_backup_vault" + ) + for page in paginator.paginate(BackupVaultName=backup_vault.name): + for recovery_point in page.get("RecoveryPoints", []): + arn = recovery_point.get("RecoveryPointArn") + if arn: + self.recovery_points.append( + RecoveryPoint( + arn=arn, + backup_vault_name=backup_vault.name, + encrypted=recovery_point.get( + "IsEncrypted", False + ), + backup_vault_region=backup_vault.region, + tags=[], + ) + ) except ClientError as error: logger.error( f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_enumeration/cloudtrail_threat_detection_enumeration.py b/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_enumeration/cloudtrail_threat_detection_enumeration.py index f1ff06da85..8b8dfeca40 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_enumeration/cloudtrail_threat_detection_enumeration.py +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_enumeration/cloudtrail_threat_detection_enumeration.py @@ -67,10 +67,8 @@ class cloudtrail_threat_detection_enumeration(Check): found_potential_enumeration = True report = Check_Report_AWS(self.metadata()) report.region = cloudtrail_client.region - report.resource_id = cloudtrail_client.audited_account - report.resource_arn = cloudtrail_client._get_trail_arn_template( - cloudtrail_client.region - ) + report.resource_id = aws_identity_arn.split("/")[-1] + report.resource_arn = aws_identity_arn report.status = "FAIL" report.status_extended = f"Potential enumeration attack detected from AWS {aws_identity_type} {aws_identity_arn.split('/')[-1]} with an threshold of {identity_threshold}." findings.append(report) diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_llm_jacking/cloudtrail_threat_detection_llm_jacking.py b/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_llm_jacking/cloudtrail_threat_detection_llm_jacking.py index 8686a9a2ed..0dd77c4463 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_llm_jacking/cloudtrail_threat_detection_llm_jacking.py +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_llm_jacking/cloudtrail_threat_detection_llm_jacking.py @@ -67,10 +67,8 @@ class cloudtrail_threat_detection_llm_jacking(Check): found_potential_llm_jacking = True report = Check_Report_AWS(self.metadata()) report.region = cloudtrail_client.region - report.resource_id = cloudtrail_client.audited_account - report.resource_arn = cloudtrail_client._get_trail_arn_template( - cloudtrail_client.region - ) + report.resource_id = aws_identity_arn.split("/")[-1] + report.resource_arn = aws_identity_arn report.status = "FAIL" report.status_extended = f"Potential LLM Jacking attack detected from AWS {aws_identity_type} {aws_identity_arn.split('/')[-1]} with an threshold of {identity_threshold}." findings.append(report) diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_privilege_escalation/cloudtrail_threat_detection_privilege_escalation.py b/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_privilege_escalation/cloudtrail_threat_detection_privilege_escalation.py index 2992400912..676dde026f 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_privilege_escalation/cloudtrail_threat_detection_privilege_escalation.py +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_privilege_escalation/cloudtrail_threat_detection_privilege_escalation.py @@ -69,10 +69,8 @@ class cloudtrail_threat_detection_privilege_escalation(Check): found_potential_privilege_escalation = True report = Check_Report_AWS(self.metadata()) report.region = cloudtrail_client.region - report.resource_id = cloudtrail_client.audited_account - report.resource_arn = cloudtrail_client._get_trail_arn_template( - cloudtrail_client.region - ) + report.resource_id = aws_identity_arn.split("/")[-1] + report.resource_arn = aws_identity_arn report.status = "FAIL" report.status_extended = f"Potential privilege escalation attack detected from AWS {aws_identity_type} {aws_identity_arn.split('/')[-1]} with an threshold of {identity_threshold}." findings.append(report) diff --git a/prowler/providers/aws/services/ec2/ec2_instance_account_imdsv2_enabled/ec2_instance_account_imdsv2_enabled.metadata.json b/prowler/providers/aws/services/ec2/ec2_instance_account_imdsv2_enabled/ec2_instance_account_imdsv2_enabled.metadata.json index f5ff575139..f329b9df8b 100644 --- a/prowler/providers/aws/services/ec2/ec2_instance_account_imdsv2_enabled/ec2_instance_account_imdsv2_enabled.metadata.json +++ b/prowler/providers/aws/services/ec2/ec2_instance_account_imdsv2_enabled/ec2_instance_account_imdsv2_enabled.metadata.json @@ -8,7 +8,7 @@ "ServiceName": "ec2", "SubServiceName": "", "ResourceIdTemplate": "arn:partition:service:region:account-id", - "Severity": "medium", + "Severity": "high", "ResourceType": "AwsEc2Instance", "Description": "Ensure Instance Metadata Service Version 2 (IMDSv2) is enforced for EC2 instances at the account level to protect against SSRF vulnerabilities.", "Risk": "EC2 instances that use IMDSv1 are vulnerable to SSRF attacks.", diff --git a/prowler/providers/aws/services/ec2/ec2_instance_imdsv2_enabled/ec2_instance_imdsv2_enabled.metadata.json b/prowler/providers/aws/services/ec2/ec2_instance_imdsv2_enabled/ec2_instance_imdsv2_enabled.metadata.json index a83a12a9d7..3f613e6421 100644 --- a/prowler/providers/aws/services/ec2/ec2_instance_imdsv2_enabled/ec2_instance_imdsv2_enabled.metadata.json +++ b/prowler/providers/aws/services/ec2/ec2_instance_imdsv2_enabled/ec2_instance_imdsv2_enabled.metadata.json @@ -8,7 +8,7 @@ "ServiceName": "ec2", "SubServiceName": "", "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", - "Severity": "medium", + "Severity": "high", "ResourceType": "AwsEc2Instance", "Description": "Check if EC2 Instance Metadata Service Version 2 (IMDSv2) is Enabled and Required.", "Risk": "Using IMDSv2 will protect from misconfiguration and SSRF vulnerabilities. IMDSv1 will not.", diff --git a/prowler/providers/aws/services/ec2/ec2_instance_port_cifs_exposed_to_internet/ec2_instance_port_cifs_exposed_to_internet_fixer.py b/prowler/providers/aws/services/ec2/ec2_instance_port_cifs_exposed_to_internet/ec2_instance_port_cifs_exposed_to_internet_fixer.py new file mode 100644 index 0000000000..f898b8e5f3 --- /dev/null +++ b/prowler/providers/aws/services/ec2/ec2_instance_port_cifs_exposed_to_internet/ec2_instance_port_cifs_exposed_to_internet_fixer.py @@ -0,0 +1,51 @@ +from prowler.lib.logger import logger +from prowler.providers.aws.services.ec2.ec2_client import ec2_client +from prowler.providers.aws.services.ec2.lib.security_groups import check_security_group + + +def fixer(resource_id: str, region: str) -> bool: + """ + Revokes any ingress rule allowing CIFS ports (139, 445) from any address (0.0.0.0/0) + for the EC2 instance's security groups. + This fixer will only be triggered if the check identifies CIFS ports open to the Internet. + Requires the ec2:RevokeSecurityGroupIngress permission. + Permissions: + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "ec2:RevokeSecurityGroupIngress", + "Resource": "*" + } + ] + } + Args: + resource_id (str): The EC2 instance ID. + region (str): The AWS region where the EC2 instance exists. + Returns: + bool: True if the operation is successful (ingress rule revoked), False otherwise. + """ + try: + regional_client = ec2_client.regional_clients[region] + check_ports = [139, 445] + for instance in ec2_client.instances: + if instance.id == resource_id: + for sg in ec2_client.security_groups.values(): + if sg.id in instance.security_groups: + for ingress_rule in sg.ingress_rules: + if check_security_group( + ingress_rule, "tcp", check_ports, any_address=True + ): + regional_client.revoke_security_group_ingress( + GroupId=sg.id, + IpPermissions=[ingress_rule], + ) + + except Exception as error: + logger.error( + f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return False + else: + return True diff --git a/prowler/providers/aws/services/ec2/ec2_launch_template_imdsv2_required/__init__.py b/prowler/providers/aws/services/ec2/ec2_launch_template_imdsv2_required/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/ec2/ec2_launch_template_imdsv2_required/ec2_launch_template_imdsv2_required.metadata.json b/prowler/providers/aws/services/ec2/ec2_launch_template_imdsv2_required/ec2_launch_template_imdsv2_required.metadata.json new file mode 100644 index 0000000000..591225a7ce --- /dev/null +++ b/prowler/providers/aws/services/ec2/ec2_launch_template_imdsv2_required/ec2_launch_template_imdsv2_required.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "ec2_launch_template_imdsv2_required", + "CheckTitle": "Amazon EC2 launch templates should have IMDSv2 enabled and required.", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "ec2", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:ec2:region:account-id:launch-template/resource-id", + "Severity": "high", + "ResourceType": "AwsEc2LaunchTemplate", + "Description": "This control checks if Amazon EC2 launch templates are configured with IMDSv2 enabled and required. The control fails if IMDSv2 is not enabled or required in the launch template versions.", + "Risk": "Without IMDSv2 required, EC2 instances may be vulnerable to metadata service attacks, allowing unauthorized access to instance metadata, potentially leading to compromise of instance credentials or other sensitive data.", + "RelatedUrl": "https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html", + "Remediation": { + "Code": { + "CLI": "aws ec2 modify-launch-template --launch-template-id --version --metadata-options HttpTokens=required", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/ec2-controls.html#ec2-170", + "Terraform": "" + }, + "Recommendation": { + "Text": "To ensure EC2 launch templates have IMDSv2 enabled and required, update the template to configure the Instance Metadata Service Version 2 as required.", + "Url": "https://docs.aws.amazon.com/autoscaling/ec2/userguide/create-launch-template.html#change-metadata-options" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/ec2/ec2_launch_template_imdsv2_required/ec2_launch_template_imdsv2_required.py b/prowler/providers/aws/services/ec2/ec2_launch_template_imdsv2_required/ec2_launch_template_imdsv2_required.py new file mode 100644 index 0000000000..377d7519ed --- /dev/null +++ b/prowler/providers/aws/services/ec2/ec2_launch_template_imdsv2_required/ec2_launch_template_imdsv2_required.py @@ -0,0 +1,42 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.ec2.ec2_client import ec2_client + + +class ec2_launch_template_imdsv2_required(Check): + def execute(self): + findings = [] + for template in ec2_client.launch_templates: + report = Check_Report_AWS(self.metadata()) + report.region = template.region + report.resource_id = template.id + report.resource_arn = template.arn + report.resource_tags = template.tags + + versions_with_imdsv2_required = [] + versions_with_metadata_disabled = [] + versions_with_no_imdsv2 = [] + + for version in template.versions: + if ( + version.template_data.http_endpoint == "enabled" + and version.template_data.http_tokens == "required" + ): + versions_with_imdsv2_required.append(str(version.version_number)) + elif version.template_data.http_endpoint == "disabled": + versions_with_metadata_disabled.append(str(version.version_number)) + else: + versions_with_no_imdsv2.append(str(version.version_number)) + + if versions_with_imdsv2_required: + report.status = "PASS" + report.status_extended = f"EC2 Launch Template {template.name} has IMDSv2 enabled and required in the following versions: {', '.join(versions_with_imdsv2_required)}." + elif versions_with_metadata_disabled: + report.status = "PASS" + report.status_extended = f"EC2 Launch Template {template.name} has metadata service disabled in the following versions: {', '.join(versions_with_metadata_disabled)}." + else: + report.status = "FAIL" + report.status_extended = f"EC2 Launch Template {template.name} has IMDSv2 disabled or not required in the following versions: {', '.join(versions_with_no_imdsv2)}." + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/ec2/ec2_service.py b/prowler/providers/aws/services/ec2/ec2_service.py index 4c695c3110..cd559128f8 100644 --- a/prowler/providers/aws/services/ec2/ec2_service.py +++ b/prowler/providers/aws/services/ec2/ec2_service.py @@ -10,7 +10,6 @@ from prowler.lib.scan_filters.scan_filters import is_resource_filtered from prowler.providers.aws.lib.service.service import AWSService -################## EC2 class EC2(AWSService): def __init__(self, provider): # Call AWSService's __init__ @@ -569,6 +568,12 @@ class EC2(AWSService): ), network_interfaces=enis, associate_public_ip_address=associate_public_ip, + http_tokens=template_version["LaunchTemplateData"] + .get("MetadataOptions", {}) + .get("HttpTokens", ""), + http_endpoint=template_version["LaunchTemplateData"] + .get("MetadataOptions", {}) + .get("HttpEndpoint", ""), ), ) ) @@ -763,6 +768,8 @@ class TemplateData(BaseModel): user_data: str network_interfaces: Optional[list[NetworkInterface]] associate_public_ip_address: Optional[bool] + http_tokens: Optional[str] + http_endpoint: Optional[str] class LaunchTemplateVersion(BaseModel): diff --git a/prowler/providers/aws/services/ecr/ecr_repositories_not_publicly_accessible/ecr_repositories_not_publicly_accessible_fixer.py b/prowler/providers/aws/services/ecr/ecr_repositories_not_publicly_accessible/ecr_repositories_not_publicly_accessible_fixer.py new file mode 100644 index 0000000000..6c43385e73 --- /dev/null +++ b/prowler/providers/aws/services/ecr/ecr_repositories_not_publicly_accessible/ecr_repositories_not_publicly_accessible_fixer.py @@ -0,0 +1,38 @@ +from prowler.lib.logger import logger +from prowler.providers.aws.services.ecr.ecr_client import ecr_client + + +def fixer(resource_id: str, region: str) -> bool: + """ + Modify the ECR repository's policy to remove public access. + Specifically, this fixer delete the policy that had public access. + Requires the ecr:DeleteRepositoryPolicy permission. + Permissions: + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "ecr:DeleteRepositoryPolicy", + "Resource": "*" + } + ] + } + Args: + resource_id (str): The ECR repository name. + region (str): AWS region where the ECR repository exists. + Returns: + bool: True if the operation is successful (policy updated), False otherwise. + """ + try: + regional_client = ecr_client.regional_clients[region] + + regional_client.delete_repository_policy(repositoryName=resource_id) + + except Exception as error: + logger.error( + f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return False + else: + return True diff --git a/prowler/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/firehose_stream_encrypted_at_rest.py b/prowler/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/firehose_stream_encrypted_at_rest.py index fe63558366..0430b3d184 100644 --- a/prowler/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/firehose_stream_encrypted_at_rest.py +++ b/prowler/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/firehose_stream_encrypted_at_rest.py @@ -31,10 +31,7 @@ class firehose_stream_encrypted_at_rest(Check): f"Firehose Stream {stream.name} does have at rest encryption enabled." ) - if ( - stream.kms_encryption != EncryptionStatus.ENABLED - or not stream.kms_key_arn - ): + if stream.kms_encryption != EncryptionStatus.ENABLED: report.status = "FAIL" report.status_extended = f"Firehose Stream {stream.name} does not have at rest encryption enabled." diff --git a/prowler/providers/aws/services/glacier/glacier_vaults_policy_public_access/glacier_vaults_policy_public_access_fixer.py b/prowler/providers/aws/services/glacier/glacier_vaults_policy_public_access/glacier_vaults_policy_public_access_fixer.py new file mode 100644 index 0000000000..c9362e07bf --- /dev/null +++ b/prowler/providers/aws/services/glacier/glacier_vaults_policy_public_access/glacier_vaults_policy_public_access_fixer.py @@ -0,0 +1,38 @@ +from prowler.lib.logger import logger +from prowler.providers.aws.services.glacier.glacier_client import glacier_client + + +def fixer(resource_id: str, region: str) -> bool: + """ + Modify the Glacier vault's policy to remove public access. + Specifically, this fixer delete the vault policy that has public access. + Requires the glacier:DeleteVaultAccessPolicy permission. + Permissions: + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "glacier:DeleteVaultAccessPolicy", + "Resource": "*" + } + ] + } + Args: + resource_id (str): The Glacier vault name. + region (str): AWS region where the Glacier vault exists. + Returns: + bool: True if the operation is successful (policy updated), False otherwise. + """ + try: + regional_client = glacier_client.regional_clients[region] + + regional_client.delete_vault_access_policy(vaultName=resource_id) + + except Exception as error: + logger.error( + f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return False + else: + return True diff --git a/prowler/providers/aws/services/iam/iam_rotate_access_key_90_days/iam_rotate_access_key_90_days.py b/prowler/providers/aws/services/iam/iam_rotate_access_key_90_days/iam_rotate_access_key_90_days.py index 3de61166c5..225b92381d 100644 --- a/prowler/providers/aws/services/iam/iam_rotate_access_key_90_days/iam_rotate_access_key_90_days.py +++ b/prowler/providers/aws/services/iam/iam_rotate_access_key_90_days/iam_rotate_access_key_90_days.py @@ -49,7 +49,7 @@ class iam_rotate_access_key_90_days(Check): old_access_keys = True report = Check_Report_AWS(self.metadata()) report.region = iam_client.region - report.resource_id = user["user"] + report.resource_id = f"{user['user']}-access-key-1" report.resource_arn = user["arn"] report.resource_tags = user_tags report.status = "FAIL" @@ -66,7 +66,7 @@ class iam_rotate_access_key_90_days(Check): old_access_keys = True report = Check_Report_AWS(self.metadata()) report.region = iam_client.region - report.resource_id = user["user"] + report.resource_id = f"{user['user']}-access-key-2" report.resource_arn = user["arn"] report.resource_tags = user_tags report.status = "FAIL" diff --git a/prowler/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible_fixer.py b/prowler/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible_fixer.py new file mode 100644 index 0000000000..a43a94fc6e --- /dev/null +++ b/prowler/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible_fixer.py @@ -0,0 +1,43 @@ +from prowler.lib.logger import logger +from prowler.providers.aws.services.opensearch.opensearch_client import ( + opensearch_client, +) + + +def fixer(resource_id: str, region: str) -> bool: + """ + Modify the OpenSearch domain's resource-based policy to remove public access. + Specifically, this fixer update the domain config and add an empty policy to remove the old one. + Requires the es:UpdateDomainConfig permission. + Permissions: + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "es:UpdateDomainConfig", + "Resource": "*" + } + ] + } + Args: + resource_id (str): The OpenSearch domain name. + region (str): AWS region where the OpenSearch domain exists. + Returns: + bool: True if the operation is successful (policy updated), False otherwise. + """ + try: + regional_client = opensearch_client.regional_clients[region] + + regional_client.update_domain_config( + DomainName=resource_id, + AccessPolicies="", + ) + + except Exception as error: + logger.error( + f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return False + else: + return True diff --git a/prowler/providers/aws/services/rds/rds_cluster_non_default_port/rds_cluster_non_default_port.py b/prowler/providers/aws/services/rds/rds_cluster_non_default_port/rds_cluster_non_default_port.py index dfbb39fb77..f1e149db1d 100644 --- a/prowler/providers/aws/services/rds/rds_cluster_non_default_port/rds_cluster_non_default_port.py +++ b/prowler/providers/aws/services/rds/rds_cluster_non_default_port/rds_cluster_non_default_port.py @@ -6,8 +6,8 @@ class rds_cluster_non_default_port(Check): def execute(self): findings = [] default_ports = { - 3306: ["mysql", "mariadb"], - 5432: ["postgres"], + 3306: ["mysql", "mariadb", "aurora-mysql"], + 5432: ["postgres", "aurora-postgresql"], 1521: ["oracle"], 1433: ["sqlserver"], 50000: ["db2"], diff --git a/prowler/providers/aws/services/rds/rds_instance_no_public_access/rds_instance_no_public_access.py b/prowler/providers/aws/services/rds/rds_instance_no_public_access/rds_instance_no_public_access.py index ca0e1b3beb..dffa850693 100644 --- a/prowler/providers/aws/services/rds/rds_instance_no_public_access/rds_instance_no_public_access.py +++ b/prowler/providers/aws/services/rds/rds_instance_no_public_access/rds_instance_no_public_access.py @@ -37,18 +37,21 @@ class rds_instance_no_public_access(Check): ): report.status_extended = f"RDS Instance {db_instance.id} is set as publicly accessible and security group {security_group.name} ({security_group.id}) has {db_instance.engine} port {db_instance_port} open to the Internet at endpoint {db_instance.endpoint.get('Address')} but is not in a public subnet." public_sg = True + if db_instance.subnet_ids: + for subnet_id in db_instance.subnet_ids: + if ( + subnet_id in vpc_client.vpc_subnets + and vpc_client.vpc_subnets[ + subnet_id + ].public + ): + report.status = "FAIL" + report.status_extended = f"RDS Instance {db_instance.id} is set as publicly accessible and security group {security_group.name} ({security_group.id}) has {db_instance.engine} port {db_instance_port} open to the Internet at endpoint {db_instance.endpoint.get('Address')} in a public subnet {subnet_id}." + break + if public_sg: break if public_sg: break - if db_instance.subnet_ids: - for subnet_id in db_instance.subnet_ids: - if ( - subnet_id in vpc_client.vpc_subnets - and vpc_client.vpc_subnets[subnet_id].public - ): - report.status = "FAIL" - report.status_extended = f"RDS Instance {db_instance.id} is set as publicly accessible and security group {security_group.name} ({security_group.id}) has {db_instance.engine} port {db_instance_port} open to the Internet at endpoint {db_instance.endpoint.get('Address')} in a public subnet {subnet_id}." - break findings.append(report) diff --git a/prowler/providers/aws/services/rds/rds_instance_non_default_port/rds_instance_non_default_port.py b/prowler/providers/aws/services/rds/rds_instance_non_default_port/rds_instance_non_default_port.py index 9e2dda34ed..17a5bc65ef 100644 --- a/prowler/providers/aws/services/rds/rds_instance_non_default_port/rds_instance_non_default_port.py +++ b/prowler/providers/aws/services/rds/rds_instance_non_default_port/rds_instance_non_default_port.py @@ -6,8 +6,8 @@ class rds_instance_non_default_port(Check): def execute(self): findings = [] default_ports = { - 3306: ["mysql", "mariadb"], - 5432: ["postgres"], + 3306: ["mysql", "mariadb", "aurora-mysql"], + 5432: ["postgres", "aurora-postgresql"], 1521: ["oracle"], 1433: ["sqlserver"], 50000: ["db2"], diff --git a/prowler/providers/aws/services/s3/s3_bucket_public_access/s3_bucket_public_access_fixer.py b/prowler/providers/aws/services/s3/s3_bucket_public_access/s3_bucket_public_access_fixer.py new file mode 100644 index 0000000000..693336dec9 --- /dev/null +++ b/prowler/providers/aws/services/s3/s3_bucket_public_access/s3_bucket_public_access_fixer.py @@ -0,0 +1,46 @@ +from prowler.lib.logger import logger +from prowler.providers.aws.services.s3.s3_client import s3_client + + +def fixer(resource_id: str, region: str) -> bool: + """ + Modify the S3 bucket's public access settings to block all public access. + Specifically, this fixer configures the bucket's public access block settings to + prevent any public access (ACLs and policies). Requires the s3:PutBucketPublicAccessBlock + permission to modify the public access settings. + Permissions: + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "s3:PutBucketPublicAccessBlock", + "Resource": "*" + } + ] + } + Args: + resource_id (str): The S3 bucket name. + region (str): AWS region where the S3 bucket exists. + Returns: + bool: True if the operation is successful (public access is blocked), + False otherwise. + """ + try: + regional_client = s3_client.regional_clients[region] + regional_client.put_public_access_block( + Bucket=resource_id, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": True, + "IgnorePublicAcls": True, + "BlockPublicPolicy": True, + "RestrictPublicBuckets": True, + }, + ) + except Exception as error: + logger.error( + f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return False + else: + return True diff --git a/prowler/providers/aws/services/s3/s3_bucket_public_list_acl/s3_bucket_public_list_acl_fixer.py b/prowler/providers/aws/services/s3/s3_bucket_public_list_acl/s3_bucket_public_list_acl_fixer.py new file mode 100644 index 0000000000..1ab315b61d --- /dev/null +++ b/prowler/providers/aws/services/s3/s3_bucket_public_list_acl/s3_bucket_public_list_acl_fixer.py @@ -0,0 +1,40 @@ +from prowler.lib.logger import logger +from prowler.providers.aws.services.s3.s3_client import s3_client + + +def fixer(resource_id: str, region: str) -> bool: + """ + Modify the S3 bucket ACL to restrict public read access. + Specifically, this fixer sets the ACL of the bucket to 'private' to prevent + any public access to the S3 bucket. + Requires the s3:PutBucketAcl permission. + + Permissions: + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "s3:PutBucketAcl", + "Resource": "*" + } + ] + } + + Args: + resource_id (str): The S3 bucket name. + region (str): AWS region where the S3 bucket exists. + + Returns: + bool: True if the operation is successful (bucket access is updated), False otherwise. + """ + try: + regional_client = s3_client.regional_clients[region] + regional_client.put_bucket_acl(Bucket=resource_id, ACL="private") + except Exception as error: + logger.error( + f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return False + else: + return True diff --git a/prowler/providers/aws/services/s3/s3_bucket_public_write_acl/s3_bucket_public_write_acl_fixer.py b/prowler/providers/aws/services/s3/s3_bucket_public_write_acl/s3_bucket_public_write_acl_fixer.py new file mode 100644 index 0000000000..e27787a967 --- /dev/null +++ b/prowler/providers/aws/services/s3/s3_bucket_public_write_acl/s3_bucket_public_write_acl_fixer.py @@ -0,0 +1,36 @@ +from prowler.lib.logger import logger +from prowler.providers.aws.services.s3.s3_client import s3_client + + +def fixer(resource_id: str, region: str) -> bool: + """ + Modify the S3 bucket ACL to restrict public write access. + Specifically, this fixer sets the ACL of the bucket to 'private' to prevent + public write access to the S3 bucket. Requires the s3:PutBucketAcl permission. + Permissions: + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "s3:PutBucketAcl", + "Resource": "*" + } + ] + } + Args: + resource_id (str): The S3 bucket id. + region (str): AWS region where the S3 bucket exists. + Returns: + bool: True if the operation is successful (bucket access is updated), False otherwise. + """ + try: + regional_client = s3_client.regional_clients[region] + regional_client.put_bucket_acl(Bucket=resource_id, ACL="private") + except Exception as error: + logger.error( + f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return False + else: + return True diff --git a/prowler/providers/aws/services/sqs/sqs_queues_not_publicly_accessible/sqs_queues_not_publicly_accessible_fixer.py b/prowler/providers/aws/services/sqs/sqs_queues_not_publicly_accessible/sqs_queues_not_publicly_accessible_fixer.py new file mode 100644 index 0000000000..a8e0e489d4 --- /dev/null +++ b/prowler/providers/aws/services/sqs/sqs_queues_not_publicly_accessible/sqs_queues_not_publicly_accessible_fixer.py @@ -0,0 +1,64 @@ +import json + +from prowler.lib.logger import logger +from prowler.providers.aws.services.sqs.sqs_client import sqs_client + + +def fixer(resource_id: str, region: str) -> bool: + """ + Modify the SQS queue's resource-based policy to remove public access and replace with trusted account access. + Specifically, this fixer checks if any statement has a public Principal (e.g., "*" or "CanonicalUser") + and replaces it with the ARN of the trusted AWS account. + Requires the sqs:SetQueueAttributes permission. + Permissions: + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "sqs:SetQueueAttributes", + "Resource": "*" + } + ] + } + Args: + resource_id (str): The SQS queue name or ARN. + region (str): AWS region where the SQS queue exists. + Returns: + bool: True if the operation is successful (policy updated), False otherwise. + """ + try: + account_id = sqs_client.audited_account + audited_partition = sqs_client.audited_partition + + regional_client = sqs_client.regional_clients[region] + + queue_name = resource_id.split("/")[-1] + + trusted_policy = { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "ProwlerFixerStatement", + "Effect": "Allow", + "Principal": { + "AWS": account_id, + }, + "Action": "sqs:*", + "Resource": f"arn:{audited_partition}:sqs:{region}:{account_id}:{queue_name}", + } + ], + } + + regional_client.set_queue_attributes( + QueueUrl=resource_id, + Attributes={"Policy": json.dumps(trusted_policy)}, + ) + + except Exception as error: + logger.error( + f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return False + else: + return True diff --git a/prowler/providers/aws/services/wafv2/wafv2_service.py b/prowler/providers/aws/services/wafv2/wafv2_service.py index 85feed76f6..c867691b77 100644 --- a/prowler/providers/aws/services/wafv2/wafv2_service.py +++ b/prowler/providers/aws/services/wafv2/wafv2_service.py @@ -150,6 +150,22 @@ class WAFv2(AWSService): else: acl.rules.append(new_rule) + firewall_manager_managed_rg = get_web_acl.get("WebACL", {}).get( + "PreProcessFirewallManagerRuleGroups", [] + ) + get_web_acl.get("WebACL", {}).get( + "PostProcessFirewallManagerRuleGroups", [] + ) + + for rule in firewall_manager_managed_rg: + acl.rule_groups.append( + Rule( + name=rule.get("Name", ""), + cloudwatch_metrics_enabled=rule.get( + "VisibilityConfig", {} + ).get("CloudWatchMetricsEnabled", False), + ) + ) + except Exception as error: logger.error( f"{acl.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" @@ -193,13 +209,6 @@ class Rule(BaseModel): cloudwatch_metrics_enabled: bool = False -class FirewallManagerRuleGroup(BaseModel): - """Model representing a rule group for the Web ACL.""" - - name: str - cloudwatch_metrics_enabled: bool = False - - class WebAclv2(BaseModel): """Model representing a Web ACL for WAFv2.""" diff --git a/prowler/providers/azure/services/app/app_minimum_tls_version_12/app_minimum_tls_version_12.py b/prowler/providers/azure/services/app/app_minimum_tls_version_12/app_minimum_tls_version_12.py index 32020e2ec2..519335a016 100644 --- a/prowler/providers/azure/services/app/app_minimum_tls_version_12/app_minimum_tls_version_12.py +++ b/prowler/providers/azure/services/app/app_minimum_tls_version_12/app_minimum_tls_version_12.py @@ -19,12 +19,11 @@ class app_minimum_tls_version_12(Check): report.location = app.location report.status_extended = f"Minimum TLS version is not set to 1.2 for app '{app_name}' in subscription '{subscription_name}'." - if ( - app.configurations - and getattr(app.configurations, "min_tls_version", "") == "1.2" - ): + if app.configurations and getattr( + app.configurations, "min_tls_version", "" + ) in ["1.2", "1.3"]: report.status = "PASS" - report.status_extended = f"Minimum TLS version is set to 1.2 for app '{app_name}' in subscription '{subscription_name}'." + report.status_extended = f"Minimum TLS version is set to {app.configurations.min_tls_version} for app '{app_name}' in subscription '{subscription_name}'." findings.append(report) diff --git a/prowler/providers/azure/services/sqlserver/sqlserver_recommended_minimal_tls_version/__init__.py b/prowler/providers/azure/services/sqlserver/sqlserver_recommended_minimal_tls_version/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/azure/services/sqlserver/sqlserver_recommended_minimal_tls_version/sqlserver_recommended_minimal_tls_version.metadata.json b/prowler/providers/azure/services/sqlserver/sqlserver_recommended_minimal_tls_version/sqlserver_recommended_minimal_tls_version.metadata.json new file mode 100644 index 0000000000..42e35c2c4f --- /dev/null +++ b/prowler/providers/azure/services/sqlserver/sqlserver_recommended_minimal_tls_version/sqlserver_recommended_minimal_tls_version.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "azure", + "CheckID": "sqlserver_recommended_minimal_tls_version", + "CheckTitle": "Ensure SQL server has a recommended minimal TLS version required.", + "CheckType": [], + "ServiceName": "sqlserver", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "medium", + "ResourceType": "SQLServer", + "Description": "Ensure that SQL Server instances are configured with the recommended minimal TLS version to maintain secure connections.", + "Risk": "Using outdated or weak TLS versions can expose SQL Server instances to vulnerabilities, increasing the risk of data breaches and unauthorized access.", + "RelatedUrl": "https://learn.microsoft.com/en-us/azure/azure-sql/database/connectivity-settings?view=azuresql&tabs=azure-portal#configure-minimum-tls-version", + "Remediation": { + "Code": { + "CLI": "az sql server update -n sql-server-name -g sql-server-group --set minimalTlsVersion=", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "1. Go to Azure SQL Server 2. Navigate to 'Security' -> 'Networking' 3. Select 'Connectivity' 4. Update the TLS version in the field 'Minimum TLS version' to a recommended minimal version (e.g., TLS 1.2).", + "Url": "https://learn.microsoft.com/en-us/azure/azure-sql/database/connectivity-settings?view=azuresql&tabs=azure-portal#configure-minimum-tls-version" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Verify support for the TLS version from the application side before changing the minimal version." +} diff --git a/prowler/providers/azure/services/sqlserver/sqlserver_recommended_minimal_tls_version/sqlserver_recommended_minimal_tls_version.py b/prowler/providers/azure/services/sqlserver/sqlserver_recommended_minimal_tls_version/sqlserver_recommended_minimal_tls_version.py new file mode 100644 index 0000000000..d0f97b8861 --- /dev/null +++ b/prowler/providers/azure/services/sqlserver/sqlserver_recommended_minimal_tls_version/sqlserver_recommended_minimal_tls_version.py @@ -0,0 +1,27 @@ +from typing import List + +from prowler.lib.check.models import Check, Check_Report_Azure +from prowler.providers.azure.services.sqlserver.sqlserver_client import sqlserver_client + + +class sqlserver_recommended_minimal_tls_version(Check): + def execute(self) -> List[Check_Report_Azure]: + findings = [] + recommended_minimal_tls_versions = sqlserver_client.audit_config.get( + "recommended_minimal_tls_versions", ["1.2", "1.3"] + ) + for subscription, sql_servers in sqlserver_client.sql_servers.items(): + for sql_server in sql_servers: + report = Check_Report_Azure(self.metadata()) + report.subscription = subscription + report.resource_name = sql_server.name + report.resource_id = sql_server.id + report.status = "FAIL" + report.location = sql_server.location + report.status_extended = f"SQL Server {sql_server.name} from subscription {subscription} is using TLS version {sql_server.minimal_tls_version} as minimal accepted which is not recommended. Please use one of the recommended versions: {', '.join(recommended_minimal_tls_versions)}." + if sql_server.minimal_tls_version in recommended_minimal_tls_versions: + report.status_extended = f"SQL Server {sql_server.name} from subscription {subscription} is using version {sql_server.minimal_tls_version} as minimal accepted which is recommended." + report.status = "PASS" + findings.append(report) + + return findings diff --git a/prowler/providers/gcp/gcp_provider.py b/prowler/providers/gcp/gcp_provider.py index 1365d3586d..4fff965212 100644 --- a/prowler/providers/gcp/gcp_provider.py +++ b/prowler/providers/gcp/gcp_provider.py @@ -181,8 +181,6 @@ class GcpProvider(Provider): message="No Project IDs can be accessed via Google Credentials.", ) if project_ids: - if self._default_project_id not in project_ids: - self._default_project_id = project_ids[0] for input_project in project_ids: for ( accessible_project_id, @@ -203,6 +201,10 @@ class GcpProvider(Provider): self._projects[project_id] = project self._project_ids.append(project_id) + # Change default project if not in active projects + if self._project_ids and self._default_project_id not in self._project_ids: + self._default_project_id = self._project_ids[0] + # Remove excluded projects if any input if excluded_project_ids: for excluded_project in excluded_project_ids: diff --git a/pyproject.toml b/pyproject.toml index c72a5ab9a7..5220cc87c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ packages = [ {include = "dashboard"} ] readme = "README.md" -version = "5.0.0" +version = "5.1.0" [tool.poetry.dependencies] alive-progress = "3.2.0" @@ -48,19 +48,19 @@ azure-mgmt-storage = "21.2.1" azure-mgmt-subscription = "3.1.1" azure-mgmt-web = "7.3.1" azure-storage-blob = "12.24.0" -boto3 = "1.35.71" -botocore = "1.35.71" +boto3 = "1.35.78" +botocore = "1.35.79" colorama = "0.4.6" cryptography = "43.0.1" dash = "2.18.2" dash-bootstrap-components = "1.6.0" detect-secrets = "1.5.0" -google-api-python-client = "2.154.0" +google-api-python-client = "2.155.0" google-auth-httplib2 = ">=0.1,<0.3" jsonschema = "4.23.0" kubernetes = "31.0.0" -microsoft-kiota-abstractions = "1.6.2" -msgraph-sdk = "1.12.0" +microsoft-kiota-abstractions = "1.6.6" +msgraph-sdk = "1.14.0" numpy = "2.0.2" pandas = "2.2.3" py-ocsf-models = "0.2.0" @@ -70,14 +70,14 @@ python-dateutil = "^2.9.0.post0" pytz = "2024.2" schema = "0.7.7" shodan = "1.31.0" -slack-sdk = "3.33.4" +slack-sdk = "3.33.5" tabulate = "0.9.0" tzlocal = "5.2" [tool.poetry.group.dev.dependencies] bandit = "1.8.0" black = "24.10.0" -coverage = "7.6.8" +coverage = "7.6.9" docker = "7.1.0" flake8 = "7.1.1" freezegun = "1.5.1" @@ -85,14 +85,14 @@ mock = "5.1.0" moto = {extras = ["all"], version = "5.0.16"} openapi-schema-validator = "0.6.2" openapi-spec-validator = "0.7.1" -pylint = "3.3.1" -pytest = "8.3.3" +pylint = "3.3.2" +pytest = "8.3.4" pytest-cov = "6.0.0" pytest-env = "1.1.5" pytest-randomly = "3.16.0" pytest-xdist = "3.6.1" safety = "3.2.9" -vulture = "2.13" +vulture = "2.14" [tool.poetry.group.docs] optional = true @@ -100,7 +100,7 @@ optional = true [tool.poetry.group.docs.dependencies] mkdocs = "1.6.1" mkdocs-git-revision-date-localized-plugin = "1.3.0" -mkdocs-material = "9.5.46" +mkdocs-material = "9.5.48" mkdocs-material-extensions = "1.3.1" [tool.poetry.scripts] diff --git a/tests/config/config_test.py b/tests/config/config_test.py index f79ef50eff..14e0077a88 100644 --- a/tests/config/config_test.py +++ b/tests/config/config_test.py @@ -318,6 +318,7 @@ config_azure = { "php_latest_version": "8.2", "python_latest_version": "3.12", "java_latest_version": "17", + "recommended_minimal_tls_versions": ["1.2", "1.3"], } config_gcp = {"shodan_api_key": None} diff --git a/tests/config/fixtures/config.yaml b/tests/config/fixtures/config.yaml index 54b1c011b3..0994a496dc 100644 --- a/tests/config/fixtures/config.yaml +++ b/tests/config/fixtures/config.yaml @@ -379,6 +379,14 @@ azure: # azure.app_ensure_java_version_is_latest java_latest_version: "17" + # Azure SQL Server + # azure.sqlserver_minimal_tls_version + recommended_minimal_tls_versions: + [ + "1.2", + "1.3" + ] + # GCP Configuration gcp: # GCP Compute Configuration diff --git a/tests/providers/aws/services/autoscaling/autoscaling_group_launch_configuration_no_public_ip/autoscaling_group_launch_configuration_no_public_ip_test.py b/tests/providers/aws/services/autoscaling/autoscaling_group_launch_configuration_no_public_ip/autoscaling_group_launch_configuration_no_public_ip_test.py index 7362e3ad7c..a911e0169d 100644 --- a/tests/providers/aws/services/autoscaling/autoscaling_group_launch_configuration_no_public_ip/autoscaling_group_launch_configuration_no_public_ip_test.py +++ b/tests/providers/aws/services/autoscaling/autoscaling_group_launch_configuration_no_public_ip/autoscaling_group_launch_configuration_no_public_ip_test.py @@ -171,10 +171,6 @@ class Test_autoscaling_group_launch_configuration_no_public_ip: AvailabilityZones=["us-east-1a", "us-east-1b"], ) - autoscaling_group_arn = autoscaling_client.describe_auto_scaling_groups( - AutoScalingGroupNames=[autoscaling_group_name] - )["AutoScalingGroups"][0]["AutoScalingGroupARN"] - from prowler.providers.aws.services.autoscaling.autoscaling_service import ( AutoScaling, ) @@ -196,12 +192,4 @@ class Test_autoscaling_group_launch_configuration_no_public_ip: check = autoscaling_group_launch_configuration_no_public_ip() result = check.execute() - assert len(result) == 1 - assert result[0].status == "PASS" - assert ( - result[0].status_extended - == f"Autoscaling group {autoscaling_group_name} does not have an associated launch configuration assigning a public IP address." - ) - assert result[0].resource_id == autoscaling_group_name - assert result[0].resource_tags == [] - assert result[0].resource_arn == autoscaling_group_arn + assert len(result) == 0 diff --git a/tests/providers/aws/services/autoscaling/autoscaling_group_launch_configuration_requires_imdsv2/autoscaling_group_launch_configuration_requires_imdsv2_test.py b/tests/providers/aws/services/autoscaling/autoscaling_group_launch_configuration_requires_imdsv2/autoscaling_group_launch_configuration_requires_imdsv2_test.py index b1f769626f..a9239a3ed1 100644 --- a/tests/providers/aws/services/autoscaling/autoscaling_group_launch_configuration_requires_imdsv2/autoscaling_group_launch_configuration_requires_imdsv2_test.py +++ b/tests/providers/aws/services/autoscaling/autoscaling_group_launch_configuration_requires_imdsv2/autoscaling_group_launch_configuration_requires_imdsv2_test.py @@ -119,10 +119,6 @@ class Test_autoscaling_group_launch_configuration_requires_imdsv2: AvailabilityZones=["us-east-1a", "us-east-1b"], ) - autoscaling_group_arn = autoscaling_client.describe_auto_scaling_groups( - AutoScalingGroupNames=[autoscaling_group_name] - )["AutoScalingGroups"][0]["AutoScalingGroupARN"] - from prowler.providers.aws.services.autoscaling.autoscaling_service import ( AutoScaling, ) @@ -144,15 +140,7 @@ class Test_autoscaling_group_launch_configuration_requires_imdsv2: check = autoscaling_group_launch_configuration_requires_imdsv2() result = check.execute() - assert len(result) == 1 - assert result[0].status == "FAIL" - assert ( - result[0].status_extended - == f"Autoscaling group {autoscaling_group_name} has IMDSv2 disabled or not required." - ) - assert result[0].resource_id == autoscaling_group_name - assert result[0].resource_tags == [] - assert result[0].resource_arn == autoscaling_group_arn + assert len(result) == 0 @mock_aws def test_groups_with_imdsv2_disabled(self): diff --git a/tests/providers/aws/services/awslambda/awslambda_function_not_publicly_accessible/awslambda_function_not_publicly_accessible_fixer_test.py b/tests/providers/aws/services/awslambda/awslambda_function_not_publicly_accessible/awslambda_function_not_publicly_accessible_fixer_test.py new file mode 100644 index 0000000000..958a77c00e --- /dev/null +++ b/tests/providers/aws/services/awslambda/awslambda_function_not_publicly_accessible/awslambda_function_not_publicly_accessible_fixer_test.py @@ -0,0 +1,268 @@ +from json import dumps +from unittest import mock + +from boto3 import client +from moto import mock_aws + +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_EU_WEST_1, + set_mocked_aws_provider, +) + + +class Test_awslambda_function_not_publicly_accessible_fixer: + @mock_aws + def test_function_public(self): + # Create the mock IAM role + iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1) + role_name = "test-role" + assume_role_policy_document = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "lambda.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ], + } + role_arn = iam_client.create_role( + RoleName=role_name, + AssumeRolePolicyDocument=dumps(assume_role_policy_document), + )["Role"]["Arn"] + + function_name = "test-lambda" + + # Create the lambda function using boto3 client + lambda_client = client("lambda", region_name=AWS_REGION_EU_WEST_1) + lambda_client.create_function( + FunctionName=function_name, + Runtime="nodejs4.3", + Role=role_arn, + Handler="index.handler", + Code={"ZipFile": b"fileb://file-path/to/your-deployment-package.zip"}, + Description="Test Lambda function", + Timeout=3, + MemorySize=128, + Publish=True, + Tags={"tag1": "value1", "tag2": "value2"}, + )["FunctionArn"] + + # Attach the policy to the lambda function with a wildcard principal + lambda_client.add_permission( + FunctionName=function_name, + StatementId="public-access", + Action="lambda:InvokeFunction", + Principal="*", + ) + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + + from prowler.providers.aws.services.awslambda.awslambda_service import Lambda + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.awslambda.awslambda_function_not_publicly_accessible.awslambda_function_not_publicly_accessible_fixer.awslambda_client", + new=Lambda(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.awslambda.awslambda_function_not_publicly_accessible.awslambda_function_not_publicly_accessible_fixer import ( + fixer, + ) + + assert fixer(function_name, AWS_REGION_EU_WEST_1) + + @mock_aws + def test_function_public_with_source_account(self): + # Create the mock IAM role + iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1) + role_name = "test-role" + assume_role_policy_document = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "lambda.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ], + } + role_arn = iam_client.create_role( + RoleName=role_name, + AssumeRolePolicyDocument=dumps(assume_role_policy_document), + )["Role"]["Arn"] + + function_name = "test-lambda" + + # Create the lambda function using boto3 client + lambda_client = client("lambda", region_name=AWS_REGION_EU_WEST_1) + function_arn = lambda_client.create_function( + FunctionName=function_name, + Runtime="nodejs4.3", + Role=role_arn, + Handler="index.handler", + Code={"ZipFile": b"fileb://file-path/to/your-deployment-package.zip"}, + Description="Test Lambda function", + Timeout=3, + MemorySize=128, + Publish=True, + Tags={"tag1": "value1", "tag2": "value2"}, + )["FunctionArn"] + + # Attach the policy to the lambda function with a wildcard principal + lambda_client.add_permission( + FunctionName=function_name, + StatementId="public-access", + Action="lambda:InvokeFunction", + Principal="*", + SourceArn=function_arn, + SourceAccount=AWS_ACCOUNT_NUMBER, + ) + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + + from prowler.providers.aws.services.awslambda.awslambda_service import Lambda + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.awslambda.awslambda_function_not_publicly_accessible.awslambda_function_not_publicly_accessible_fixer.awslambda_client", + new=Lambda(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.awslambda.awslambda_function_not_publicly_accessible.awslambda_function_not_publicly_accessible_fixer import ( + fixer, + ) + + assert fixer(function_name, AWS_REGION_EU_WEST_1) + + @mock_aws + def test_function_not_public(self): + # Create the mock IAM role + iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1) + role_name = "test-role" + assume_role_policy_document = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "lambda.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ], + } + role_arn = iam_client.create_role( + RoleName=role_name, + AssumeRolePolicyDocument=dumps(assume_role_policy_document), + )["Role"]["Arn"] + + function_name = "test-lambda" + + # Create the lambda function using boto3 client + lambda_client = client("lambda", region_name=AWS_REGION_EU_WEST_1) + function_arn = lambda_client.create_function( + FunctionName=function_name, + Runtime="nodejs4.3", + Role=role_arn, + Handler="index.handler", + Code={"ZipFile": b"fileb://file-path/to/your-deployment-package.zip"}, + Description="Test Lambda function", + Timeout=3, + MemorySize=128, + Publish=True, + Tags={"tag1": "value1", "tag2": "value2"}, + )["FunctionArn"] + + # Attach the policy to the lambda function with a specific AWS account number as principal + lambda_client.add_permission( + FunctionName=function_name, + StatementId="public-access", + Action="lambda:InvokeFunction", + Principal=AWS_ACCOUNT_NUMBER, + SourceArn=function_arn, + ) + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + + from prowler.providers.aws.services.awslambda.awslambda_service import Lambda + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.awslambda.awslambda_function_not_publicly_accessible.awslambda_function_not_publicly_accessible_fixer.awslambda_client", + new=Lambda(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.awslambda.awslambda_function_not_publicly_accessible.awslambda_function_not_publicly_accessible_fixer import ( + fixer, + ) + + assert fixer(function_name, AWS_REGION_EU_WEST_1) + + @mock_aws + def test_function_public_error(self): + # Create the mock IAM role + iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1) + role_name = "test-role" + assume_role_policy_document = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "lambda.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ], + } + role_arn = iam_client.create_role( + RoleName=role_name, + AssumeRolePolicyDocument=dumps(assume_role_policy_document), + )["Role"]["Arn"] + + function_name = "test-lambda" + + # Create the lambda function using boto3 client + lambda_client = client("lambda", region_name=AWS_REGION_EU_WEST_1) + lambda_client.create_function( + FunctionName=function_name, + Runtime="nodejs4.3", + Role=role_arn, + Handler="index.handler", + Code={"ZipFile": b"fileb://file-path/to/your-deployment-package.zip"}, + Description="Test Lambda function", + Timeout=3, + MemorySize=128, + Publish=True, + Tags={"tag1": "value1", "tag2": "value2"}, + )["FunctionArn"] + + # Attach the policy to the lambda function with a wildcard principal + lambda_client.add_permission( + FunctionName=function_name, + StatementId="public-access", + Action="lambda:InvokeFunction", + Principal="*", + ) + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + + from prowler.providers.aws.services.awslambda.awslambda_service import Lambda + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.awslambda.awslambda_function_not_publicly_accessible.awslambda_function_not_publicly_accessible_fixer.awslambda_client", + new=Lambda(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.awslambda.awslambda_function_not_publicly_accessible.awslambda_function_not_publicly_accessible_fixer import ( + fixer, + ) + + assert not fixer("non-exsting", AWS_REGION_EU_WEST_1) diff --git a/tests/providers/aws/services/cloudtrail/cloudtrail_threat_detection_enumeration/cloudtrail_threat_detection_enumeration_test.py b/tests/providers/aws/services/cloudtrail/cloudtrail_threat_detection_enumeration/cloudtrail_threat_detection_enumeration_test.py index b38d448eac..b56252edb8 100644 --- a/tests/providers/aws/services/cloudtrail/cloudtrail_threat_detection_enumeration/cloudtrail_threat_detection_enumeration_test.py +++ b/tests/providers/aws/services/cloudtrail/cloudtrail_threat_detection_enumeration/cloudtrail_threat_detection_enumeration_test.py @@ -19,10 +19,10 @@ def mock_get_trail_arn_template(region=None, *_) -> str: def mock__get_lookup_events__(trail=None, event_name=None, minutes=None, *_) -> list: return [ { - "CloudTrailEvent": '{"eventName": "DescribeAccessEntry", "userIdentity": {"type": "IAMUser", "principalId": "EXAMPLE6E4XEGITWATV6R", "arn": "arn:aws:iam::123456789012:user/Mateo", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "userName": "Mateo", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}' + "CloudTrailEvent": '{"eventName": "DescribeAccessEntry", "userIdentity": {"type": "IAMUser", "principalId": "EXAMPLE6E4XEGITWATV6R", "arn": "arn:aws:iam::123456789012:user/Attacker", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "userName": "Attacker", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}' }, { - "CloudTrailEvent": '{"eventName": "DescribeAccountAttributes", "userIdentity": {"type": "IAMUser", "principalId": "EXAMPLE6E4XEGITWATV6R", "arn": "arn:aws:iam::123456789012:user/Mateo", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "userName": "Mateo", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}' + "CloudTrailEvent": '{"eventName": "DescribeAccountAttributes", "userIdentity": {"type": "IAMUser", "principalId": "EXAMPLE6E4XEGITWATV6R", "arn": "arn:aws:iam::123456789012:user/Attacker", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "userName": "Attacker", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}' }, ] @@ -50,12 +50,15 @@ class Test_cloudtrail_threat_detection_enumeration: cloudtrail_client.audited_account = AWS_ACCOUNT_NUMBER cloudtrail_client.region = AWS_REGION_US_EAST_1 - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=set_mocked_aws_provider(), - ), mock.patch( - "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration.cloudtrail_client", - new=cloudtrail_client, + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider(), + ), + mock.patch( + "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration.cloudtrail_client", + new=cloudtrail_client, + ), ): # Test Check from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration import ( @@ -99,12 +102,15 @@ class Test_cloudtrail_threat_detection_enumeration: cloudtrail_client._lookup_events = mock__get_lookup_events__ cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=set_mocked_aws_provider(), - ), mock.patch( - "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration.cloudtrail_client", - new=cloudtrail_client, + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider(), + ), + mock.patch( + "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration.cloudtrail_client", + new=cloudtrail_client, + ), ): # Test Check from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration import ( @@ -148,12 +154,15 @@ class Test_cloudtrail_threat_detection_enumeration: cloudtrail_client._lookup_events = mock__get_lookup_events__ cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=set_mocked_aws_provider(), - ), mock.patch( - "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration.cloudtrail_client", - new=cloudtrail_client, + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider(), + ), + mock.patch( + "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration.cloudtrail_client", + new=cloudtrail_client, + ), ): # Test Check from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration import ( @@ -167,13 +176,13 @@ class Test_cloudtrail_threat_detection_enumeration: assert result[0].status == "FAIL" assert ( result[0].status_extended - == "Potential enumeration attack detected from AWS IAMUser Mateo with an threshold of 1.0." + == "Potential enumeration attack detected from AWS IAMUser Attacker with an threshold of 1.0." ) - assert result[0].resource_id == AWS_ACCOUNT_NUMBER + assert result[0].resource_id == "Attacker" assert result[0].region == AWS_REGION_US_EAST_1 assert ( result[0].resource_arn - == f"arn:aws:cloudtrail:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:trail" + == f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:user/Attacker" ) @mock_aws @@ -198,12 +207,15 @@ class Test_cloudtrail_threat_detection_enumeration: cloudtrail_client._lookup_events = mock__get_lookup_events__ cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=set_mocked_aws_provider(), - ), mock.patch( - "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration.cloudtrail_client", - new=cloudtrail_client, + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider(), + ), + mock.patch( + "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration.cloudtrail_client", + new=cloudtrail_client, + ), ): # Test Check from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration import ( @@ -247,12 +259,15 @@ class Test_cloudtrail_threat_detection_enumeration: cloudtrail_client._lookup_events = mock__get_lookup_events_aws_service__ cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=set_mocked_aws_provider(), - ), mock.patch( - "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration.cloudtrail_client", - new=cloudtrail_client, + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider(), + ), + mock.patch( + "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration.cloudtrail_client", + new=cloudtrail_client, + ), ): # Test Check from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration import ( diff --git a/tests/providers/aws/services/cloudtrail/cloudtrail_threat_detection_llm_jacking/cloudtrail_threat_detection_llm_jacking_test.py b/tests/providers/aws/services/cloudtrail/cloudtrail_threat_detection_llm_jacking/cloudtrail_threat_detection_llm_jacking_test.py index 06ad5e33cb..fa73c8a4a7 100644 --- a/tests/providers/aws/services/cloudtrail/cloudtrail_threat_detection_llm_jacking/cloudtrail_threat_detection_llm_jacking_test.py +++ b/tests/providers/aws/services/cloudtrail/cloudtrail_threat_detection_llm_jacking/cloudtrail_threat_detection_llm_jacking_test.py @@ -19,10 +19,10 @@ def mock_get_trail_arn_template(region=None, *_) -> str: def mock__get_lookup_events__(trail=None, event_name=None, minutes=None, *_) -> list: return [ { - "CloudTrailEvent": '{"eventName": "InvokeModel", "userIdentity": {"type": "IAMUser", "principalId": "EXAMPLE6E4XEGITWATV6R", "arn": "arn:aws:iam::123456789012:user/Mateo", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "userName": "Mateo", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}' + "CloudTrailEvent": '{"eventName": "InvokeModel", "userIdentity": {"type": "IAMUser", "principalId": "EXAMPLE6E4XEGITWATV6R", "arn": "arn:aws:iam::123456789012:user/Attacker", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "userName": "Attacker", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}' }, { - "CloudTrailEvent": '{"eventName": "InvokeModelWithResponseStream", "userIdentity": {"type": "IAMUser", "principalId": "EXAMPLE6E4XEGITWATV6R", "arn": "arn:aws:iam::123456789012:user/Mateo", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "userName": "Mateo", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}' + "CloudTrailEvent": '{"eventName": "InvokeModelWithResponseStream", "userIdentity": {"type": "IAMUser", "principalId": "EXAMPLE6E4XEGITWATV6R", "arn": "arn:aws:iam::123456789012:user/Attacker", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "userName": "Attacker", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}' }, ] @@ -50,12 +50,15 @@ class Test_cloudtrail_threat_detection_llm_jacking: cloudtrail_client.audited_account = AWS_ACCOUNT_NUMBER cloudtrail_client.region = AWS_REGION_US_EAST_1 - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=set_mocked_aws_provider(), - ), mock.patch( - "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking.cloudtrail_client", - new=cloudtrail_client, + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider(), + ), + mock.patch( + "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking.cloudtrail_client", + new=cloudtrail_client, + ), ): # Test Check from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking import ( @@ -96,12 +99,15 @@ class Test_cloudtrail_threat_detection_llm_jacking: cloudtrail_client._lookup_events = mock__get_lookup_events__ cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=set_mocked_aws_provider(), - ), mock.patch( - "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking.cloudtrail_client", - new=cloudtrail_client, + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider(), + ), + mock.patch( + "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking.cloudtrail_client", + new=cloudtrail_client, + ), ): # Test Check from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking import ( @@ -145,12 +151,15 @@ class Test_cloudtrail_threat_detection_llm_jacking: cloudtrail_client._lookup_events = mock__get_lookup_events__ cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=set_mocked_aws_provider(), - ), mock.patch( - "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking.cloudtrail_client", - new=cloudtrail_client, + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider(), + ), + mock.patch( + "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking.cloudtrail_client", + new=cloudtrail_client, + ), ): # Test Check from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking import ( @@ -164,13 +173,13 @@ class Test_cloudtrail_threat_detection_llm_jacking: assert result[0].status == "FAIL" assert ( result[0].status_extended - == "Potential LLM Jacking attack detected from AWS IAMUser Mateo with an threshold of 1.0." + == "Potential LLM Jacking attack detected from AWS IAMUser Attacker with an threshold of 1.0." ) - assert result[0].resource_id == AWS_ACCOUNT_NUMBER + assert result[0].resource_id == "Attacker" assert result[0].region == AWS_REGION_US_EAST_1 assert ( result[0].resource_arn - == f"arn:aws:cloudtrail:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:trail" + == f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:user/Attacker" ) @mock_aws @@ -195,12 +204,15 @@ class Test_cloudtrail_threat_detection_llm_jacking: cloudtrail_client._lookup_events = mock__get_lookup_events__ cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=set_mocked_aws_provider(), - ), mock.patch( - "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking.cloudtrail_client", - new=cloudtrail_client, + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider(), + ), + mock.patch( + "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking.cloudtrail_client", + new=cloudtrail_client, + ), ): # Test Check from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking import ( @@ -244,12 +256,15 @@ class Test_cloudtrail_threat_detection_llm_jacking: cloudtrail_client._lookup_events = mock__get_lookup_events_aws_service__ cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=set_mocked_aws_provider(), - ), mock.patch( - "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking.cloudtrail_client", - new=cloudtrail_client, + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider(), + ), + mock.patch( + "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking.cloudtrail_client", + new=cloudtrail_client, + ), ): # Test Check from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking import ( diff --git a/tests/providers/aws/services/cloudtrail/cloudtrail_threat_detection_privilege_escalation/cloudtrail_threat_detection_privilege_escalation_test.py b/tests/providers/aws/services/cloudtrail/cloudtrail_threat_detection_privilege_escalation/cloudtrail_threat_detection_privilege_escalation_test.py index ba278db3cf..19a50172cb 100644 --- a/tests/providers/aws/services/cloudtrail/cloudtrail_threat_detection_privilege_escalation/cloudtrail_threat_detection_privilege_escalation_test.py +++ b/tests/providers/aws/services/cloudtrail/cloudtrail_threat_detection_privilege_escalation/cloudtrail_threat_detection_privilege_escalation_test.py @@ -19,10 +19,10 @@ def mock_get_trail_arn_template(region=None, *_) -> str: def mock__get_lookup_events__(trail=None, event_name=None, minutes=None, *_) -> list: return [ { - "CloudTrailEvent": '{"eventName": "CreateLoginProfile", "userIdentity": {"type": "IAMUser", "principalId": "EXAMPLE6E4XEGITWATV6R", "arn": "arn:aws:iam::123456789012:user/Mateo", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "userName": "Mateo", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}' + "CloudTrailEvent": '{"eventName": "CreateLoginProfile", "userIdentity": {"type": "IAMUser", "principalId": "EXAMPLE6E4XEGITWATV6R", "arn": "arn:aws:iam::123456789012:user/Attacker", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "userName": "Attacker", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}' }, { - "CloudTrailEvent": '{"eventName": "UpdateLoginProfile", "userIdentity": {"type": "IAMUser", "principalId": "EXAMPLE6E4XEGITWATV6R", "arn": "arn:aws:iam::123456789012:user/Mateo", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "userName": "Mateo", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}' + "CloudTrailEvent": '{"eventName": "UpdateLoginProfile", "userIdentity": {"type": "IAMUser", "principalId": "EXAMPLE6E4XEGITWATV6R", "arn": "arn:aws:iam::123456789012:user/Attacker", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "userName": "Attacker", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}' }, ] @@ -50,12 +50,15 @@ class Test_cloudtrail_threat_detection_privilege_escalation: cloudtrail_client.audited_account = AWS_ACCOUNT_NUMBER cloudtrail_client.region = AWS_REGION_US_EAST_1 - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=set_mocked_aws_provider(), - ), mock.patch( - "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation.cloudtrail_client", - new=cloudtrail_client, + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider(), + ), + mock.patch( + "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation.cloudtrail_client", + new=cloudtrail_client, + ), ): # Test Check from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation import ( @@ -97,12 +100,15 @@ class Test_cloudtrail_threat_detection_privilege_escalation: cloudtrail_client._lookup_events = mock__get_lookup_events__ cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=set_mocked_aws_provider(), - ), mock.patch( - "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation.cloudtrail_client", - new=cloudtrail_client, + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider(), + ), + mock.patch( + "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation.cloudtrail_client", + new=cloudtrail_client, + ), ): # Test Check from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation import ( @@ -147,12 +153,15 @@ class Test_cloudtrail_threat_detection_privilege_escalation: cloudtrail_client._lookup_events = mock__get_lookup_events__ cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=set_mocked_aws_provider(), - ), mock.patch( - "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation.cloudtrail_client", - new=cloudtrail_client, + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider(), + ), + mock.patch( + "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation.cloudtrail_client", + new=cloudtrail_client, + ), ): # Test Check from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation import ( @@ -166,13 +175,13 @@ class Test_cloudtrail_threat_detection_privilege_escalation: assert result[0].status == "FAIL" assert ( result[0].status_extended - == "Potential privilege escalation attack detected from AWS IAMUser Mateo with an threshold of 1.0." + == "Potential privilege escalation attack detected from AWS IAMUser Attacker with an threshold of 1.0." ) - assert result[0].resource_id == AWS_ACCOUNT_NUMBER + assert result[0].resource_id == "Attacker" assert result[0].region == AWS_REGION_US_EAST_1 assert ( result[0].resource_arn - == f"arn:aws:cloudtrail:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:trail" + == f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:user/Attacker" ) @mock_aws @@ -197,12 +206,15 @@ class Test_cloudtrail_threat_detection_privilege_escalation: cloudtrail_client._lookup_events = mock__get_lookup_events__ cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=set_mocked_aws_provider(), - ), mock.patch( - "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation.cloudtrail_client", - new=cloudtrail_client, + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider(), + ), + mock.patch( + "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation.cloudtrail_client", + new=cloudtrail_client, + ), ): # Test Check from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation import ( @@ -247,12 +259,15 @@ class Test_cloudtrail_threat_detection_privilege_escalation: cloudtrail_client._lookup_events = mock__get_lookup_events_aws_service__ cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=set_mocked_aws_provider(), - ), mock.patch( - "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation.cloudtrail_client", - new=cloudtrail_client, + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider(), + ), + mock.patch( + "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation.cloudtrail_client", + new=cloudtrail_client, + ), ): # Test Check from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation import ( diff --git a/tests/providers/aws/services/ec2/ec2_instance_port_cifs_exposed_to_internet/ec2_instance_port_cifs_exposed_to_internet_fixer_test.py b/tests/providers/aws/services/ec2/ec2_instance_port_cifs_exposed_to_internet/ec2_instance_port_cifs_exposed_to_internet_fixer_test.py new file mode 100644 index 0000000000..ef7771637e --- /dev/null +++ b/tests/providers/aws/services/ec2/ec2_instance_port_cifs_exposed_to_internet/ec2_instance_port_cifs_exposed_to_internet_fixer_test.py @@ -0,0 +1,430 @@ +from unittest import mock + +import botocore +import botocore.client +from boto3 import client, resource +from moto import mock_aws + +from tests.providers.aws.utils import AWS_REGION_EU_WEST_1, set_mocked_aws_provider + +mock_make_api_call = botocore.client.BaseClient._make_api_call + + +def mock_make_api_call_error(self, operation_name, kwarg): + if operation_name == "RevokeSecurityGroupIngress": + raise botocore.exceptions.ClientError( + { + "Error": { + "Code": "InvalidPermission.NotFound", + "Message": "The specified rule does not exist in this security group.", + } + }, + operation_name, + ) + return mock_make_api_call(self, operation_name, kwarg) + + +class Test_ec2_instance_port_cifs_exposed_to_internet_fixer: + @mock_aws + def test_ec2_instance_exposed_port_in_private_subnet_with_ip4_and_ip6(self): + # Create EC2 Mocked Resources + ec2_client = client("ec2", region_name=AWS_REGION_EU_WEST_1) + ec2_resource = resource("ec2", region_name=AWS_REGION_EU_WEST_1) + vpc_id = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"] + default_sg = ec2_client.describe_security_groups(GroupNames=["default"])[ + "SecurityGroups" + ][0] + default_sg_id = default_sg["GroupId"] + ec2_client.authorize_security_group_ingress( + GroupId=default_sg_id, + IpPermissions=[ + { + "IpProtocol": "tcp", + "FromPort": 1, + "ToPort": 1000, + "IpRanges": [{"CidrIp": "0.0.0.0/0"}, {"CidrIp": "10.0.0.0/24"}], + "Ipv6Ranges": [{"CidrIpv6": "::/0"}, {"CidrIpv6": "2001:db8::/32"}], + }, + { + "IpProtocol": "tcp", + "FromPort": 445, + "ToPort": 445, + "IpRanges": [{"CidrIp": "0.0.0.0/0"}, {"CidrIp": "10.0.0.0/24"}], + "Ipv6Ranges": [{"CidrIpv6": "::/0"}, {"CidrIpv6": "2001:db8::/32"}], + }, + ], + ) + subnet_id = ec2_client.create_subnet(VpcId=vpc_id, CidrBlock="10.0.0.0/16")[ + "Subnet" + ]["SubnetId"] + instance_id = ec2_resource.create_instances( + ImageId="ami-12345678", + MinCount=1, + MaxCount=1, + InstanceType="t2.micro", + SecurityGroupIds=[default_sg_id], + SubnetId=subnet_id, + TagSpecifications=[ + {"ResourceType": "instance", "Tags": [{"Key": "Name", "Value": "test"}]} + ], + )[0].id + + from prowler.providers.aws.services.ec2.ec2_service import EC2 + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.ec2.ec2_instance_port_cifs_exposed_to_internet.ec2_instance_port_cifs_exposed_to_internet_fixer.ec2_client", + new=EC2(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.ec2.ec2_instance_port_cifs_exposed_to_internet.ec2_instance_port_cifs_exposed_to_internet_fixer import ( + fixer, + ) + + assert fixer(instance_id, AWS_REGION_EU_WEST_1) + + @mock_aws + def test_ec2_instance_exposed_port_error(self): + with mock.patch( + "botocore.client.BaseClient._make_api_call", new=mock_make_api_call_error + ): + # Create EC2 Mocked Resources + ec2_client = client("ec2", region_name=AWS_REGION_EU_WEST_1) + ec2_resource = resource("ec2", region_name=AWS_REGION_EU_WEST_1) + vpc_id = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"] + default_sg = ec2_client.describe_security_groups(GroupNames=["default"])[ + "SecurityGroups" + ][0] + default_sg_id = default_sg["GroupId"] + ec2_client.authorize_security_group_ingress( + GroupId=default_sg_id, + IpPermissions=[ + { + "IpProtocol": "tcp", + "FromPort": 1, + "ToPort": 1000, + "IpRanges": [ + {"CidrIp": "0.0.0.0/0"}, + {"CidrIp": "10.0.0.0/24"}, + ], + "Ipv6Ranges": [ + {"CidrIpv6": "::/0"}, + {"CidrIpv6": "2001:db8::/32"}, + ], + }, + { + "IpProtocol": "tcp", + "FromPort": 445, + "ToPort": 445, + "IpRanges": [ + {"CidrIp": "0.0.0.0/0"}, + {"CidrIp": "10.0.0.0/24"}, + ], + "Ipv6Ranges": [ + {"CidrIpv6": "::/0"}, + {"CidrIpv6": "2001:db8::/32"}, + ], + }, + ], + ) + subnet_id = ec2_client.create_subnet(VpcId=vpc_id, CidrBlock="10.0.0.0/16")[ + "Subnet" + ]["SubnetId"] + instance_id = ec2_resource.create_instances( + ImageId="ami-12345678", + MinCount=1, + MaxCount=1, + InstanceType="t2.micro", + SecurityGroupIds=[default_sg_id], + SubnetId=subnet_id, + TagSpecifications=[ + { + "ResourceType": "instance", + "Tags": [{"Key": "Name", "Value": "test"}], + } + ], + )[0].id + + from prowler.providers.aws.services.ec2.ec2_service import EC2 + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.ec2.ec2_instance_port_cifs_exposed_to_internet.ec2_instance_port_cifs_exposed_to_internet_fixer.ec2_client", + new=EC2(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.ec2.ec2_instance_port_cifs_exposed_to_internet.ec2_instance_port_cifs_exposed_to_internet_fixer import ( + fixer, + ) + + assert not fixer(instance_id, AWS_REGION_EU_WEST_1) + + @mock_aws + def test_ec2_instance_exposed_port_in_private_subnet_only_with_ip4(self): + # Create EC2 Mocked Resources + ec2_client = client("ec2", region_name=AWS_REGION_EU_WEST_1) + ec2_resource = resource("ec2", region_name=AWS_REGION_EU_WEST_1) + vpc_id = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"] + default_sg = ec2_client.describe_security_groups(GroupNames=["default"])[ + "SecurityGroups" + ][0] + default_sg_id = default_sg["GroupId"] + ec2_client.authorize_security_group_ingress( + GroupId=default_sg_id, + IpPermissions=[ + { + "IpProtocol": "tcp", + "FromPort": 139, + "ToPort": 139, + "IpRanges": [{"CidrIp": "0.0.0.0/0"}, {"CidrIp": "10.0.0.0/24"}], + }, + { + "IpProtocol": "tcp", + "FromPort": 445, + "ToPort": 445, + "IpRanges": [{"CidrIp": "0.0.0.0/0"}, {"CidrIp": "10.0.0.0/24"}], + }, + ], + ) + subnet_id = ec2_client.create_subnet(VpcId=vpc_id, CidrBlock="10.0.0.0/16")[ + "Subnet" + ]["SubnetId"] + instance_id = ec2_resource.create_instances( + ImageId="ami-12345678", + MinCount=1, + MaxCount=1, + InstanceType="t2.micro", + SecurityGroupIds=[default_sg_id], + SubnetId=subnet_id, + TagSpecifications=[ + {"ResourceType": "instance", "Tags": [{"Key": "Name", "Value": "test"}]} + ], + )[0].id + + from prowler.providers.aws.services.ec2.ec2_service import EC2 + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.ec2.ec2_instance_port_cifs_exposed_to_internet.ec2_instance_port_cifs_exposed_to_internet_fixer.ec2_client", + new=EC2(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.ec2.ec2_instance_port_cifs_exposed_to_internet.ec2_instance_port_cifs_exposed_to_internet_fixer import ( + fixer, + ) + + assert fixer(instance_id, AWS_REGION_EU_WEST_1) + + @mock_aws + def test_ec2_instance_exposed_port_in_private_subnet_only_with_ip6(self): + # Create EC2 Mocked Resources + ec2_client = client("ec2", region_name=AWS_REGION_EU_WEST_1) + ec2_resource = resource("ec2", region_name=AWS_REGION_EU_WEST_1) + vpc_id = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"] + default_sg = ec2_client.describe_security_groups(GroupNames=["default"])[ + "SecurityGroups" + ][0] + default_sg_id = default_sg["GroupId"] + ec2_client.authorize_security_group_ingress( + GroupId=default_sg_id, + IpPermissions=[ + { + "IpProtocol": "tcp", + "FromPort": 139, + "ToPort": 139, + "Ipv6Ranges": [{"CidrIpv6": "::/0"}, {"CidrIpv6": "2001:db8::/32"}], + }, + { + "IpProtocol": "tcp", + "FromPort": 445, + "ToPort": 445, + "Ipv6Ranges": [{"CidrIpv6": "::/0"}, {"CidrIpv6": "2001:db8::/32"}], + }, + ], + ) + subnet_id = ec2_client.create_subnet(VpcId=vpc_id, CidrBlock="10.0.0.0/16")[ + "Subnet" + ]["SubnetId"] + instance_id = ec2_resource.create_instances( + ImageId="ami-12345678", + MinCount=1, + MaxCount=1, + InstanceType="t2.micro", + SecurityGroupIds=[default_sg_id], + SubnetId=subnet_id, + TagSpecifications=[ + {"ResourceType": "instance", "Tags": [{"Key": "Name", "Value": "test"}]} + ], + )[0].id + + from prowler.providers.aws.services.ec2.ec2_service import EC2 + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.ec2.ec2_instance_port_cifs_exposed_to_internet.ec2_instance_port_cifs_exposed_to_internet_fixer.ec2_client", + new=EC2(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.ec2.ec2_instance_port_cifs_exposed_to_internet.ec2_instance_port_cifs_exposed_to_internet_fixer import ( + fixer, + ) + + assert fixer(instance_id, AWS_REGION_EU_WEST_1) + + @mock_aws + def test_ec2_instance_exposed_port_in_public_subnet_only_139_port(self): + # Create EC2 Mocked Resources + ec2_client = client("ec2", region_name=AWS_REGION_EU_WEST_1) + ec2_resource = resource("ec2", region_name=AWS_REGION_EU_WEST_1) + vpc_id = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"] + default_sg = ec2_client.describe_security_groups(GroupNames=["default"])[ + "SecurityGroups" + ][0] + default_sg_id = default_sg["GroupId"] + ec2_client.authorize_security_group_ingress( + GroupId=default_sg_id, + IpPermissions=[ + { + "IpProtocol": "tcp", + "FromPort": 139, + "ToPort": 139, + "IpRanges": [{"CidrIp": "0.0.0.0/0"}], + } + ], + ) + subnet_id = ec2_client.create_subnet(VpcId=vpc_id, CidrBlock="10.0.0.0/16")[ + "Subnet" + ]["SubnetId"] + instance = ec2_resource.create_instances( + ImageId="ami-12345678", + MinCount=1, + MaxCount=1, + InstanceType="t2.micro", + SecurityGroupIds=[default_sg_id], + NetworkInterfaces=[ + { + "DeviceIndex": 0, + "SubnetId": subnet_id, + "AssociatePublicIpAddress": True, + } + ], + TagSpecifications=[ + {"ResourceType": "instance", "Tags": [{"Key": "Name", "Value": "test"}]} + ], + )[0] + + from prowler.providers.aws.services.ec2.ec2_service import EC2 + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.ec2.ec2_instance_port_cifs_exposed_to_internet.ec2_instance_port_cifs_exposed_to_internet_fixer.ec2_client", + new=EC2(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.ec2.ec2_instance_port_cifs_exposed_to_internet.ec2_instance_port_cifs_exposed_to_internet_fixer import ( + fixer, + ) + + assert fixer(instance.id, AWS_REGION_EU_WEST_1) + + @mock_aws + def test_ec2_instance_exposed_port_with_public_ip_in_public_subnet_only_445_port( + self, + ): + # Create EC2 Mocked Resources + ec2_client = client("ec2", region_name=AWS_REGION_EU_WEST_1) + ec2_resource = resource("ec2", region_name=AWS_REGION_EU_WEST_1) + vpc_id = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"] + default_sg = ec2_client.describe_security_groups(GroupNames=["default"])[ + "SecurityGroups" + ][0] + default_sg_id = default_sg["GroupId"] + ec2_client.authorize_security_group_ingress( + GroupId=default_sg_id, + IpPermissions=[ + { + "IpProtocol": "tcp", + "FromPort": 445, + "ToPort": 445, + "IpRanges": [{"CidrIp": "0.0.0.0/0"}], + } + ], + ) + subnet_id = ec2_client.create_subnet(VpcId=vpc_id, CidrBlock="10.0.0.0/16")[ + "Subnet" + ]["SubnetId"] + # add default route of subnet to an internet gateway to make it public + igw_id = ec2_client.create_internet_gateway()["InternetGateway"][ + "InternetGatewayId" + ] + # attach internet gateway to subnet + ec2_client.attach_internet_gateway(InternetGatewayId=igw_id, VpcId=vpc_id) + # create route table + route_table_id = ec2_client.create_route_table(VpcId=vpc_id)["RouteTable"][ + "RouteTableId" + ] + # associate route table with subnet + ec2_client.associate_route_table( + RouteTableId=route_table_id, SubnetId=subnet_id + ) + # add route to route table + ec2_client.create_route( + RouteTableId=route_table_id, + DestinationCidrBlock="0.0.0.0/0", + GatewayId=igw_id, + ) + instance = ec2_resource.create_instances( + ImageId="ami-12345678", + MinCount=1, + MaxCount=1, + InstanceType="t2.micro", + SecurityGroupIds=[default_sg_id], + NetworkInterfaces=[ + { + "DeviceIndex": 0, + "SubnetId": subnet_id, + "AssociatePublicIpAddress": True, + } + ], + TagSpecifications=[ + {"ResourceType": "instance", "Tags": [{"Key": "Name", "Value": "test"}]} + ], + )[0] + + from prowler.providers.aws.services.ec2.ec2_service import EC2 + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.ec2.ec2_instance_port_cifs_exposed_to_internet.ec2_instance_port_cifs_exposed_to_internet_fixer.ec2_client", + new=EC2(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.ec2.ec2_instance_port_cifs_exposed_to_internet.ec2_instance_port_cifs_exposed_to_internet_fixer import ( + fixer, + ) + + assert fixer(instance.id, AWS_REGION_EU_WEST_1) diff --git a/tests/providers/aws/services/ec2/ec2_launch_template_imdsv2_required/ec2_launch_template_imdsv2_required_test.py b/tests/providers/aws/services/ec2/ec2_launch_template_imdsv2_required/ec2_launch_template_imdsv2_required_test.py new file mode 100644 index 0000000000..22ff04f349 --- /dev/null +++ b/tests/providers/aws/services/ec2/ec2_launch_template_imdsv2_required/ec2_launch_template_imdsv2_required_test.py @@ -0,0 +1,195 @@ +from unittest import mock + +import botocore +from boto3 import client +from moto import mock_aws + +from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider + +make_api_call = botocore.client.BaseClient._make_api_call + + +def mock_make_api_call(self, operation_name, kwarg): + if operation_name == "DescribeLaunchTemplateVersions": + return { + "LaunchTemplateVersions": [ + { + "VersionNumber": 1, + "LaunchTemplateData": { + "MetadataOptions": { + "HttpEndpoint": "enabled", + "HttpTokens": "required", + } + }, + } + ] + } + return make_api_call(self, operation_name, kwarg) + + +def mock_make_api_call_not_required(self, operation_name, kwarg): + if operation_name == "DescribeLaunchTemplateVersions": + return { + "LaunchTemplateVersions": [ + { + "VersionNumber": 1, + "LaunchTemplateData": { + "MetadataOptions": { + "HttpEndpoint": "enabled", + "HttpTokens": "optional", + } + }, + } + ] + } + return make_api_call(self, operation_name, kwarg) + + +class Test_ec2_launch_template_imdsv2_required: + @mock_aws + def test_no_launch_templates(self): + ec2_client = client("ec2", region_name=AWS_REGION_US_EAST_1) + ec2_client.launch_templates = [] + + from prowler.providers.aws.services.ec2.ec2_service import EC2 + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.ec2.ec2_launch_template_imdsv2_required.ec2_launch_template_imdsv2_required.ec2_client", + new=EC2(aws_provider), + ), + ): + # Test Check + from prowler.providers.aws.services.ec2.ec2_launch_template_imdsv2_required.ec2_launch_template_imdsv2_required import ( + ec2_launch_template_imdsv2_required, + ) + + check = ec2_launch_template_imdsv2_required() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_launch_template_imdsv2_required(self): + with mock.patch( + "botocore.client.BaseClient._make_api_call", new=mock_make_api_call + ): + launch_template_name = "test-imdsv2-required" + ec2_client = client("ec2", region_name=AWS_REGION_US_EAST_1) + ec2_client.create_launch_template( + LaunchTemplateName=launch_template_name, + VersionDescription="Launch Template with IMDSv2 required", + LaunchTemplateData={ + "InstanceType": "t1.micro", + "MetadataOptions": { + "HttpEndpoint": "enabled", + "HttpTokens": "required", + }, + }, + ) + + launch_template_id = ec2_client.describe_launch_templates( + LaunchTemplateNames=[launch_template_name] + )["LaunchTemplates"][0]["LaunchTemplateId"] + + from prowler.providers.aws.services.ec2.ec2_service import EC2 + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.ec2.ec2_launch_template_imdsv2_required.ec2_launch_template_imdsv2_required.ec2_client", + new=EC2(aws_provider), + ), + ): + # Test Check + from prowler.providers.aws.services.ec2.ec2_launch_template_imdsv2_required.ec2_launch_template_imdsv2_required import ( + ec2_launch_template_imdsv2_required, + ) + + check = ec2_launch_template_imdsv2_required() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"EC2 Launch Template {launch_template_name} has IMDSv2 enabled and required in the following versions: 1." + ) + assert result[0].resource_id == launch_template_id + assert result[0].region == AWS_REGION_US_EAST_1 + assert ( + result[0].resource_arn + == f"arn:aws:ec2:{AWS_REGION_US_EAST_1}:123456789012:launch-template/{launch_template_id}" + ) + assert result[0].resource_tags == [] + + @mock_aws + def test_launch_template_imdsv2_not_required(self): + with mock.patch( + "botocore.client.BaseClient._make_api_call", + new=mock_make_api_call_not_required, + ): + ec2_client = client("ec2", region_name=AWS_REGION_US_EAST_1) + launch_template_name = "test-imdsv2-not-required" + ec2_client.create_launch_template( + LaunchTemplateName=launch_template_name, + VersionDescription="Launch Template without IMDSv2 required", + LaunchTemplateData={ + "InstanceType": "t1.micro", + "MetadataOptions": { + "HttpEndpoint": "enabled", + "HttpTokens": "optional", + }, + }, + ) + + launch_template_id = ec2_client.describe_launch_templates( + LaunchTemplateNames=[launch_template_name] + )["LaunchTemplates"][0]["LaunchTemplateId"] + + from prowler.providers.aws.services.ec2.ec2_service import EC2 + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.ec2.ec2_launch_template_imdsv2_required.ec2_launch_template_imdsv2_required.ec2_client", + new=EC2(aws_provider), + ), + ): + # Test Check + from prowler.providers.aws.services.ec2.ec2_launch_template_imdsv2_required.ec2_launch_template_imdsv2_required import ( + ec2_launch_template_imdsv2_required, + ) + + check = ec2_launch_template_imdsv2_required() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"EC2 Launch Template {launch_template_name} has IMDSv2 disabled or not required in the following versions: 1." + ) + assert result[0].resource_id == launch_template_id + assert result[0].region == AWS_REGION_US_EAST_1 + assert ( + result[0].resource_arn + == f"arn:aws:ec2:{AWS_REGION_US_EAST_1}:123456789012:launch-template/{launch_template_id}" + ) + assert result[0].resource_tags == [] diff --git a/tests/providers/aws/services/ec2/ec2_service_test.py b/tests/providers/aws/services/ec2/ec2_service_test.py index f4fd93b14a..629e319f38 100644 --- a/tests/providers/aws/services/ec2/ec2_service_test.py +++ b/tests/providers/aws/services/ec2/ec2_service_test.py @@ -36,6 +36,10 @@ def mock_make_api_call(self, operation_name, kwarg): "foobar123".encode(encoding_format_utf_8) ).decode(encoding_format_utf_8), "NetworkInterfaces": [{"AssociatePublicIpAddress": True}], + "MetadataOptions": { + "HttpEndpoint": "enabled", + "HttpTokens": "optional", + }, }, } ] @@ -741,6 +745,8 @@ class Test_EC2_Service: ) assert version.template_data.associate_public_ip_address + assert version.template_data.http_endpoint == "enabled" + assert version.template_data.http_tokens == "optional" # Test EC2 Describe VPN Endpoints @mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) diff --git a/tests/providers/aws/services/ecr/ecr_repositories_not_publicly_accessible/ecr_repositories_not_publicly_accessible_fixer_test.py b/tests/providers/aws/services/ecr/ecr_repositories_not_publicly_accessible/ecr_repositories_not_publicly_accessible_fixer_test.py new file mode 100644 index 0000000000..d2b4953cdc --- /dev/null +++ b/tests/providers/aws/services/ecr/ecr_repositories_not_publicly_accessible/ecr_repositories_not_publicly_accessible_fixer_test.py @@ -0,0 +1,135 @@ +from json import dumps +from unittest import mock + +from boto3 import client +from moto import mock_aws + +from tests.providers.aws.utils import AWS_REGION_EU_WEST_1, set_mocked_aws_provider + + +class Test_ecr_repositories_not_publicly_accessible_fixer: + @mock_aws + def test_ecr_repository_public(self): + ecr_client = client("ecr", region_name=AWS_REGION_EU_WEST_1) + + repository_name = "test-repo" + ecr_client.create_repository(repositoryName=repository_name) + + ecr_client.set_repository_policy( + repositoryName=repository_name, + policyText=dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"AWS": "*"}, + "Action": "ecr:*", + "Resource": f"arn:aws:ecr:{AWS_REGION_EU_WEST_1}:123456789012:repository/{repository_name}", + } + ], + } + ), + ) + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + + from prowler.providers.aws.services.ecr.ecr_service import ECR + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.ecr.ecr_repositories_not_publicly_accessible.ecr_repositories_not_publicly_accessible_fixer.ecr_client", + new=ECR(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.ecr.ecr_repositories_not_publicly_accessible.ecr_repositories_not_publicly_accessible_fixer import ( + fixer, + ) + + assert fixer(repository_name, AWS_REGION_EU_WEST_1) + + @mock_aws + def test_ecr_repository_not_public(self): + ecr_client = client("ecr", region_name=AWS_REGION_EU_WEST_1) + + repository_name = "test-repo" + ecr_client.create_repository(repositoryName=repository_name) + + ecr_client.set_repository_policy( + repositoryName=repository_name, + policyText=dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"AWS": "123456789012"}, + "Action": "ecr:*", + "Resource": f"arn:aws:ecr:{AWS_REGION_EU_WEST_1}:123456789012:repository/{repository_name}", + } + ], + } + ), + ) + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + + from prowler.providers.aws.services.ecr.ecr_service import ECR + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.ecr.ecr_repositories_not_publicly_accessible.ecr_repositories_not_publicly_accessible_fixer.ecr_client", + new=ECR(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.ecr.ecr_repositories_not_publicly_accessible.ecr_repositories_not_publicly_accessible_fixer import ( + fixer, + ) + + assert fixer(repository_name, AWS_REGION_EU_WEST_1) + + @mock_aws + def test_ecr_repository_public_error(self): + ecr_client = client("ecr", region_name=AWS_REGION_EU_WEST_1) + + repository_name = "test-repo" + ecr_client.create_repository(repositoryName=repository_name) + + ecr_client.set_repository_policy( + repositoryName=repository_name, + policyText=dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"AWS": "*"}, + "Action": "ecr:*", + "Resource": f"arn:aws:ecr:{AWS_REGION_EU_WEST_1}:123456789012:repository/{repository_name}", + } + ], + } + ), + ) + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + + from prowler.providers.aws.services.ecr.ecr_service import ECR + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.ecr.ecr_repositories_not_publicly_accessible.ecr_repositories_not_publicly_accessible_fixer.ecr_client", + new=ECR(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.ecr.ecr_repositories_not_publicly_accessible.ecr_repositories_not_publicly_accessible_fixer import ( + fixer, + ) + + assert not fixer("repository_name_non_existing", AWS_REGION_EU_WEST_1) diff --git a/tests/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/firehose_stream_encrypted_at_rest_test.py b/tests/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/firehose_stream_encrypted_at_rest_test.py index d2a1fa40a9..da00aa8900 100644 --- a/tests/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/firehose_stream_encrypted_at_rest_test.py +++ b/tests/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/firehose_stream_encrypted_at_rest_test.py @@ -17,12 +17,15 @@ class Test_firehose_stream_encrypted_at_rest: aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=aws_provider, - ), mock.patch( - "prowler.providers.aws.services.firehose.firehose_stream_encrypted_at_rest.firehose_stream_encrypted_at_rest.firehose_client", - new=Firehose(aws_provider), + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.firehose.firehose_stream_encrypted_at_rest.firehose_stream_encrypted_at_rest.firehose_client", + new=Firehose(aws_provider), + ), ): # Test Check from prowler.providers.aws.services.firehose.firehose_stream_encrypted_at_rest.firehose_stream_encrypted_at_rest import ( @@ -94,6 +97,65 @@ class Test_firehose_stream_encrypted_at_rest: == f"Firehose Stream {stream_name} does have at rest encryption enabled." ) + @mock_aws + def test_stream_kms_encryption_enabled_aws_managed_key(self): + # Generate S3 client + s3_client = client("s3", region_name=AWS_REGION_EU_WEST_1) + s3_client.create_bucket( + Bucket="test-bucket", + CreateBucketConfiguration={"LocationConstraint": AWS_REGION_EU_WEST_1}, + ) + + # Generate Firehose client + firehose_client = client("firehose", region_name=AWS_REGION_EU_WEST_1) + delivery_stream = firehose_client.create_delivery_stream( + DeliveryStreamName="test-delivery-stream", + DeliveryStreamType="DirectPut", + S3DestinationConfiguration={ + "RoleARN": "arn:aws:iam::012345678901:role/firehose-role", + "BucketARN": "arn:aws:s3:::test-bucket", + "Prefix": "", + "BufferingHints": {"IntervalInSeconds": 300, "SizeInMBs": 5}, + "CompressionFormat": "UNCOMPRESSED", + }, + Tags=[{"Key": "key", "Value": "value"}], + ) + arn = delivery_stream["DeliveryStreamARN"] + stream_name = arn.split("/")[-1] + + firehose_client.start_delivery_stream_encryption( + DeliveryStreamName=stream_name, + DeliveryStreamEncryptionConfigurationInput={ + "KeyType": "AWS_OWNED_CMK", + }, + ) + + from prowler.providers.aws.services.firehose.firehose_service import Firehose + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.firehose.firehose_stream_encrypted_at_rest.firehose_stream_encrypted_at_rest.firehose_client", + new=Firehose(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.firehose.firehose_stream_encrypted_at_rest.firehose_stream_encrypted_at_rest import ( + firehose_stream_encrypted_at_rest, + ) + + check = firehose_stream_encrypted_at_rest() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Firehose Stream {stream_name} does have at rest encryption enabled." + ) + @mock_aws def test_stream_kms_encryption_not_enabled(self): # Generate Firehose client diff --git a/tests/providers/aws/services/glacier/glacier_vaults_policy_public_access/glacier_vaults_policy_public_access_fixer_test.py b/tests/providers/aws/services/glacier/glacier_vaults_policy_public_access/glacier_vaults_policy_public_access_fixer_test.py new file mode 100644 index 0000000000..87c808d722 --- /dev/null +++ b/tests/providers/aws/services/glacier/glacier_vaults_policy_public_access/glacier_vaults_policy_public_access_fixer_test.py @@ -0,0 +1,81 @@ +from unittest import mock + +import botocore +from moto import mock_aws + +from tests.providers.aws.utils import AWS_REGION_EU_WEST_1, set_mocked_aws_provider + +mock_make_api_call = botocore.client.BaseClient._make_api_call + + +def mock_make_api_call_public_vault(self, operation_name, kwarg): + if operation_name == "DeleteVaultAccessPolicy": + return { + "ResponseMetadata": { + "HTTPStatusCode": 204, + "RequestId": "test-request-id", + } + } + return mock_make_api_call(self, operation_name, kwarg) + + +def mock_make_api_call_public_vault_error(self, operation_name, kwarg): + if operation_name == "DeleteVaultAccessPolicy": + raise botocore.exceptions.ClientError( + { + "Error": { + "Code": "VaultNotFound", + "Message": "VaultNotFound", + } + }, + operation_name, + ) + return mock_make_api_call(self, operation_name, kwarg) + + +class Test_glacier_vaults_policy_public_access_fixer: + @mock_aws + def test_glacier_vault_public(self): + with mock.patch( + "botocore.client.BaseClient._make_api_call", + new=mock_make_api_call_public_vault, + ): + from prowler.providers.aws.services.glacier.glacier_service import Glacier + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.glacier.glacier_vaults_policy_public_access.glacier_vaults_policy_public_access_fixer.glacier_client", + new=Glacier(aws_provider), + ): + from prowler.providers.aws.services.glacier.glacier_vaults_policy_public_access.glacier_vaults_policy_public_access_fixer import ( + fixer, + ) + + assert fixer(resource_id="test-vault", region=AWS_REGION_EU_WEST_1) + + @mock_aws + def test_glacier_vault_public_error(self): + with mock.patch( + "botocore.client.BaseClient._make_api_call", + new=mock_make_api_call_public_vault_error, + ): + from prowler.providers.aws.services.glacier.glacier_service import Glacier + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.glacier.glacier_vaults_policy_public_access.glacier_vaults_policy_public_access_fixer.glacier_client", + new=Glacier(aws_provider), + ): + from prowler.providers.aws.services.glacier.glacier_vaults_policy_public_access.glacier_vaults_policy_public_access_fixer import ( + fixer, + ) + + assert not fixer(resource_id="test-vault", region=AWS_REGION_EU_WEST_1) diff --git a/tests/providers/aws/services/glacier/glacier_vaults_policy_public_access/glacier_vaults_policy_public_access_test.py b/tests/providers/aws/services/glacier/glacier_vaults_policy_public_access/glacier_vaults_policy_public_access_test.py index 2d10ed7d97..d267e1bc66 100644 --- a/tests/providers/aws/services/glacier/glacier_vaults_policy_public_access/glacier_vaults_policy_public_access_test.py +++ b/tests/providers/aws/services/glacier/glacier_vaults_policy_public_access/glacier_vaults_policy_public_access_test.py @@ -11,6 +11,9 @@ class Test_glacier_vaults_policy_public_access: with mock.patch( "prowler.providers.aws.services.glacier.glacier_service.Glacier", new=glacier_client, + ), mock.patch( + "prowler.providers.aws.services.glacier.glacier_client.glacier_client", + new=glacier_client, ): # Test Check from prowler.providers.aws.services.glacier.glacier_vaults_policy_public_access.glacier_vaults_policy_public_access import ( @@ -37,6 +40,9 @@ class Test_glacier_vaults_policy_public_access: with mock.patch( "prowler.providers.aws.services.glacier.glacier_service.Glacier", new=glacier_client, + ), mock.patch( + "prowler.providers.aws.services.glacier.glacier_client.glacier_client", + new=glacier_client, ): # Test Check from prowler.providers.aws.services.glacier.glacier_vaults_policy_public_access.glacier_vaults_policy_public_access import ( @@ -89,6 +95,9 @@ class Test_glacier_vaults_policy_public_access: with mock.patch( "prowler.providers.aws.services.glacier.glacier_service.Glacier", new=glacier_client, + ), mock.patch( + "prowler.providers.aws.services.glacier.glacier_client.glacier_client", + new=glacier_client, ): # Test Check from prowler.providers.aws.services.glacier.glacier_vaults_policy_public_access.glacier_vaults_policy_public_access import ( @@ -141,6 +150,9 @@ class Test_glacier_vaults_policy_public_access: with mock.patch( "prowler.providers.aws.services.glacier.glacier_service.Glacier", new=glacier_client, + ), mock.patch( + "prowler.providers.aws.services.glacier.glacier_client.glacier_client", + new=glacier_client, ): # Test Check from prowler.providers.aws.services.glacier.glacier_vaults_policy_public_access.glacier_vaults_policy_public_access import ( @@ -193,6 +205,9 @@ class Test_glacier_vaults_policy_public_access: with mock.patch( "prowler.providers.aws.services.glacier.glacier_service.Glacier", new=glacier_client, + ), mock.patch( + "prowler.providers.aws.services.glacier.glacier_client.glacier_client", + new=glacier_client, ): # Test Check from prowler.providers.aws.services.glacier.glacier_vaults_policy_public_access.glacier_vaults_policy_public_access import ( @@ -249,6 +264,9 @@ class Test_glacier_vaults_policy_public_access: with mock.patch( "prowler.providers.aws.services.glacier.glacier_service.Glacier", new=glacier_client, + ), mock.patch( + "prowler.providers.aws.services.glacier.glacier_client.glacier_client", + new=glacier_client, ): # Test Check from prowler.providers.aws.services.glacier.glacier_vaults_policy_public_access.glacier_vaults_policy_public_access import ( diff --git a/tests/providers/aws/services/iam/iam_rotate_access_key_90_days/iam_rotate_access_key_90_days_test.py b/tests/providers/aws/services/iam/iam_rotate_access_key_90_days/iam_rotate_access_key_90_days_test.py index 57c697062f..8205cd583c 100644 --- a/tests/providers/aws/services/iam/iam_rotate_access_key_90_days/iam_rotate_access_key_90_days_test.py +++ b/tests/providers/aws/services/iam/iam_rotate_access_key_90_days/iam_rotate_access_key_90_days_test.py @@ -21,13 +21,16 @@ class Test_iam_rotate_access_key_90_days_test: aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=aws_provider, - ), mock.patch( - "prowler.providers.aws.services.iam.iam_rotate_access_key_90_days.iam_rotate_access_key_90_days.iam_client", - new=IAM(aws_provider), - ) as service_client: + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.iam.iam_rotate_access_key_90_days.iam_rotate_access_key_90_days.iam_client", + new=IAM(aws_provider), + ) as service_client, + ): from prowler.providers.aws.services.iam.iam_rotate_access_key_90_days.iam_rotate_access_key_90_days import ( iam_rotate_access_key_90_days, ) @@ -62,13 +65,16 @@ class Test_iam_rotate_access_key_90_days_test: aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=aws_provider, - ), mock.patch( - "prowler.providers.aws.services.iam.iam_rotate_access_key_90_days.iam_rotate_access_key_90_days.iam_client", - new=IAM(aws_provider), - ) as service_client: + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.iam.iam_rotate_access_key_90_days.iam_rotate_access_key_90_days.iam_client", + new=IAM(aws_provider), + ) as service_client, + ): from prowler.providers.aws.services.iam.iam_rotate_access_key_90_days.iam_rotate_access_key_90_days import ( iam_rotate_access_key_90_days, ) @@ -86,7 +92,7 @@ class Test_iam_rotate_access_key_90_days_test: result[0].status_extended == f"User {user} has not rotated access key 1 in over 90 days (100 days)." ) - assert result[0].resource_id == user + assert result[0].resource_id == f"{user}-access-key-1" assert result[0].resource_arn == arn assert result[0].region == AWS_REGION_US_EAST_1 assert result[0].resource_tags == [{"Key": "test-tag", "Value": "test"}] @@ -106,13 +112,16 @@ class Test_iam_rotate_access_key_90_days_test: aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=aws_provider, - ), mock.patch( - "prowler.providers.aws.services.iam.iam_rotate_access_key_90_days.iam_rotate_access_key_90_days.iam_client", - new=IAM(aws_provider), - ) as service_client: + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.iam.iam_rotate_access_key_90_days.iam_rotate_access_key_90_days.iam_client", + new=IAM(aws_provider), + ) as service_client, + ): from prowler.providers.aws.services.iam.iam_rotate_access_key_90_days.iam_rotate_access_key_90_days import ( iam_rotate_access_key_90_days, ) @@ -130,7 +139,7 @@ class Test_iam_rotate_access_key_90_days_test: result[0].status_extended == f"User {user} has not rotated access key 2 in over 90 days (100 days)." ) - assert result[0].resource_id == user + assert result[0].resource_id == f"{user}-access-key-2" assert result[0].resource_arn == arn assert result[0].region == AWS_REGION_US_EAST_1 assert result[0].resource_tags == [{"Key": "test-tag", "Value": "test"}] @@ -150,13 +159,16 @@ class Test_iam_rotate_access_key_90_days_test: aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=aws_provider, - ), mock.patch( - "prowler.providers.aws.services.iam.iam_rotate_access_key_90_days.iam_rotate_access_key_90_days.iam_client", - new=IAM(aws_provider), - ) as service_client: + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.iam.iam_rotate_access_key_90_days.iam_rotate_access_key_90_days.iam_client", + new=IAM(aws_provider), + ) as service_client, + ): from prowler.providers.aws.services.iam.iam_rotate_access_key_90_days.iam_rotate_access_key_90_days import ( iam_rotate_access_key_90_days, ) @@ -179,7 +191,7 @@ class Test_iam_rotate_access_key_90_days_test: result[0].status_extended == f"User {user} has not rotated access key 1 in over 90 days (100 days)." ) - assert result[0].resource_id == user + assert result[0].resource_id == f"{user}-access-key-1" assert result[0].resource_arn == arn assert result[0].region == AWS_REGION_US_EAST_1 assert result[0].resource_tags == [{"Key": "test-tag", "Value": "test"}] @@ -188,7 +200,7 @@ class Test_iam_rotate_access_key_90_days_test: result[1].status_extended == f"User {user} has not rotated access key 2 in over 90 days (100 days)." ) - assert result[1].resource_id == user + assert result[1].resource_id == f"{user}-access-key-2" assert result[1].resource_arn == arn assert result[1].region == AWS_REGION_US_EAST_1 assert result[1].resource_tags == [{"Key": "test-tag", "Value": "test"}] diff --git a/tests/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible_fixer_test.py b/tests/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible_fixer_test.py new file mode 100644 index 0000000000..01bb37f03d --- /dev/null +++ b/tests/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible_fixer_test.py @@ -0,0 +1,135 @@ +from json import dumps +from unittest import mock + +from boto3 import client +from moto import mock_aws + +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_WEST_2, + set_mocked_aws_provider, +) + +domain_name = "test-domain" + +policy_data_restricted = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"AWS": [f"{AWS_ACCOUNT_NUMBER}"]}, + "Action": ["es:*"], + "Resource": f"arn:aws:es:us-west-2:{AWS_ACCOUNT_NUMBER}:domain/{domain_name}/*", + } + ], +} + +policy_data_not_restricted = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"AWS": ["*"]}, + "Action": ["es:*"], + "Resource": f"arn:aws:es:us-west-2:{AWS_ACCOUNT_NUMBER}:domain/{domain_name}/*", + } + ], +} + +policy_data_not_restricted_principal = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": "*", + "Action": ["es:*"], + "Resource": f"arn:aws:es:us-west-2:{AWS_ACCOUNT_NUMBER}:domain/{domain_name}/*", + } + ], +} + + +class Test_opensearch_service_domains_not_publicly_accessible_fixer: + @mock_aws + def test_policy_data_restricted_error(self): + opensearch_client = client("opensearch", region_name=AWS_REGION_US_WEST_2) + opensearch_client.create_domain(DomainName=domain_name)["DomainStatus"]["ARN"] + opensearch_client.update_domain_config( + DomainName=domain_name, + AccessPolicies=str(policy_data_restricted), + ) + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_WEST_2]) + + from prowler.providers.aws.services.opensearch.opensearch_service import ( + OpenSearchService, + ) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.opensearch.opensearch_service_domains_not_publicly_accessible.opensearch_service_domains_not_publicly_accessible_fixer.opensearch_client", + new=OpenSearchService(aws_provider), + ): + from prowler.providers.aws.services.opensearch.opensearch_service_domains_not_publicly_accessible.opensearch_service_domains_not_publicly_accessible_fixer import ( + fixer, + ) + + assert not fixer("domain_name_non_existing", AWS_REGION_US_WEST_2) + + @mock_aws + def test_policy_data_not_restricted_with_principal_AWS(self): + opensearch_client = client("opensearch", region_name=AWS_REGION_US_WEST_2) + opensearch_client.create_domain(DomainName=domain_name)["DomainStatus"]["ARN"] + opensearch_client.update_domain_config( + DomainName=domain_name, + AccessPolicies=dumps(policy_data_not_restricted), + ) + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_WEST_2]) + + from prowler.providers.aws.services.opensearch.opensearch_service import ( + OpenSearchService, + ) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.opensearch.opensearch_service_domains_not_publicly_accessible.opensearch_service_domains_not_publicly_accessible_fixer.opensearch_client", + new=OpenSearchService(aws_provider), + ): + from prowler.providers.aws.services.opensearch.opensearch_service_domains_not_publicly_accessible.opensearch_service_domains_not_publicly_accessible_fixer import ( + fixer, + ) + + assert fixer(domain_name, AWS_REGION_US_WEST_2) + + @mock_aws + def test_policy_data_not_restricted_with_principal_no_AWS(self): + opensearch_client = client("opensearch", region_name=AWS_REGION_US_WEST_2) + opensearch_client.create_domain(DomainName=domain_name)["DomainStatus"]["ARN"] + opensearch_client.update_domain_config( + DomainName=domain_name, + AccessPolicies=dumps(policy_data_not_restricted_principal), + ) + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_WEST_2]) + + from prowler.providers.aws.services.opensearch.opensearch_service import ( + OpenSearchService, + ) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.opensearch.opensearch_service_domains_not_publicly_accessible.opensearch_service_domains_not_publicly_accessible_fixer.opensearch_client", + new=OpenSearchService(aws_provider), + ): + from prowler.providers.aws.services.opensearch.opensearch_service_domains_not_publicly_accessible.opensearch_service_domains_not_publicly_accessible_fixer import ( + fixer, + ) + + assert fixer(domain_name, AWS_REGION_US_WEST_2) diff --git a/tests/providers/aws/services/rds/rds_cluster_non_default_port/rds_cluster_non_default_port_test.py b/tests/providers/aws/services/rds/rds_cluster_non_default_port/rds_cluster_non_default_port_test.py index 5315b16965..50c4ae1b5c 100644 --- a/tests/providers/aws/services/rds/rds_cluster_non_default_port/rds_cluster_non_default_port_test.py +++ b/tests/providers/aws/services/rds/rds_cluster_non_default_port/rds_cluster_non_default_port_test.py @@ -35,7 +35,7 @@ class Test_rds_cluster_non_default_port: assert len(result) == 0 @mock_aws - def test_rds_cluster_using_default_port(self): + def test_rds_cluster_aurora_postgres_using_default_port(self): conn = client("rds", region_name=AWS_REGION_US_EAST_1) conn.create_db_cluster( DBClusterIdentifier="db-cluster-1", @@ -82,10 +82,10 @@ class Test_rds_cluster_non_default_port: assert result[0].resource_tags == [{"Key": "test", "Value": "test"}] @mock_aws - def test_rds_cluster_using_non_default_port(self): + def test_rds_cluster_aurora_postgres_using_non_default_port(self): conn = client("rds", region_name=AWS_REGION_US_EAST_1) conn.create_db_cluster( - DBClusterIdentifier="db-cluster-1", + DBClusterIdentifier="db-cluster-2", Engine="aurora-postgresql", StorageEncrypted=True, DeletionProtection=True, @@ -118,13 +118,205 @@ class Test_rds_cluster_non_default_port: assert result[0].status == "PASS" assert ( result[0].status_extended - == "RDS Cluster db-cluster-1 is not using the default port 5433 for aurora-postgresql." + == "RDS Cluster db-cluster-2 is not using the default port 5433 for aurora-postgresql." ) - assert result[0].resource_id == "db-cluster-1" + assert result[0].resource_id == "db-cluster-2" assert result[0].region == AWS_REGION_US_EAST_1 assert ( result[0].resource_arn - == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:cluster:db-cluster-1" + == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:cluster:db-cluster-2" + ) + assert result[0].resource_tags == [ + {"Key": "env", "Value": "production"} + ] + + @mock_aws + def test_rds_cluster_postgres_using_default_port(self): + conn = client("rds", region_name=AWS_REGION_US_EAST_1) + conn.create_db_cluster( + DBClusterIdentifier="db-cluster-3", + Engine="postgres", + StorageEncrypted=True, + DeletionProtection=True, + MasterUsername="cluster", + MasterUserPassword="password", + Port=5432, + Tags=[{"Key": "test", "Value": "test"}], + ) + + from prowler.providers.aws.services.rds.rds_service import RDS + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.rds.rds_cluster_non_default_port.rds_cluster_non_default_port.rds_client", + new=RDS(aws_provider), + ): + from prowler.providers.aws.services.rds.rds_cluster_non_default_port.rds_cluster_non_default_port import ( + rds_cluster_non_default_port, + ) + + check = rds_cluster_non_default_port() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "RDS Cluster db-cluster-3 is using the default port 5432 for postgres." + ) + assert result[0].resource_id == "db-cluster-3" + assert result[0].region == AWS_REGION_US_EAST_1 + assert ( + result[0].resource_arn + == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:cluster:db-cluster-3" + ) + assert result[0].resource_tags == [{"Key": "test", "Value": "test"}] + + @mock_aws + def test_rds_cluster_postgres_using_non_default_port(self): + conn = client("rds", region_name=AWS_REGION_US_EAST_1) + conn.create_db_cluster( + DBClusterIdentifier="db-cluster-4", + Engine="postgres", + StorageEncrypted=True, + DeletionProtection=True, + MasterUsername="cluster", + MasterUserPassword="password", + Port=5433, + Tags=[{"Key": "env", "Value": "production"}], + ) + + from prowler.providers.aws.services.rds.rds_service import RDS + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.rds.rds_cluster_non_default_port.rds_cluster_non_default_port.rds_client", + new=RDS(aws_provider), + ): + from prowler.providers.aws.services.rds.rds_cluster_non_default_port.rds_cluster_non_default_port import ( + rds_cluster_non_default_port, + ) + + check = rds_cluster_non_default_port() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "RDS Cluster db-cluster-4 is not using the default port 5433 for postgres." + ) + assert result[0].resource_id == "db-cluster-4" + assert result[0].region == AWS_REGION_US_EAST_1 + assert ( + result[0].resource_arn + == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:cluster:db-cluster-4" + ) + assert result[0].resource_tags == [ + {"Key": "env", "Value": "production"} + ] + + @mock_aws + def test_rds_cluster_aurora_mysql_default_port(self): + conn = client("rds", region_name=AWS_REGION_US_EAST_1) + conn.create_db_cluster( + DBClusterIdentifier="db-cluster-5", + Engine="aurora-mysql", + StorageEncrypted=True, + DeletionProtection=True, + MasterUsername="cluster", + MasterUserPassword="password", + Port=3306, + Tags=[{"Key": "env", "Value": "staging"}], + ) + + from prowler.providers.aws.services.rds.rds_service import RDS + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.rds.rds_cluster_non_default_port.rds_cluster_non_default_port.rds_client", + new=RDS(aws_provider), + ): + from prowler.providers.aws.services.rds.rds_cluster_non_default_port.rds_cluster_non_default_port import ( + rds_cluster_non_default_port, + ) + + check = rds_cluster_non_default_port() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "RDS Cluster db-cluster-5 is using the default port 3306 for aurora-mysql." + ) + assert result[0].resource_id == "db-cluster-5" + assert result[0].region == AWS_REGION_US_EAST_1 + assert ( + result[0].resource_arn + == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:cluster:db-cluster-5" + ) + assert result[0].resource_tags == [{"Key": "env", "Value": "staging"}] + + @mock_aws + def test_rds_cluster_aurora_mysql_non_default_port(self): + conn = client("rds", region_name=AWS_REGION_US_EAST_1) + conn.create_db_cluster( + DBClusterIdentifier="db-cluster-6", + Engine="aurora-mysql", + StorageEncrypted=True, + DeletionProtection=True, + MasterUsername="cluster", + MasterUserPassword="password", + Port=3307, + Tags=[{"Key": "env", "Value": "production"}], + ) + + from prowler.providers.aws.services.rds.rds_service import RDS + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.rds.rds_cluster_non_default_port.rds_cluster_non_default_port.rds_client", + new=RDS(aws_provider), + ): + from prowler.providers.aws.services.rds.rds_cluster_non_default_port.rds_cluster_non_default_port import ( + rds_cluster_non_default_port, + ) + + check = rds_cluster_non_default_port() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "RDS Cluster db-cluster-6 is not using the default port 3307 for aurora-mysql." + ) + assert result[0].resource_id == "db-cluster-6" + assert result[0].region == AWS_REGION_US_EAST_1 + assert ( + result[0].resource_arn + == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:cluster:db-cluster-6" ) assert result[0].resource_tags == [ {"Key": "env", "Value": "production"} @@ -134,7 +326,7 @@ class Test_rds_cluster_non_default_port: def test_rds_cluster_mysql_default_port(self): conn = client("rds", region_name=AWS_REGION_US_EAST_1) conn.create_db_cluster( - DBClusterIdentifier="db-cluster-1", + DBClusterIdentifier="db-cluster-7", Engine="mysql", StorageEncrypted=True, DeletionProtection=True, @@ -167,13 +359,13 @@ class Test_rds_cluster_non_default_port: assert result[0].status == "FAIL" assert ( result[0].status_extended - == "RDS Cluster db-cluster-1 is using the default port 3306 for mysql." + == "RDS Cluster db-cluster-7 is using the default port 3306 for mysql." ) - assert result[0].resource_id == "db-cluster-1" + assert result[0].resource_id == "db-cluster-7" assert result[0].region == AWS_REGION_US_EAST_1 assert ( result[0].resource_arn - == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:cluster:db-cluster-1" + == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:cluster:db-cluster-7" ) assert result[0].resource_tags == [{"Key": "env", "Value": "staging"}] @@ -181,7 +373,7 @@ class Test_rds_cluster_non_default_port: def test_rds_cluster_mysql_non_default_port(self): conn = client("rds", region_name=AWS_REGION_US_EAST_1) conn.create_db_cluster( - DBClusterIdentifier="db-cluster-1", + DBClusterIdentifier="db-cluster-8", Engine="mysql", StorageEncrypted=True, DeletionProtection=True, @@ -214,13 +406,13 @@ class Test_rds_cluster_non_default_port: assert result[0].status == "PASS" assert ( result[0].status_extended - == "RDS Cluster db-cluster-1 is not using the default port 3307 for mysql." + == "RDS Cluster db-cluster-8 is not using the default port 3307 for mysql." ) - assert result[0].resource_id == "db-cluster-1" + assert result[0].resource_id == "db-cluster-8" assert result[0].region == AWS_REGION_US_EAST_1 assert ( result[0].resource_arn - == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:cluster:db-cluster-1" + == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:cluster:db-cluster-8" ) assert result[0].resource_tags == [ {"Key": "env", "Value": "production"} diff --git a/tests/providers/aws/services/rds/rds_instance_non_default_port/rds_instance_non_default_port_test.py b/tests/providers/aws/services/rds/rds_instance_non_default_port/rds_instance_non_default_port_test.py index 97775dc847..fb2a356eef 100644 --- a/tests/providers/aws/services/rds/rds_instance_non_default_port/rds_instance_non_default_port_test.py +++ b/tests/providers/aws/services/rds/rds_instance_non_default_port/rds_instance_non_default_port_test.py @@ -35,11 +35,115 @@ class Test_rds_instance_non_default_port: assert len(result) == 0 @mock_aws - def test_rds_instance_using_default_port(self): + def test_rds_instance_aurora_postgres_using_default_port(self): conn = client("rds", region_name=AWS_REGION_US_EAST_1) conn.create_db_instance( DBInstanceIdentifier="db-master-1", AllocatedStorage=10, + Engine="aurora-postgresql", + DBName="staging-postgres", + DBInstanceClass="db.m1.small", + StorageEncrypted=True, + DeletionProtection=True, + PubliclyAccessible=True, + AutoMinorVersionUpgrade=True, + BackupRetentionPeriod=10, + Port=5432, + Tags=[{"Key": "test", "Value": "test"}], + ) + + from prowler.providers.aws.services.rds.rds_service import RDS + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.rds.rds_instance_non_default_port.rds_instance_non_default_port.rds_client", + new=RDS(aws_provider), + ): + from prowler.providers.aws.services.rds.rds_instance_non_default_port.rds_instance_non_default_port import ( + rds_instance_non_default_port, + ) + + check = rds_instance_non_default_port() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "RDS Instance db-master-1 is using the default port 5432 for aurora-postgresql." + ) + assert result[0].resource_id == "db-master-1" + assert result[0].region == AWS_REGION_US_EAST_1 + assert ( + result[0].resource_arn + == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:db-master-1" + ) + assert result[0].resource_tags == [{"Key": "test", "Value": "test"}] + + @mock_aws + def test_rds_instance_aurora_postgres_using_non_default_port(self): + conn = client("rds", region_name=AWS_REGION_US_EAST_1) + conn.create_db_instance( + DBInstanceIdentifier="db-master-2", + AllocatedStorage=10, + Engine="aurora-postgresql", + DBName="production-postgres", + DBInstanceClass="db.m1.small", + StorageEncrypted=True, + DeletionProtection=True, + PubliclyAccessible=True, + AutoMinorVersionUpgrade=True, + BackupRetentionPeriod=10, + Port=5433, + Tags=[{"Key": "env", "Value": "production"}], + ) + + from prowler.providers.aws.services.rds.rds_service import RDS + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.rds.rds_instance_non_default_port.rds_instance_non_default_port.rds_client", + new=RDS(aws_provider), + ): + from prowler.providers.aws.services.rds.rds_instance_non_default_port.rds_instance_non_default_port import ( + rds_instance_non_default_port, + ) + + check = rds_instance_non_default_port() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "RDS Instance db-master-2 is not using the default port 5433 for aurora-postgresql." + ) + assert result[0].resource_id == "db-master-2" + assert result[0].region == AWS_REGION_US_EAST_1 + assert ( + result[0].resource_arn + == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:db-master-2" + ) + assert result[0].resource_tags == [ + {"Key": "env", "Value": "production"} + ] + + @mock_aws + def test_rds_instance_postgres_using_default_port(self): + conn = client("rds", region_name=AWS_REGION_US_EAST_1) + conn.create_db_instance( + DBInstanceIdentifier="db-master-3", + AllocatedStorage=10, Engine="postgres", DBName="staging-postgres", DBInstanceClass="db.m1.small", @@ -75,21 +179,21 @@ class Test_rds_instance_non_default_port: assert result[0].status == "FAIL" assert ( result[0].status_extended - == "RDS Instance db-master-1 is using the default port 5432 for postgres." + == "RDS Instance db-master-3 is using the default port 5432 for postgres." ) - assert result[0].resource_id == "db-master-1" + assert result[0].resource_id == "db-master-3" assert result[0].region == AWS_REGION_US_EAST_1 assert ( result[0].resource_arn - == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:db-master-1" + == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:db-master-3" ) assert result[0].resource_tags == [{"Key": "test", "Value": "test"}] @mock_aws - def test_rds_instance_using_non_default_port(self): + def test_rds_instance_postgres_using_non_default_port(self): conn = client("rds", region_name=AWS_REGION_US_EAST_1) conn.create_db_instance( - DBInstanceIdentifier="db-master-2", + DBInstanceIdentifier="db-master-4", AllocatedStorage=10, Engine="postgres", DBName="production-postgres", @@ -126,13 +230,221 @@ class Test_rds_instance_non_default_port: assert result[0].status == "PASS" assert ( result[0].status_extended - == "RDS Instance db-master-2 is not using the default port 5433 for postgres." + == "RDS Instance db-master-4 is not using the default port 5433 for postgres." ) - assert result[0].resource_id == "db-master-2" + assert result[0].resource_id == "db-master-4" assert result[0].region == AWS_REGION_US_EAST_1 assert ( result[0].resource_arn - == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:db-master-2" + == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:db-master-4" + ) + assert result[0].resource_tags == [ + {"Key": "env", "Value": "production"} + ] + + @mock_aws + def test_rds_instance_mysql_default_port(self): + conn = client("rds", region_name=AWS_REGION_US_EAST_1) + conn.create_db_instance( + DBInstanceIdentifier="db-master-5", + AllocatedStorage=10, + Engine="mysql", + DBName="staging-mariadb", + DBInstanceClass="db.m1.small", + StorageEncrypted=True, + DeletionProtection=True, + PubliclyAccessible=True, + AutoMinorVersionUpgrade=True, + BackupRetentionPeriod=10, + Port=3306, + Tags=[{"Key": "env", "Value": "staging"}], + ) + + from prowler.providers.aws.services.rds.rds_service import RDS + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.rds.rds_instance_non_default_port.rds_instance_non_default_port.rds_client", + new=RDS(aws_provider), + ): + from prowler.providers.aws.services.rds.rds_instance_non_default_port.rds_instance_non_default_port import ( + rds_instance_non_default_port, + ) + + check = rds_instance_non_default_port() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "RDS Instance db-master-5 is using the default port 3306 for mysql." + ) + assert result[0].resource_id == "db-master-5" + assert result[0].region == AWS_REGION_US_EAST_1 + assert ( + result[0].resource_arn + == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:db-master-5" + ) + assert result[0].resource_tags == [{"Key": "env", "Value": "staging"}] + + @mock_aws + def test_rds_instance_mysql_non_default_port(self): + conn = client("rds", region_name=AWS_REGION_US_EAST_1) + conn.create_db_instance( + DBInstanceIdentifier="db-master-6", + AllocatedStorage=10, + Engine="mysql", + DBName="production-mariadb", + DBInstanceClass="db.m1.small", + StorageEncrypted=True, + DeletionProtection=True, + PubliclyAccessible=True, + AutoMinorVersionUpgrade=True, + BackupRetentionPeriod=10, + Port=3307, + Tags=[{"Key": "env", "Value": "production"}], + ) + + from prowler.providers.aws.services.rds.rds_service import RDS + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.rds.rds_instance_non_default_port.rds_instance_non_default_port.rds_client", + new=RDS(aws_provider), + ): + from prowler.providers.aws.services.rds.rds_instance_non_default_port.rds_instance_non_default_port import ( + rds_instance_non_default_port, + ) + + check = rds_instance_non_default_port() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "RDS Instance db-master-6 is not using the default port 3307 for mysql." + ) + assert result[0].resource_id == "db-master-6" + assert result[0].region == AWS_REGION_US_EAST_1 + assert ( + result[0].resource_arn + == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:db-master-6" + ) + assert result[0].resource_tags == [ + {"Key": "env", "Value": "production"} + ] + + @mock_aws + def test_rds_instance_aurora_mysql_default_port(self): + conn = client("rds", region_name=AWS_REGION_US_EAST_1) + conn.create_db_instance( + DBInstanceIdentifier="db-master-7", + AllocatedStorage=10, + Engine="aurora-mysql", + DBName="staging-mariadb", + DBInstanceClass="db.m1.small", + StorageEncrypted=True, + DeletionProtection=True, + PubliclyAccessible=True, + AutoMinorVersionUpgrade=True, + BackupRetentionPeriod=10, + Port=3306, + Tags=[{"Key": "env", "Value": "staging"}], + ) + + from prowler.providers.aws.services.rds.rds_service import RDS + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.rds.rds_instance_non_default_port.rds_instance_non_default_port.rds_client", + new=RDS(aws_provider), + ): + from prowler.providers.aws.services.rds.rds_instance_non_default_port.rds_instance_non_default_port import ( + rds_instance_non_default_port, + ) + + check = rds_instance_non_default_port() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "RDS Instance db-master-7 is using the default port 3306 for aurora-mysql." + ) + assert result[0].resource_id == "db-master-7" + assert result[0].region == AWS_REGION_US_EAST_1 + assert ( + result[0].resource_arn + == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:db-master-7" + ) + assert result[0].resource_tags == [{"Key": "env", "Value": "staging"}] + + @mock_aws + def test_rds_instance_aurora_mysql_non_default_port(self): + conn = client("rds", region_name=AWS_REGION_US_EAST_1) + conn.create_db_instance( + DBInstanceIdentifier="db-master-8", + AllocatedStorage=10, + Engine="aurora-mysql", + DBName="production-mariadb", + DBInstanceClass="db.m1.small", + StorageEncrypted=True, + DeletionProtection=True, + PubliclyAccessible=True, + AutoMinorVersionUpgrade=True, + BackupRetentionPeriod=10, + Port=3307, + Tags=[{"Key": "env", "Value": "production"}], + ) + + from prowler.providers.aws.services.rds.rds_service import RDS + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.rds.rds_instance_non_default_port.rds_instance_non_default_port.rds_client", + new=RDS(aws_provider), + ): + from prowler.providers.aws.services.rds.rds_instance_non_default_port.rds_instance_non_default_port import ( + rds_instance_non_default_port, + ) + + check = rds_instance_non_default_port() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "RDS Instance db-master-8 is not using the default port 3307 for aurora-mysql." + ) + assert result[0].resource_id == "db-master-8" + assert result[0].region == AWS_REGION_US_EAST_1 + assert ( + result[0].resource_arn + == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:db-master-8" ) assert result[0].resource_tags == [ {"Key": "env", "Value": "production"} @@ -142,7 +454,7 @@ class Test_rds_instance_non_default_port: def test_rds_instance_mariadb_default_port(self): conn = client("rds", region_name=AWS_REGION_US_EAST_1) conn.create_db_instance( - DBInstanceIdentifier="db-master-3", + DBInstanceIdentifier="db-master-9", AllocatedStorage=10, Engine="mariadb", DBName="staging-mariadb", @@ -179,13 +491,13 @@ class Test_rds_instance_non_default_port: assert result[0].status == "FAIL" assert ( result[0].status_extended - == "RDS Instance db-master-3 is using the default port 3306 for mariadb." + == "RDS Instance db-master-9 is using the default port 3306 for mariadb." ) - assert result[0].resource_id == "db-master-3" + assert result[0].resource_id == "db-master-9" assert result[0].region == AWS_REGION_US_EAST_1 assert ( result[0].resource_arn - == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:db-master-3" + == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:db-master-9" ) assert result[0].resource_tags == [{"Key": "env", "Value": "staging"}] @@ -193,7 +505,7 @@ class Test_rds_instance_non_default_port: def test_rds_instance_mariadb_non_default_port(self): conn = client("rds", region_name=AWS_REGION_US_EAST_1) conn.create_db_instance( - DBInstanceIdentifier="db-master-4", + DBInstanceIdentifier="db-master-10", AllocatedStorage=10, Engine="mariadb", DBName="production-mariadb", @@ -230,13 +542,13 @@ class Test_rds_instance_non_default_port: assert result[0].status == "PASS" assert ( result[0].status_extended - == "RDS Instance db-master-4 is not using the default port 3307 for mariadb." + == "RDS Instance db-master-10 is not using the default port 3307 for mariadb." ) - assert result[0].resource_id == "db-master-4" + assert result[0].resource_id == "db-master-10" assert result[0].region == AWS_REGION_US_EAST_1 assert ( result[0].resource_arn - == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:db-master-4" + == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:db-master-10" ) assert result[0].resource_tags == [ {"Key": "env", "Value": "production"} diff --git a/tests/providers/aws/services/s3/s3_bucket_public_access/s3_bucket_public_access_fixer_test.py b/tests/providers/aws/services/s3/s3_bucket_public_access/s3_bucket_public_access_fixer_test.py new file mode 100644 index 0000000000..39099ac39b --- /dev/null +++ b/tests/providers/aws/services/s3/s3_bucket_public_access/s3_bucket_public_access_fixer_test.py @@ -0,0 +1,169 @@ +from unittest import mock + +from boto3 import client +from moto import mock_aws + +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + + +class Test_s3_bucket_public_access_fixer: + @mock_aws + def test_no_buckets(self): + from prowler.providers.aws.services.s3.s3_service import S3 + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.s3.s3_bucket_public_access.s3_bucket_public_access_fixer.s3_client", + new=S3(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.s3.s3_bucket_public_access.s3_bucket_public_access_fixer import ( + fixer, + ) + + assert not fixer("bucket_test_us", AWS_REGION_US_EAST_1) + + @mock_aws + def test_bucket_public_ACL(self): + s3_client = client("s3", region_name=AWS_REGION_US_EAST_1) + bucket_name_us = "bucket_test_us" + s3_client.create_bucket(Bucket=bucket_name_us) + bucket_owner = s3_client.get_bucket_acl(Bucket=bucket_name_us)["Owner"] + s3_client.put_public_access_block( + Bucket=bucket_name_us, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_bucket_acl( + Bucket=bucket_name_us, + AccessControlPolicy={ + "Grants": [ + { + "Grantee": { + "URI": "http://acs.amazonaws.com/groups/global/AllUsers", + "Type": "Group", + }, + "Permission": "READ", + }, + ], + "Owner": bucket_owner, + }, + ) + from prowler.providers.aws.services.s3.s3_service import S3 + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.s3.s3_bucket_public_access.s3_bucket_public_access_fixer.s3_client", + new=S3(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.s3.s3_bucket_public_access.s3_bucket_public_access_fixer import ( + fixer, + ) + + assert fixer(bucket_name_us, AWS_REGION_US_EAST_1) + + @mock_aws + def test_bucket_public_policy(self): + s3_client = client("s3", region_name=AWS_REGION_US_EAST_1) + bucket_name_us = "bucket_test_us" + s3_client.create_bucket(Bucket=bucket_name_us) + # Generate S3Control Client + s3control_client = client("s3control", region_name=AWS_REGION_US_EAST_1) + s3control_client.put_public_access_block( + AccountId=AWS_ACCOUNT_NUMBER, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_public_access_block( + Bucket=bucket_name_us, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + public_write_policy = '{"Version": "2012-10-17","Id": "PutObjPolicy","Statement": [{"Sid": "PublicWritePolicy","Effect": "Allow","Principal": "*","Action": "s3:PutObject","Resource": "arn:aws:s3:::bucket_test_us/*"}]}' + s3_client.put_bucket_policy( + Bucket=bucket_name_us, + Policy=public_write_policy, + ) + from prowler.providers.aws.services.s3.s3_service import S3 + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.s3.s3_bucket_public_access.s3_bucket_public_access_fixer.s3_client", + new=S3(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.s3.s3_bucket_public_access.s3_bucket_public_access_fixer import ( + fixer, + ) + + assert fixer(bucket_name_us, AWS_REGION_US_EAST_1) + + @mock_aws + def test_bucket_public_due_to_policy_conditions_from_public_ip(self): + s3_client = client("s3", region_name=AWS_REGION_US_EAST_1) + bucket_name_us = "bucket_test_us" + s3_client.create_bucket(Bucket=bucket_name_us) + s3_client.put_public_access_block( + Bucket=bucket_name_us, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + public_write_policy = '{"Version": "2012-10-17","Id": "PutObjPolicy","Statement": [{"Sid": "PublicWritePolicy","Effect": "Allow","Principal": "*","Action": "s3:PutObject","Resource": "arn:aws:s3:::bucket_test_us/*","Condition": {"IpAddress": {"aws:SourceIp": "1.2.3.4"}}}]}' + s3_client.put_bucket_policy( + Bucket=bucket_name_us, + Policy=public_write_policy, + ) + from prowler.providers.aws.services.s3.s3_service import S3 + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.s3.s3_bucket_public_access.s3_bucket_public_access_fixer.s3_client", + new=S3(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.s3.s3_bucket_public_access.s3_bucket_public_access_fixer import ( + fixer, + ) + + assert fixer(bucket_name_us, AWS_REGION_US_EAST_1) diff --git a/tests/providers/aws/services/s3/s3_bucket_public_list_acl/s3_bucket_public_list_acl_fixer_test.py b/tests/providers/aws/services/s3/s3_bucket_public_list_acl/s3_bucket_public_list_acl_fixer_test.py new file mode 100644 index 0000000000..6f7a8f2414 --- /dev/null +++ b/tests/providers/aws/services/s3/s3_bucket_public_list_acl/s3_bucket_public_list_acl_fixer_test.py @@ -0,0 +1,432 @@ +from unittest import mock + +from boto3 import client +from moto import mock_aws + +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + + +class Test_s3_bucket_public_list_acl_fixer: + @mock_aws + def test_bucket_public_list_ACL_AllUsers_READ(self): + s3_client = client("s3", region_name=AWS_REGION_US_EAST_1) + bucket_name_us = "bucket_test_us" + s3_client.create_bucket(Bucket=bucket_name_us) + bucket_owner = s3_client.get_bucket_acl(Bucket=bucket_name_us)["Owner"] + # Generate S3Control Client + s3control_client = client("s3control", region_name=AWS_REGION_US_EAST_1) + s3control_client.put_public_access_block( + AccountId=AWS_ACCOUNT_NUMBER, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_public_access_block( + Bucket=bucket_name_us, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_bucket_acl( + Bucket=bucket_name_us, + AccessControlPolicy={ + "Grants": [ + { + "Grantee": { + "URI": "http://acs.amazonaws.com/groups/global/AllUsers", + "Type": "Group", + }, + "Permission": "READ", + }, + ], + "Owner": bucket_owner, + }, + ) + from prowler.providers.aws.services.s3.s3_service import S3 + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.s3.s3_bucket_public_list_acl.s3_bucket_public_list_acl_fixer.s3_client", + new=S3(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.s3.s3_bucket_public_list_acl.s3_bucket_public_list_acl_fixer import ( + fixer, + ) + + assert fixer(bucket_name_us, AWS_REGION_US_EAST_1) + + @mock_aws + def test_bucket_public_list_ACL_AllUsers_READ_ACP(self): + s3_client = client("s3", region_name=AWS_REGION_US_EAST_1) + bucket_name_us = "bucket_test_us" + s3_client.create_bucket(Bucket=bucket_name_us) + bucket_owner = s3_client.get_bucket_acl(Bucket=bucket_name_us)["Owner"] + # Generate S3Control Client + s3control_client = client("s3control", region_name=AWS_REGION_US_EAST_1) + s3control_client.put_public_access_block( + AccountId=AWS_ACCOUNT_NUMBER, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_public_access_block( + Bucket=bucket_name_us, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_bucket_acl( + Bucket=bucket_name_us, + AccessControlPolicy={ + "Grants": [ + { + "Grantee": { + "URI": "http://acs.amazonaws.com/groups/global/AllUsers", + "Type": "Group", + }, + "Permission": "READ_ACP", + }, + ], + "Owner": bucket_owner, + }, + ) + from prowler.providers.aws.services.s3.s3_service import S3 + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.s3.s3_bucket_public_list_acl.s3_bucket_public_list_acl_fixer.s3_client", + new=S3(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.s3.s3_bucket_public_list_acl.s3_bucket_public_list_acl_fixer import ( + fixer, + ) + + assert fixer(bucket_name_us, AWS_REGION_US_EAST_1) + + @mock_aws + def test_bucket_public_list_ACL_AllUsers_FULL_CONTROL(self): + s3_client = client("s3", region_name=AWS_REGION_US_EAST_1) + bucket_name_us = "bucket_test_us" + s3_client.create_bucket(Bucket=bucket_name_us) + bucket_owner = s3_client.get_bucket_acl(Bucket=bucket_name_us)["Owner"] + # Generate S3Control Client + s3control_client = client("s3control", region_name=AWS_REGION_US_EAST_1) + s3control_client.put_public_access_block( + AccountId=AWS_ACCOUNT_NUMBER, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_public_access_block( + Bucket=bucket_name_us, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_bucket_acl( + Bucket=bucket_name_us, + AccessControlPolicy={ + "Grants": [ + { + "Grantee": { + "URI": "http://acs.amazonaws.com/groups/global/AllUsers", + "Type": "Group", + }, + "Permission": "FULL_CONTROL", + }, + ], + "Owner": bucket_owner, + }, + ) + from prowler.providers.aws.services.s3.s3_service import S3 + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.s3.s3_bucket_public_list_acl.s3_bucket_public_list_acl_fixer.s3_client", + new=S3(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.s3.s3_bucket_public_list_acl.s3_bucket_public_list_acl_fixer import ( + fixer, + ) + + assert fixer(bucket_name_us, AWS_REGION_US_EAST_1) + + @mock_aws + def test_bucket_public_list_ACL_AuthenticatedUsers_READ(self): + s3_client = client("s3", region_name=AWS_REGION_US_EAST_1) + bucket_name_us = "bucket_test_us" + s3_client.create_bucket(Bucket=bucket_name_us) + bucket_owner = s3_client.get_bucket_acl(Bucket=bucket_name_us)["Owner"] + # Generate S3Control Client + s3control_client = client("s3control", region_name=AWS_REGION_US_EAST_1) + s3control_client.put_public_access_block( + AccountId=AWS_ACCOUNT_NUMBER, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_public_access_block( + Bucket=bucket_name_us, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_bucket_acl( + Bucket=bucket_name_us, + AccessControlPolicy={ + "Grants": [ + { + "Grantee": { + "URI": "http://acs.amazonaws.com/groups/global/AuthenticatedUsers", + "Type": "Group", + }, + "Permission": "READ", + }, + ], + "Owner": bucket_owner, + }, + ) + from prowler.providers.aws.services.s3.s3_service import S3 + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.s3.s3_bucket_public_list_acl.s3_bucket_public_list_acl_fixer.s3_client", + new=S3(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.s3.s3_bucket_public_list_acl.s3_bucket_public_list_acl_fixer import ( + fixer, + ) + + assert fixer(bucket_name_us, AWS_REGION_US_EAST_1) + + @mock_aws + def test_bucket_public_list_ACL_AuthenticatedUsers_READ_ACP(self): + s3_client = client("s3", region_name=AWS_REGION_US_EAST_1) + bucket_name_us = "bucket_test_us" + s3_client.create_bucket(Bucket=bucket_name_us) + bucket_owner = s3_client.get_bucket_acl(Bucket=bucket_name_us)["Owner"] + # Generate S3Control Client + s3control_client = client("s3control", region_name=AWS_REGION_US_EAST_1) + s3control_client.put_public_access_block( + AccountId=AWS_ACCOUNT_NUMBER, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_public_access_block( + Bucket=bucket_name_us, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_bucket_acl( + Bucket=bucket_name_us, + AccessControlPolicy={ + "Grants": [ + { + "Grantee": { + "URI": "http://acs.amazonaws.com/groups/global/AuthenticatedUsers", + "Type": "Group", + }, + "Permission": "READ_ACP", + }, + ], + "Owner": bucket_owner, + }, + ) + from prowler.providers.aws.services.s3.s3_service import S3 + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.s3.s3_bucket_public_list_acl.s3_bucket_public_list_acl_fixer.s3_client", + new=S3(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.s3.s3_bucket_public_list_acl.s3_bucket_public_list_acl_fixer import ( + fixer, + ) + + assert fixer(bucket_name_us, AWS_REGION_US_EAST_1) + + @mock_aws + def test_bucket_public_list_ACL_AuthenticatedUsers_FULL_CONTROL(self): + s3_client = client("s3", region_name=AWS_REGION_US_EAST_1) + bucket_name_us = "bucket_test_us" + s3_client.create_bucket(Bucket=bucket_name_us) + bucket_owner = s3_client.get_bucket_acl(Bucket=bucket_name_us)["Owner"] + # Generate S3Control Client + s3control_client = client("s3control", region_name=AWS_REGION_US_EAST_1) + s3control_client.put_public_access_block( + AccountId=AWS_ACCOUNT_NUMBER, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_public_access_block( + Bucket=bucket_name_us, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_bucket_acl( + Bucket=bucket_name_us, + AccessControlPolicy={ + "Grants": [ + { + "Grantee": { + "URI": "http://acs.amazonaws.com/groups/global/AuthenticatedUsers", + "Type": "Group", + }, + "Permission": "FULL_CONTROL", + }, + ], + "Owner": bucket_owner, + }, + ) + from prowler.providers.aws.services.s3.s3_service import S3 + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.s3.s3_bucket_public_list_acl.s3_bucket_public_list_acl_fixer.s3_client", + new=S3(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.s3.s3_bucket_public_list_acl.s3_bucket_public_list_acl_fixer import ( + fixer, + ) + + assert fixer(bucket_name_us, AWS_REGION_US_EAST_1) + + @mock_aws + def test_bucket_public_list_ACL_AllUsers_READ_error(self): + s3_client = client("s3", region_name=AWS_REGION_US_EAST_1) + bucket_name_us = "bucket_test_us" + s3_client.create_bucket(Bucket=bucket_name_us) + bucket_owner = s3_client.get_bucket_acl(Bucket=bucket_name_us)["Owner"] + # Generate S3Control Client + s3control_client = client("s3control", region_name=AWS_REGION_US_EAST_1) + s3control_client.put_public_access_block( + AccountId=AWS_ACCOUNT_NUMBER, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_public_access_block( + Bucket=bucket_name_us, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_bucket_acl( + Bucket=bucket_name_us, + AccessControlPolicy={ + "Grants": [ + { + "Grantee": { + "URI": "http://acs.amazonaws.com/groups/global/AllUsers", + "Type": "Group", + }, + "Permission": "READ", + }, + ], + "Owner": bucket_owner, + }, + ) + from prowler.providers.aws.services.s3.s3_service import S3 + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.s3.s3_bucket_public_list_acl.s3_bucket_public_list_acl_fixer.s3_client", + new=S3(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.s3.s3_bucket_public_list_acl.s3_bucket_public_list_acl_fixer import ( + fixer, + ) + + assert not fixer("bucket_name_us_non_existing", AWS_REGION_US_EAST_1) diff --git a/tests/providers/aws/services/s3/s3_bucket_public_write_acl/s3_bucket_public_write_acl_fixer_test.py b/tests/providers/aws/services/s3/s3_bucket_public_write_acl/s3_bucket_public_write_acl_fixer_test.py new file mode 100644 index 0000000000..768ad12f79 --- /dev/null +++ b/tests/providers/aws/services/s3/s3_bucket_public_write_acl/s3_bucket_public_write_acl_fixer_test.py @@ -0,0 +1,432 @@ +from unittest import mock + +from boto3 import client +from moto import mock_aws + +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + + +class Test_s3_bucket_public_write_acl_fixer: + @mock_aws + def test_bucket_public_write_ACL_AllUsers_WRITE(self): + s3_client = client("s3", region_name=AWS_REGION_US_EAST_1) + bucket_name_us = "bucket_test_us" + s3_client.create_bucket(Bucket=bucket_name_us) + bucket_owner = s3_client.get_bucket_acl(Bucket=bucket_name_us)["Owner"] + # Generate S3Control Client + s3control_client = client("s3control", region_name=AWS_REGION_US_EAST_1) + s3control_client.put_public_access_block( + AccountId=AWS_ACCOUNT_NUMBER, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_public_access_block( + Bucket=bucket_name_us, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_bucket_acl( + Bucket=bucket_name_us, + AccessControlPolicy={ + "Grants": [ + { + "Grantee": { + "URI": "http://acs.amazonaws.com/groups/global/AllUsers", + "Type": "Group", + }, + "Permission": "WRITE", + }, + ], + "Owner": bucket_owner, + }, + ) + from prowler.providers.aws.services.s3.s3_service import S3 + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.s3.s3_bucket_public_write_acl.s3_bucket_public_write_acl_fixer.s3_client", + new=S3(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.s3.s3_bucket_public_write_acl.s3_bucket_public_write_acl_fixer import ( + fixer, + ) + + assert fixer(bucket_name_us, AWS_REGION_US_EAST_1) + + @mock_aws + def test_bucket_public_write_ACL_AllUsers_WRITE_ACP(self): + s3_client = client("s3", region_name=AWS_REGION_US_EAST_1) + bucket_name_us = "bucket_test_us" + s3_client.create_bucket(Bucket=bucket_name_us) + bucket_owner = s3_client.get_bucket_acl(Bucket=bucket_name_us)["Owner"] + # Generate S3Control Client + s3control_client = client("s3control", region_name=AWS_REGION_US_EAST_1) + s3control_client.put_public_access_block( + AccountId=AWS_ACCOUNT_NUMBER, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_public_access_block( + Bucket=bucket_name_us, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_bucket_acl( + Bucket=bucket_name_us, + AccessControlPolicy={ + "Grants": [ + { + "Grantee": { + "URI": "http://acs.amazonaws.com/groups/global/AllUsers", + "Type": "Group", + }, + "Permission": "WRITE_ACP", + }, + ], + "Owner": bucket_owner, + }, + ) + from prowler.providers.aws.services.s3.s3_service import S3 + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.s3.s3_bucket_public_write_acl.s3_bucket_public_write_acl_fixer.s3_client", + new=S3(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.s3.s3_bucket_public_write_acl.s3_bucket_public_write_acl_fixer import ( + fixer, + ) + + assert fixer(bucket_name_us, AWS_REGION_US_EAST_1) + + @mock_aws + def test_bucket_public_write_ACL_AllUsers_FULL_CONTROL(self): + s3_client = client("s3", region_name=AWS_REGION_US_EAST_1) + bucket_name_us = "bucket_test_us" + s3_client.create_bucket(Bucket=bucket_name_us) + bucket_owner = s3_client.get_bucket_acl(Bucket=bucket_name_us)["Owner"] + # Generate S3Control Client + s3control_client = client("s3control", region_name=AWS_REGION_US_EAST_1) + s3control_client.put_public_access_block( + AccountId=AWS_ACCOUNT_NUMBER, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_public_access_block( + Bucket=bucket_name_us, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_bucket_acl( + Bucket=bucket_name_us, + AccessControlPolicy={ + "Grants": [ + { + "Grantee": { + "URI": "http://acs.amazonaws.com/groups/global/AllUsers", + "Type": "Group", + }, + "Permission": "FULL_CONTROL", + }, + ], + "Owner": bucket_owner, + }, + ) + from prowler.providers.aws.services.s3.s3_service import S3 + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.s3.s3_bucket_public_write_acl.s3_bucket_public_write_acl_fixer.s3_client", + new=S3(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.s3.s3_bucket_public_write_acl.s3_bucket_public_write_acl_fixer import ( + fixer, + ) + + assert fixer(bucket_name_us, AWS_REGION_US_EAST_1) + + @mock_aws + def test_bucket_public_write_ACL_AuthenticatedUsers_WRITE(self): + s3_client = client("s3", region_name=AWS_REGION_US_EAST_1) + bucket_name_us = "bucket_test_us" + s3_client.create_bucket(Bucket=bucket_name_us) + bucket_owner = s3_client.get_bucket_acl(Bucket=bucket_name_us)["Owner"] + # Generate S3Control Client + s3control_client = client("s3control", region_name=AWS_REGION_US_EAST_1) + s3control_client.put_public_access_block( + AccountId=AWS_ACCOUNT_NUMBER, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_public_access_block( + Bucket=bucket_name_us, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_bucket_acl( + Bucket=bucket_name_us, + AccessControlPolicy={ + "Grants": [ + { + "Grantee": { + "URI": "http://acs.amazonaws.com/groups/global/AuthenticatedUsers", + "Type": "Group", + }, + "Permission": "WRITE", + }, + ], + "Owner": bucket_owner, + }, + ) + from prowler.providers.aws.services.s3.s3_service import S3 + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.s3.s3_bucket_public_write_acl.s3_bucket_public_write_acl_fixer.s3_client", + new=S3(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.s3.s3_bucket_public_write_acl.s3_bucket_public_write_acl_fixer import ( + fixer, + ) + + assert fixer(bucket_name_us, AWS_REGION_US_EAST_1) + + @mock_aws + def test_bucket_public_write_ACL_AuthenticatedUsers_WRITE_ACP(self): + s3_client = client("s3", region_name=AWS_REGION_US_EAST_1) + bucket_name_us = "bucket_test_us" + s3_client.create_bucket(Bucket=bucket_name_us) + bucket_owner = s3_client.get_bucket_acl(Bucket=bucket_name_us)["Owner"] + # Generate S3Control Client + s3control_client = client("s3control", region_name=AWS_REGION_US_EAST_1) + s3control_client.put_public_access_block( + AccountId=AWS_ACCOUNT_NUMBER, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_public_access_block( + Bucket=bucket_name_us, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_bucket_acl( + Bucket=bucket_name_us, + AccessControlPolicy={ + "Grants": [ + { + "Grantee": { + "URI": "http://acs.amazonaws.com/groups/global/AuthenticatedUsers", + "Type": "Group", + }, + "Permission": "WRITE_ACP", + }, + ], + "Owner": bucket_owner, + }, + ) + from prowler.providers.aws.services.s3.s3_service import S3 + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.s3.s3_bucket_public_write_acl.s3_bucket_public_write_acl_fixer.s3_client", + new=S3(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.s3.s3_bucket_public_write_acl.s3_bucket_public_write_acl_fixer import ( + fixer, + ) + + assert fixer(bucket_name_us, AWS_REGION_US_EAST_1) + + @mock_aws + def test_bucket_public_write_ACL_AuthenticatedUsers_FULL_CONTROL(self): + s3_client = client("s3", region_name=AWS_REGION_US_EAST_1) + bucket_name_us = "bucket_test_us" + s3_client.create_bucket(Bucket=bucket_name_us) + bucket_owner = s3_client.get_bucket_acl(Bucket=bucket_name_us)["Owner"] + # Generate S3Control Client + s3control_client = client("s3control", region_name=AWS_REGION_US_EAST_1) + s3control_client.put_public_access_block( + AccountId=AWS_ACCOUNT_NUMBER, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_public_access_block( + Bucket=bucket_name_us, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_bucket_acl( + Bucket=bucket_name_us, + AccessControlPolicy={ + "Grants": [ + { + "Grantee": { + "URI": "http://acs.amazonaws.com/groups/global/AuthenticatedUsers", + "Type": "Group", + }, + "Permission": "FULL_CONTROL", + }, + ], + "Owner": bucket_owner, + }, + ) + from prowler.providers.aws.services.s3.s3_service import S3 + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.s3.s3_bucket_public_write_acl.s3_bucket_public_write_acl_fixer.s3_client", + new=S3(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.s3.s3_bucket_public_write_acl.s3_bucket_public_write_acl_fixer import ( + fixer, + ) + + assert fixer(bucket_name_us, AWS_REGION_US_EAST_1) + + @mock_aws + def test_bucket_public_write_ACL_AllUsers_WRITE_error(self): + s3_client = client("s3", region_name=AWS_REGION_US_EAST_1) + bucket_name_us = "bucket_test_us" + s3_client.create_bucket(Bucket=bucket_name_us) + bucket_owner = s3_client.get_bucket_acl(Bucket=bucket_name_us)["Owner"] + # Generate S3Control Client + s3control_client = client("s3control", region_name=AWS_REGION_US_EAST_1) + s3control_client.put_public_access_block( + AccountId=AWS_ACCOUNT_NUMBER, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_public_access_block( + Bucket=bucket_name_us, + PublicAccessBlockConfiguration={ + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + }, + ) + s3_client.put_bucket_acl( + Bucket=bucket_name_us, + AccessControlPolicy={ + "Grants": [ + { + "Grantee": { + "URI": "http://acs.amazonaws.com/groups/global/AllUsers", + "Type": "Group", + }, + "Permission": "WRITE", + }, + ], + "Owner": bucket_owner, + }, + ) + from prowler.providers.aws.services.s3.s3_service import S3 + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.s3.s3_bucket_public_write_acl.s3_bucket_public_write_acl_fixer.s3_client", + new=S3(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.s3.s3_bucket_public_write_acl.s3_bucket_public_write_acl_fixer import ( + fixer, + ) + + assert not fixer("bucket_name_us_non_existing", AWS_REGION_US_EAST_1) diff --git a/tests/providers/aws/services/sqs/sqs_queues_not_publicly_accessible/sqs_queues_not_publicly_accessible_fixer_test.py b/tests/providers/aws/services/sqs/sqs_queues_not_publicly_accessible/sqs_queues_not_publicly_accessible_fixer_test.py new file mode 100644 index 0000000000..8603573e3a --- /dev/null +++ b/tests/providers/aws/services/sqs/sqs_queues_not_publicly_accessible/sqs_queues_not_publicly_accessible_fixer_test.py @@ -0,0 +1,106 @@ +from json import dumps +from unittest import mock + +from boto3 import client +from moto import mock_aws + +from tests.providers.aws.utils import AWS_REGION_EU_WEST_1, set_mocked_aws_provider + + +class Test_sqs_queues_not_publicly_accessible_fixer: + @mock_aws + def test_queue_public(self): + sqs_client = client("sqs", region_name=AWS_REGION_EU_WEST_1) + + queue_url = sqs_client.create_queue(QueueName="test-queue")["QueueUrl"] + + sqs_client.set_queue_attributes( + QueueUrl=queue_url, + Attributes={ + "Policy": dumps({"Statement": [{"Effect": "Allow", "Principal": "*"}]}) + }, + ) + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + + from prowler.providers.aws.services.sqs.sqs_service import SQS + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.sqs.sqs_queues_not_publicly_accessible.sqs_queues_not_publicly_accessible_fixer.sqs_client", + new=SQS(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.sqs.sqs_queues_not_publicly_accessible.sqs_queues_not_publicly_accessible_fixer import ( + fixer, + ) + + assert fixer(queue_url, AWS_REGION_EU_WEST_1) + + @mock_aws + def test_queue_public_with_aws(self): + sqs_client = client("sqs", region_name=AWS_REGION_EU_WEST_1) + + queue_url = sqs_client.create_queue(QueueName="test-queue")["QueueUrl"] + + sqs_client.set_queue_attributes( + QueueUrl=queue_url, + Attributes={ + "Policy": dumps( + {"Statement": [{"Effect": "Allow", "Principal": {"AWS": "*"}}]} + ) + }, + ) + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + + from prowler.providers.aws.services.sqs.sqs_service import SQS + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.sqs.sqs_queues_not_publicly_accessible.sqs_queues_not_publicly_accessible_fixer.sqs_client", + new=SQS(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.sqs.sqs_queues_not_publicly_accessible.sqs_queues_not_publicly_accessible_fixer import ( + fixer, + ) + + assert fixer(queue_url, AWS_REGION_EU_WEST_1) + + @mock_aws + def test_queue_public_error(self): + sqs_client = client("sqs", region_name=AWS_REGION_EU_WEST_1) + + queue_url = sqs_client.create_queue(QueueName="test-queue")["QueueUrl"] + + sqs_client.set_queue_attributes( + QueueUrl=queue_url, + Attributes={ + "Policy": dumps( + {"Statement": [{"Effect": "Allow", "Principal": {"AWS": "*"}}]} + ) + }, + ) + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + + from prowler.providers.aws.services.sqs.sqs_service import SQS + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.sqs.sqs_queues_not_publicly_accessible.sqs_queues_not_publicly_accessible_fixer.sqs_client", + new=SQS(aws_provider), + ): + # Test Fixer + from prowler.providers.aws.services.sqs.sqs_queues_not_publicly_accessible.sqs_queues_not_publicly_accessible_fixer import ( + fixer, + ) + + assert not fixer("queue_url_non_existing", AWS_REGION_EU_WEST_1) diff --git a/tests/providers/aws/services/sqs/sqs_queues_not_publicly_accessible/sqs_queues_not_publicly_accessible_test.py b/tests/providers/aws/services/sqs/sqs_queues_not_publicly_accessible/sqs_queues_not_publicly_accessible_test.py index 6f0c5197a5..b1d6ab3a78 100644 --- a/tests/providers/aws/services/sqs/sqs_queues_not_publicly_accessible/sqs_queues_not_publicly_accessible_test.py +++ b/tests/providers/aws/services/sqs/sqs_queues_not_publicly_accessible/sqs_queues_not_publicly_accessible_test.py @@ -110,7 +110,10 @@ class Test_sqs_queues_not_publicly_accessible: sqs_client.queues = [] with mock.patch( "prowler.providers.aws.services.sqs.sqs_service.SQS", - sqs_client, + new=sqs_client, + ), mock.patch( + "prowler.providers.aws.services.sqs.sqs_client.sqs_client", + new=sqs_client, ): from prowler.providers.aws.services.sqs.sqs_queues_not_publicly_accessible.sqs_queues_not_publicly_accessible import ( sqs_queues_not_publicly_accessible, @@ -167,7 +170,10 @@ class Test_sqs_queues_not_publicly_accessible: ) with mock.patch( "prowler.providers.aws.services.sqs.sqs_service.SQS", - sqs_client, + new=sqs_client, + ), mock.patch( + "prowler.providers.aws.services.sqs.sqs_client.sqs_client", + new=sqs_client, ): from prowler.providers.aws.services.sqs.sqs_queues_not_publicly_accessible.sqs_queues_not_publicly_accessible import ( sqs_queues_not_publicly_accessible, @@ -201,7 +207,10 @@ class Test_sqs_queues_not_publicly_accessible: ) with mock.patch( "prowler.providers.aws.services.sqs.sqs_service.SQS", - sqs_client, + new=sqs_client, + ), mock.patch( + "prowler.providers.aws.services.sqs.sqs_client.sqs_client", + new=sqs_client, ): from prowler.providers.aws.services.sqs.sqs_queues_not_publicly_accessible.sqs_queues_not_publicly_accessible import ( sqs_queues_not_publicly_accessible, @@ -235,7 +244,10 @@ class Test_sqs_queues_not_publicly_accessible: ) with mock.patch( "prowler.providers.aws.services.sqs.sqs_service.SQS", - sqs_client, + new=sqs_client, + ), mock.patch( + "prowler.providers.aws.services.sqs.sqs_client.sqs_client", + new=sqs_client, ): from prowler.providers.aws.services.sqs.sqs_queues_not_publicly_accessible.sqs_queues_not_publicly_accessible import ( sqs_queues_not_publicly_accessible, @@ -269,7 +281,10 @@ class Test_sqs_queues_not_publicly_accessible: ) with mock.patch( "prowler.providers.aws.services.sqs.sqs_service.SQS", - sqs_client, + new=sqs_client, + ), mock.patch( + "prowler.providers.aws.services.sqs.sqs_client.sqs_client", + new=sqs_client, ): from prowler.providers.aws.services.sqs.sqs_queues_not_publicly_accessible.sqs_queues_not_publicly_accessible import ( sqs_queues_not_publicly_accessible, @@ -303,7 +318,10 @@ class Test_sqs_queues_not_publicly_accessible: ) with mock.patch( "prowler.providers.aws.services.sqs.sqs_service.SQS", - sqs_client, + new=sqs_client, + ), mock.patch( + "prowler.providers.aws.services.sqs.sqs_client.sqs_client", + new=sqs_client, ): from prowler.providers.aws.services.sqs.sqs_queues_not_publicly_accessible.sqs_queues_not_publicly_accessible import ( sqs_queues_not_publicly_accessible, diff --git a/tests/providers/aws/services/sqs/sqs_queues_server_side_encryption_enabled/sqs_queues_server_side_encryption_enabled_test.py b/tests/providers/aws/services/sqs/sqs_queues_server_side_encryption_enabled/sqs_queues_server_side_encryption_enabled_test.py index e24b28b475..29d7f5ed05 100644 --- a/tests/providers/aws/services/sqs/sqs_queues_server_side_encryption_enabled/sqs_queues_server_side_encryption_enabled_test.py +++ b/tests/providers/aws/services/sqs/sqs_queues_server_side_encryption_enabled/sqs_queues_server_side_encryption_enabled_test.py @@ -18,7 +18,10 @@ class Test_sqs_queues_server_side_encryption_enabled: sqs_client.queues = [] with mock.patch( "prowler.providers.aws.services.sqs.sqs_service.SQS", - sqs_client, + new=sqs_client, + ), mock.patch( + "prowler.providers.aws.services.sqs.sqs_client.sqs_client", + new=sqs_client, ): from prowler.providers.aws.services.sqs.sqs_queues_server_side_encryption_enabled.sqs_queues_server_side_encryption_enabled import ( sqs_queues_server_side_encryption_enabled, @@ -42,7 +45,10 @@ class Test_sqs_queues_server_side_encryption_enabled: ) with mock.patch( "prowler.providers.aws.services.sqs.sqs_service.SQS", - sqs_client, + new=sqs_client, + ), mock.patch( + "prowler.providers.aws.services.sqs.sqs_client.sqs_client", + new=sqs_client, ): from prowler.providers.aws.services.sqs.sqs_queues_server_side_encryption_enabled.sqs_queues_server_side_encryption_enabled import ( sqs_queues_server_side_encryption_enabled, @@ -72,7 +78,10 @@ class Test_sqs_queues_server_side_encryption_enabled: ) with mock.patch( "prowler.providers.aws.services.sqs.sqs_service.SQS", - sqs_client, + new=sqs_client, + ), mock.patch( + "prowler.providers.aws.services.sqs.sqs_client.sqs_client", + new=sqs_client, ): from prowler.providers.aws.services.sqs.sqs_queues_server_side_encryption_enabled.sqs_queues_server_side_encryption_enabled import ( sqs_queues_server_side_encryption_enabled, diff --git a/tests/providers/aws/services/wafv2/wafv2_webacl_with_rules/wafv2_webacl_with_rules_test.py b/tests/providers/aws/services/wafv2/wafv2_webacl_with_rules/wafv2_webacl_with_rules_test.py index 6d476d0f9b..19cde553a7 100644 --- a/tests/providers/aws/services/wafv2/wafv2_webacl_with_rules/wafv2_webacl_with_rules_test.py +++ b/tests/providers/aws/services/wafv2/wafv2_webacl_with_rules/wafv2_webacl_with_rules_test.py @@ -1,10 +1,61 @@ from unittest import mock +from unittest.mock import patch +import botocore from boto3 import client from moto import mock_aws from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider +# Original botocore _make_api_call function +orig = botocore.client.BaseClient._make_api_call + +FM_RG_NAME = "test-firewall-managed-rule-group" +FM_RG_ARN = "arn:aws:wafv2:us-east-1:123456789012:regional/webacl/test-firewall-managed-rule-group" + + +# Mocked botocore _make_api_call function +def mock_make_api_call(self, operation_name, kwarg): + if operation_name == "ListWebACLs": + return { + "WebACLs": [ + { + "Name": FM_RG_NAME, + "Id": FM_RG_NAME, + "ARN": FM_RG_ARN, + } + ] + } + elif operation_name == "GetWebACL": + return { + "WebACL": { + "PostProcessFirewallManagerRuleGroups": [ + { + "Name": FM_RG_NAME, + "VisibilityConfig": { + "SampledRequestsEnabled": True, + "CloudWatchMetricsEnabled": True, + "MetricName": "web-acl-test-metric", + }, + } + ] + } + } + elif operation_name == "ListResourcesForWebACL": + return { + "ResourceArns": [ + FM_RG_ARN, + ] + } + elif operation_name == "ListTagsForResource": + return { + "TagInfoForResource": { + "ResourceARN": FM_RG_ARN, + "TagList": [{"Key": "Name", "Value": FM_RG_NAME}], + } + } + return orig(self, operation_name, kwarg) + class Test_wafv2_webacl_with_rules: @mock_aws @@ -13,12 +64,15 @@ class Test_wafv2_webacl_with_rules: aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=aws_provider, - ), mock.patch( - "prowler.providers.aws.services.wafv2.wafv2_webacl_with_rules.wafv2_webacl_with_rules.wafv2_client", - new=WAFv2(aws_provider), + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.wafv2.wafv2_webacl_with_rules.wafv2_webacl_with_rules.wafv2_client", + new=WAFv2(aws_provider), + ), ): from prowler.providers.aws.services.wafv2.wafv2_webacl_with_rules.wafv2_webacl_with_rules import ( wafv2_webacl_with_rules, @@ -69,12 +123,15 @@ class Test_wafv2_webacl_with_rules: aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=aws_provider, - ), mock.patch( - "prowler.providers.aws.services.wafv2.wafv2_webacl_with_rules.wafv2_webacl_with_rules.wafv2_client", - new=WAFv2(aws_provider), + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.wafv2.wafv2_webacl_with_rules.wafv2_webacl_with_rules.wafv2_client", + new=WAFv2(aws_provider), + ), ): from prowler.providers.aws.services.wafv2.wafv2_webacl_with_rules.wafv2_webacl_with_rules import ( wafv2_webacl_with_rules, @@ -137,12 +194,15 @@ class Test_wafv2_webacl_with_rules: aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=aws_provider, - ), mock.patch( - "prowler.providers.aws.services.wafv2.wafv2_webacl_with_rules.wafv2_webacl_with_rules.wafv2_client", - new=WAFv2(aws_provider), + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.wafv2.wafv2_webacl_with_rules.wafv2_webacl_with_rules.wafv2_client", + new=WAFv2(aws_provider), + ), ): from prowler.providers.aws.services.wafv2.wafv2_webacl_with_rules.wafv2_webacl_with_rules import ( wafv2_webacl_with_rules, @@ -161,6 +221,43 @@ class Test_wafv2_webacl_with_rules: assert result[0].region == AWS_REGION_US_EAST_1 assert result[0].resource_tags == [{"Key": "Name", "Value": waf_name}] + @patch( + "botocore.client.BaseClient._make_api_call", + new=mock_make_api_call, + ) + @mock_aws + def test_wafv2_web_acl_with_firewall_manager_managed_rule_group(self): + from prowler.providers.aws.services.wafv2.wafv2_service import WAFv2 + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.wafv2.wafv2_webacl_with_rules.wafv2_webacl_with_rules.wafv2_client", + new=WAFv2(aws_provider), + ), + ): + from prowler.providers.aws.services.wafv2.wafv2_webacl_with_rules.wafv2_webacl_with_rules import ( + wafv2_webacl_with_rules, + ) + + check = wafv2_webacl_with_rules() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"AWS WAFv2 Web ACL {FM_RG_NAME} does have rules or rule groups attached." + ) + assert result[0].resource_id == FM_RG_NAME + assert result[0].resource_arn == FM_RG_ARN + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [{"Key": "Name", "Value": FM_RG_NAME}] + @mock_aws def test_wafv2_web_acl_without_rule_or_rule_group(self): wafv2_client = client("wafv2", region_name=AWS_REGION_US_EAST_1) @@ -184,12 +281,15 @@ class Test_wafv2_webacl_with_rules: aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=aws_provider, - ), mock.patch( - "prowler.providers.aws.services.wafv2.wafv2_webacl_with_rules.wafv2_webacl_with_rules.wafv2_client", - new=WAFv2(aws_provider), + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.wafv2.wafv2_webacl_with_rules.wafv2_webacl_with_rules.wafv2_client", + new=WAFv2(aws_provider), + ), ): from prowler.providers.aws.services.wafv2.wafv2_webacl_with_rules.wafv2_webacl_with_rules import ( wafv2_webacl_with_rules, diff --git a/tests/providers/azure/azure_provider_test.py b/tests/providers/azure/azure_provider_test.py index ea798ad057..853903fcc2 100644 --- a/tests/providers/azure/azure_provider_test.py +++ b/tests/providers/azure/azure_provider_test.py @@ -81,6 +81,7 @@ class TestAzureProvider: "php_latest_version": "8.2", "python_latest_version": "3.12", "java_latest_version": "17", + "recommended_minimal_tls_versions": ["1.2", "1.3"], } def test_azure_provider_not_auth_methods(self): diff --git a/tests/providers/azure/services/app/app_minimum_tls_version_12/app_minimum_tls_version_12_test.py b/tests/providers/azure/services/app/app_minimum_tls_version_12/app_minimum_tls_version_12_test.py index da0271cbc0..12a149015e 100644 --- a/tests/providers/azure/services/app/app_minimum_tls_version_12/app_minimum_tls_version_12_test.py +++ b/tests/providers/azure/services/app/app_minimum_tls_version_12/app_minimum_tls_version_12_test.py @@ -171,3 +171,45 @@ class Test_app_minimum_tls_version_12: assert result[0].resource_name == "app_id-1" assert result[0].subscription == AZURE_SUBSCRIPTION_ID assert result[0].location == "West Europe" + + def test_app_min_tls_version_13(self): + resource_id = f"/subscriptions/{uuid4()}" + app_client = mock.MagicMock + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), mock.patch( + "prowler.providers.azure.services.app.app_minimum_tls_version_12.app_minimum_tls_version_12.app_client", + new=app_client, + ): + from prowler.providers.azure.services.app.app_minimum_tls_version_12.app_minimum_tls_version_12 import ( + app_minimum_tls_version_12, + ) + from prowler.providers.azure.services.app.app_service import WebApp + + app_client.apps = { + AZURE_SUBSCRIPTION_ID: { + "app_id-1": WebApp( + resource_id=resource_id, + auth_enabled=False, + configurations=mock.MagicMock(min_tls_version="1.3"), + client_cert_mode="Ignore", + https_only=False, + identity=None, + location="West Europe", + ) + } + } + check = app_minimum_tls_version_12() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Minimum TLS version is set to 1.3 for app 'app_id-1' in subscription '{AZURE_SUBSCRIPTION_ID}'." + ) + assert result[0].resource_id == resource_id + assert result[0].resource_name == "app_id-1" + assert result[0].subscription == AZURE_SUBSCRIPTION_ID + assert result[0].location == "West Europe" diff --git a/tests/providers/azure/services/sqlserver/sqlserver_recommended_minimal_tls_version/sqlserver_recommended_minimal_tls_version_test.py b/tests/providers/azure/services/sqlserver/sqlserver_recommended_minimal_tls_version/sqlserver_recommended_minimal_tls_version_test.py new file mode 100644 index 0000000000..2b80067468 --- /dev/null +++ b/tests/providers/azure/services/sqlserver/sqlserver_recommended_minimal_tls_version/sqlserver_recommended_minimal_tls_version_test.py @@ -0,0 +1,215 @@ +from unittest import mock +from uuid import uuid4 + +from azure.mgmt.sql.models import TransparentDataEncryption + +from prowler.providers.azure.services.sqlserver.sqlserver_service import ( + Database, + Server, +) +from tests.providers.azure.azure_fixtures import ( + AZURE_SUBSCRIPTION_ID, + set_mocked_azure_provider, +) + + +class Test_sqlserver_recommended_minimal_tls_version: + def test_no_sql_servers(self): + sqlserver_client = mock.MagicMock + sqlserver_client.sql_servers = {} + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), mock.patch( + "prowler.providers.azure.services.sqlserver.sqlserver_recommended_minimal_tls_version.sqlserver_recommended_minimal_tls_version.sqlserver_client", + new=sqlserver_client, + ): + from prowler.providers.azure.services.sqlserver.sqlserver_recommended_minimal_tls_version.sqlserver_recommended_minimal_tls_version import ( + sqlserver_recommended_minimal_tls_version, + ) + + sqlserver_client.audit_config = { + "recommended_minimal_tls_versions": ["1.2", "1.3"] + } + + check = sqlserver_recommended_minimal_tls_version() + result = check.execute() + assert len(result) == 0 + + def test_sql_servers_deprecated_minimal_tls_version(self): + sqlserver_client = mock.MagicMock + sql_server_name = "SQL Server Name" + sql_server_id = str(uuid4()) + database_name = "Database Name" + database_id = str(uuid4()) + database = Database( + id=database_id, + name=database_name, + type="type", + location="location", + managed_by="managed_by", + tde_encryption=TransparentDataEncryption(status="Disabled"), + ) + sqlserver_client.sql_servers = { + AZURE_SUBSCRIPTION_ID: [ + Server( + id=sql_server_id, + name=sql_server_name, + public_network_access="", + minimal_tls_version="1.0", + administrators=None, + auditing_policies=None, + firewall_rules=None, + databases=[database], + encryption_protector=None, + location="location", + ) + ] + } + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), mock.patch( + "prowler.providers.azure.services.sqlserver.sqlserver_recommended_minimal_tls_version.sqlserver_recommended_minimal_tls_version.sqlserver_client", + new=sqlserver_client, + ): + from prowler.providers.azure.services.sqlserver.sqlserver_recommended_minimal_tls_version.sqlserver_recommended_minimal_tls_version import ( + sqlserver_recommended_minimal_tls_version, + ) + + sqlserver_client.audit_config = { + "recommended_minimal_tls_versions": ["1.2", "1.3"] + } + + check = sqlserver_recommended_minimal_tls_version() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"SQL Server {sql_server_name} from subscription {AZURE_SUBSCRIPTION_ID} is using TLS version 1.0 as minimal accepted which is not recommended. Please use one of the recommended versions: {', '.join(sqlserver_client.audit_config['recommended_minimal_tls_versions'])}." + ) + assert result[0].subscription == AZURE_SUBSCRIPTION_ID + assert result[0].resource_name == sql_server_name + assert result[0].resource_id == sql_server_id + assert result[0].location == "location" + + def test_sql_servers_no_minimal_tls_version(self): + sqlserver_client = mock.MagicMock + sql_server_name = "SQL Server Name" + sql_server_id = str(uuid4()) + database_name = "Database Name" + database_id = str(uuid4()) + database = Database( + id=database_id, + name=database_name, + type="type", + location="location", + managed_by="managed_by", + tde_encryption=TransparentDataEncryption(status="Disabled"), + ) + sqlserver_client.sql_servers = { + AZURE_SUBSCRIPTION_ID: [ + Server( + id=sql_server_id, + name=sql_server_name, + public_network_access="", + minimal_tls_version="", + administrators=None, + auditing_policies=None, + firewall_rules=None, + databases=[database], + encryption_protector=None, + location="location", + ) + ] + } + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), mock.patch( + "prowler.providers.azure.services.sqlserver.sqlserver_recommended_minimal_tls_version.sqlserver_recommended_minimal_tls_version.sqlserver_client", + new=sqlserver_client, + ): + from prowler.providers.azure.services.sqlserver.sqlserver_recommended_minimal_tls_version.sqlserver_recommended_minimal_tls_version import ( + sqlserver_recommended_minimal_tls_version, + ) + + sqlserver_client.audit_config = { + "recommended_minimal_tls_versions": ["1.2", "1.3"] + } + + check = sqlserver_recommended_minimal_tls_version() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"SQL Server {sql_server_name} from subscription {AZURE_SUBSCRIPTION_ID} is using TLS version as minimal accepted which is not recommended. Please use one of the recommended versions: {', '.join(sqlserver_client.audit_config['recommended_minimal_tls_versions'])}." + ) + assert result[0].subscription == AZURE_SUBSCRIPTION_ID + assert result[0].resource_name == sql_server_name + assert result[0].resource_id == sql_server_id + assert result[0].location == "location" + + def test_sql_servers_minimal_tls_version(self): + sqlserver_client = mock.MagicMock + sql_server_name = "SQL Server Name" + sql_server_id = str(uuid4()) + database_name = "Database Name" + database_id = str(uuid4()) + database = Database( + id=database_id, + name=database_name, + type="type", + location="location", + managed_by="managed_by", + tde_encryption=TransparentDataEncryption(status="Enabled"), + ) + sqlserver_client.sql_servers = { + AZURE_SUBSCRIPTION_ID: [ + Server( + id=sql_server_id, + name=sql_server_name, + public_network_access="", + minimal_tls_version="1.2", + administrators=None, + auditing_policies=None, + firewall_rules=None, + databases=[database], + encryption_protector=None, + location="location", + ) + ] + } + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), mock.patch( + "prowler.providers.azure.services.sqlserver.sqlserver_recommended_minimal_tls_version.sqlserver_recommended_minimal_tls_version.sqlserver_client", + new=sqlserver_client, + ): + from prowler.providers.azure.services.sqlserver.sqlserver_recommended_minimal_tls_version.sqlserver_recommended_minimal_tls_version import ( + sqlserver_recommended_minimal_tls_version, + ) + + sqlserver_client.audit_config = { + "recommended_minimal_tls_versions": ["1.2", "1.3"] + } + + check = sqlserver_recommended_minimal_tls_version() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"SQL Server {sql_server_name} from subscription {AZURE_SUBSCRIPTION_ID} is using version 1.2 as minimal accepted which is recommended." + ) + assert result[0].subscription == AZURE_SUBSCRIPTION_ID + assert result[0].resource_name == sql_server_name + assert result[0].resource_id == sql_server_id + assert result[0].location == "location" diff --git a/tests/providers/gcp/gcp_provider_test.py b/tests/providers/gcp/gcp_provider_test.py index 9c74821935..9916a6902b 100644 --- a/tests/providers/gcp/gcp_provider_test.py +++ b/tests/providers/gcp/gcp_provider_test.py @@ -51,18 +51,23 @@ class TestGCPProvider: execute=MagicMock(return_value={"projects": projects}) ) - with patch( - "prowler.providers.gcp.gcp_provider.GcpProvider.setup_session", - return_value=(None, "test-project"), - ), patch( - "prowler.providers.gcp.gcp_provider.GcpProvider.get_projects", - return_value=projects, - ), patch( - "prowler.providers.gcp.gcp_provider.GcpProvider.update_projects_with_organizations", - return_value=None, - ), patch( - "prowler.providers.gcp.gcp_provider.discovery.build", - return_value=mocked_service, + with ( + patch( + "prowler.providers.gcp.gcp_provider.GcpProvider.setup_session", + return_value=(None, "test-project"), + ), + patch( + "prowler.providers.gcp.gcp_provider.GcpProvider.get_projects", + return_value=projects, + ), + patch( + "prowler.providers.gcp.gcp_provider.GcpProvider.update_projects_with_organizations", + return_value=None, + ), + patch( + "prowler.providers.gcp.gcp_provider.discovery.build", + return_value=mocked_service, + ), ): gcp_provider = GcpProvider( project_id, @@ -119,18 +124,23 @@ class TestGCPProvider: mocked_service.projects.list.return_value = MagicMock( execute=MagicMock(return_value={"projects": projects}) ) - with patch( - "prowler.providers.gcp.gcp_provider.GcpProvider.setup_session", - return_value=(None, None), - ), patch( - "prowler.providers.gcp.gcp_provider.GcpProvider.get_projects", - return_value=projects, - ), patch( - "prowler.providers.gcp.gcp_provider.GcpProvider.update_projects_with_organizations", - return_value=None, - ), patch( - "prowler.providers.gcp.gcp_provider.discovery.build", - return_value=mocked_service, + with ( + patch( + "prowler.providers.gcp.gcp_provider.GcpProvider.setup_session", + return_value=(None, None), + ), + patch( + "prowler.providers.gcp.gcp_provider.GcpProvider.get_projects", + return_value=projects, + ), + patch( + "prowler.providers.gcp.gcp_provider.GcpProvider.update_projects_with_organizations", + return_value=None, + ), + patch( + "prowler.providers.gcp.gcp_provider.discovery.build", + return_value=mocked_service, + ), ): gcp_provider = GcpProvider( arguments.organization_id, @@ -193,21 +203,27 @@ class TestGCPProvider: mocked_service.projects.list.return_value = MagicMock( execute=MagicMock(return_value={"projects": projects}) ) - with patch( - "prowler.providers.gcp.gcp_provider.GcpProvider.get_projects", - return_value=projects, - ), patch( - "prowler.providers.gcp.gcp_provider.GcpProvider.update_projects_with_organizations", - return_value=None, - ), patch( - "os.path.abspath", - return_value="test_credentials_file", - ), patch( - "prowler.providers.gcp.gcp_provider.default", - return_value=(mocked_credentials, MagicMock()), - ), patch( - "prowler.providers.gcp.gcp_provider.discovery.build", - return_value=mocked_service, + with ( + patch( + "prowler.providers.gcp.gcp_provider.GcpProvider.get_projects", + return_value=projects, + ), + patch( + "prowler.providers.gcp.gcp_provider.GcpProvider.update_projects_with_organizations", + return_value=None, + ), + patch( + "os.path.abspath", + return_value="test_credentials_file", + ), + patch( + "prowler.providers.gcp.gcp_provider.default", + return_value=(mocked_credentials, MagicMock()), + ), + patch( + "prowler.providers.gcp.gcp_provider.discovery.build", + return_value=mocked_service, + ), ): gcp_provider = GcpProvider( arguments.organization_id, @@ -257,21 +273,27 @@ class TestGCPProvider: mocked_service.projects.list.return_value = MagicMock( execute=MagicMock(return_value={"projects": projects}) ) - with patch( - "prowler.providers.gcp.gcp_provider.GcpProvider.get_projects", - return_value=projects, - ), patch( - "prowler.providers.gcp.gcp_provider.GcpProvider.update_projects_with_organizations", - return_value=None, - ), patch( - "os.path.abspath", - return_value="test_credentials_file", - ), patch( - "prowler.providers.gcp.gcp_provider.default", - return_value=(mocked_credentials, MagicMock()), - ), patch( - "prowler.providers.gcp.gcp_provider.discovery.build", - return_value=mocked_service, + with ( + patch( + "prowler.providers.gcp.gcp_provider.GcpProvider.get_projects", + return_value=projects, + ), + patch( + "prowler.providers.gcp.gcp_provider.GcpProvider.update_projects_with_organizations", + return_value=None, + ), + patch( + "os.path.abspath", + return_value="test_credentials_file", + ), + patch( + "prowler.providers.gcp.gcp_provider.default", + return_value=(mocked_credentials, MagicMock()), + ), + patch( + "prowler.providers.gcp.gcp_provider.discovery.build", + return_value=mocked_service, + ), ): gcp_provider = GcpProvider( arguments.organization_id, @@ -334,21 +356,27 @@ class TestGCPProvider: mocked_service.projects.list.return_value = MagicMock( execute=MagicMock(return_value={"projects": projects}) ) - with patch( - "prowler.providers.gcp.gcp_provider.GcpProvider.get_projects", - return_value=projects, - ), patch( - "prowler.providers.gcp.gcp_provider.GcpProvider.update_projects_with_organizations", - return_value=None, - ), patch( - "os.path.abspath", - return_value="test_credentials_file", - ), patch( - "prowler.providers.gcp.gcp_provider.default", - return_value=(mocked_credentials, MagicMock()), - ), patch( - "prowler.providers.gcp.gcp_provider.discovery.build", - return_value=mocked_service, + with ( + patch( + "prowler.providers.gcp.gcp_provider.GcpProvider.get_projects", + return_value=projects, + ), + patch( + "prowler.providers.gcp.gcp_provider.GcpProvider.update_projects_with_organizations", + return_value=None, + ), + patch( + "os.path.abspath", + return_value="test_credentials_file", + ), + patch( + "prowler.providers.gcp.gcp_provider.default", + return_value=(mocked_credentials, MagicMock()), + ), + patch( + "prowler.providers.gcp.gcp_provider.discovery.build", + return_value=mocked_service, + ), ): gcp_provider = GcpProvider( arguments.organization_id, @@ -401,21 +429,27 @@ class TestGCPProvider: mocked_service.projects.list.return_value = MagicMock( execute=MagicMock(return_value={"projects": projects}) ) - with patch( - "prowler.providers.gcp.gcp_provider.GcpProvider.get_projects", - return_value=projects, - ), patch( - "prowler.providers.gcp.gcp_provider.GcpProvider.update_projects_with_organizations", - return_value=None, - ), patch( - "os.path.abspath", - return_value="test_credentials_file", - ), patch( - "prowler.providers.gcp.gcp_provider.default", - return_value=(mocked_credentials, MagicMock()), - ), patch( - "prowler.providers.gcp.gcp_provider.discovery.build", - return_value=mocked_service, + with ( + patch( + "prowler.providers.gcp.gcp_provider.GcpProvider.get_projects", + return_value=projects, + ), + patch( + "prowler.providers.gcp.gcp_provider.GcpProvider.update_projects_with_organizations", + return_value=None, + ), + patch( + "os.path.abspath", + return_value="test_credentials_file", + ), + patch( + "prowler.providers.gcp.gcp_provider.default", + return_value=(mocked_credentials, MagicMock()), + ), + patch( + "prowler.providers.gcp.gcp_provider.discovery.build", + return_value=mocked_service, + ), ): with pytest.raises(Exception) as e: GcpProvider( @@ -433,6 +467,81 @@ class TestGCPProvider: ) assert e.type == GCPNoAccesibleProjectsError + def test_setup_session_with_inactive_default_project(self): + mocked_credentials = MagicMock() + + mocked_credentials.refresh.return_value = None + mocked_credentials._service_account_email = "test-service-account-email" + + arguments = Namespace() + arguments.project_id = ["default_project", "active_project"] + arguments.excluded_project_id = [] + arguments.organization_id = None + arguments.list_project_id = False + arguments.credentials_file = "test_credentials_file" + arguments.impersonate_service_account = "" + arguments.config_file = default_config_file_path + arguments.fixer_config = default_fixer_config_file_path + + projects = { + "default_project": GCPProject( + number="55555555", + id="default_project", + name="default_project", + labels={"test": "value"}, + lifecycle_state="DELETE_REQUESTED", + ), + "active_project": GCPProject( + number="12345678", + id="active_project", + name="active_project", + labels={"test": "value"}, + lifecycle_state="ACTIVE", + ), + } + + mocked_service = MagicMock() + + mocked_service.projects.list.return_value = MagicMock( + execute=MagicMock(return_value={"projects": projects}) + ) + with ( + patch( + "prowler.providers.gcp.gcp_provider.GcpProvider.get_projects", + return_value=projects, + ), + patch( + "prowler.providers.gcp.gcp_provider.GcpProvider.update_projects_with_organizations", + return_value=None, + ), + patch( + "os.path.abspath", + return_value="test_credentials_file", + ), + patch( + "prowler.providers.gcp.gcp_provider.default", + return_value=(mocked_credentials, "default_project"), + ), + patch( + "prowler.providers.gcp.gcp_provider.discovery.build", + return_value=mocked_service, + ), + ): + gcp_provider = GcpProvider( + arguments.organization_id, + arguments.project_id, + arguments.excluded_project_id, + arguments.credentials_file, + arguments.impersonate_service_account, + arguments.list_project_id, + arguments.config_file, + arguments.fixer_config, + client_id=None, + client_secret=None, + refresh_token=None, + ) + assert gcp_provider.default_project_id == "active_project" + def test_print_credentials_default_options(self, capsys): mocked_credentials = MagicMock() @@ -464,21 +573,27 @@ class TestGCPProvider: mocked_service.projects.list.return_value = MagicMock( execute=MagicMock(return_value={"projects": projects}) ) - with patch( - "prowler.providers.gcp.gcp_provider.GcpProvider.get_projects", - return_value=projects, - ), patch( - "prowler.providers.gcp.gcp_provider.GcpProvider.update_projects_with_organizations", - return_value=None, - ), patch( - "os.path.abspath", - return_value="test_credentials_file", - ), patch( - "prowler.providers.gcp.gcp_provider.default", - return_value=(mocked_credentials, MagicMock()), - ), patch( - "prowler.providers.gcp.gcp_provider.discovery.build", - return_value=mocked_service, + with ( + patch( + "prowler.providers.gcp.gcp_provider.GcpProvider.get_projects", + return_value=projects, + ), + patch( + "prowler.providers.gcp.gcp_provider.GcpProvider.update_projects_with_organizations", + return_value=None, + ), + patch( + "os.path.abspath", + return_value="test_credentials_file", + ), + patch( + "prowler.providers.gcp.gcp_provider.default", + return_value=(mocked_credentials, MagicMock()), + ), + patch( + "prowler.providers.gcp.gcp_provider.discovery.build", + return_value=mocked_service, + ), ): gcp_provider = GcpProvider( arguments.organization_id, @@ -535,21 +650,27 @@ class TestGCPProvider: mocked_service.projects.list.return_value = MagicMock( execute=MagicMock(return_value={"projects": projects}) ) - with patch( - "prowler.providers.gcp.gcp_provider.GcpProvider.get_projects", - return_value=projects, - ), patch( - "prowler.providers.gcp.gcp_provider.GcpProvider.update_projects_with_organizations", - return_value=None, - ), patch( - "os.path.abspath", - return_value="test_credentials_file", - ), patch( - "prowler.providers.gcp.gcp_provider.default", - return_value=(mocked_credentials, MagicMock()), - ), patch( - "prowler.providers.gcp.gcp_provider.discovery.build", - return_value=mocked_service, + with ( + patch( + "prowler.providers.gcp.gcp_provider.GcpProvider.get_projects", + return_value=projects, + ), + patch( + "prowler.providers.gcp.gcp_provider.GcpProvider.update_projects_with_organizations", + return_value=None, + ), + patch( + "os.path.abspath", + return_value="test_credentials_file", + ), + patch( + "prowler.providers.gcp.gcp_provider.default", + return_value=(mocked_credentials, MagicMock()), + ), + patch( + "prowler.providers.gcp.gcp_provider.discovery.build", + return_value=mocked_service, + ), ): gcp_provider = GcpProvider( arguments.organization_id, @@ -614,21 +735,27 @@ class TestGCPProvider: execute=MagicMock(return_value={"projects": projects}) ) - with patch( - "prowler.providers.gcp.gcp_provider.GcpProvider.get_projects", - return_value=projects, - ), patch( - "prowler.providers.gcp.gcp_provider.GcpProvider.update_projects_with_organizations", - return_value=None, - ), patch( - "os.path.abspath", - return_value="test_credentials_file", - ), patch( - "prowler.providers.gcp.gcp_provider.default", - return_value=(mocked_credentials, MagicMock()), - ), patch( - "prowler.providers.gcp.gcp_provider.discovery.build", - return_value=mocked_service, + with ( + patch( + "prowler.providers.gcp.gcp_provider.GcpProvider.get_projects", + return_value=projects, + ), + patch( + "prowler.providers.gcp.gcp_provider.GcpProvider.update_projects_with_organizations", + return_value=None, + ), + patch( + "os.path.abspath", + return_value="test_credentials_file", + ), + patch( + "prowler.providers.gcp.gcp_provider.default", + return_value=(mocked_credentials, MagicMock()), + ), + patch( + "prowler.providers.gcp.gcp_provider.discovery.build", + return_value=mocked_service, + ), ): gcp_provider = GcpProvider( arguments.organization_id, @@ -698,12 +825,15 @@ class TestGCPProvider: execute=MagicMock(return_value={"projectId": project_id}) ) - with patch( - "prowler.providers.gcp.gcp_provider.GcpProvider.setup_session", - return_value=(None, project_id), - ), patch( - "prowler.providers.gcp.gcp_provider.discovery.build", - return_value=mocked_service, + with ( + patch( + "prowler.providers.gcp.gcp_provider.GcpProvider.setup_session", + return_value=(None, project_id), + ), + patch( + "prowler.providers.gcp.gcp_provider.discovery.build", + return_value=mocked_service, + ), ): output = GcpProvider.test_connection( client_id="test-client-id", @@ -730,16 +860,19 @@ class TestGCPProvider: execute=MagicMock(return_value={"projects": projects}) ) - with patch( - "prowler.providers.gcp.gcp_provider.GcpProvider.setup_session", - return_value=(None, "test-valid-project"), - ), patch( - "prowler.providers.gcp.gcp_provider.discovery.build", - return_value=mocked_service, - ), patch( - "prowler.providers.gcp.gcp_provider.GcpProvider.validate_project_id" - ) as mock_validate_project_id: - + with ( + patch( + "prowler.providers.gcp.gcp_provider.GcpProvider.setup_session", + return_value=(None, "test-valid-project"), + ), + patch( + "prowler.providers.gcp.gcp_provider.discovery.build", + return_value=mocked_service, + ), + patch( + "prowler.providers.gcp.gcp_provider.GcpProvider.validate_project_id" + ) as mock_validate_project_id, + ): mock_validate_project_id.side_effect = GCPInvalidProviderIdError( "Invalid project ID" ) diff --git a/ui/actions/users/users.ts b/ui/actions/users/users.ts index cf3397a940..5f8cfa4b25 100644 --- a/ui/actions/users/users.ts +++ b/ui/actions/users/users.ts @@ -51,14 +51,28 @@ export const updateUser = async (formData: FormData) => { const session = await auth(); const keyServer = process.env.API_BASE_URL; - const userId = formData.get("userId"); - const userName = formData.get("name"); - const userPassword = formData.get("password"); - const userEmail = formData.get("email"); - const userCompanyName = formData.get("company_name"); + const userId = formData.get("userId") as string; // Ensure userId is a string + const userName = formData.get("name") as string | null; + const userPassword = formData.get("password") as string | null; + const userEmail = formData.get("email") as string | null; + const userCompanyName = formData.get("company_name") as string | null; const url = new URL(`${keyServer}/users/${userId}`); + // Prepare attributes to send based on changes + const attributes: Record = {}; + + // Add only changed fields + if (userName !== null) attributes.name = userName; + if (userEmail !== null) attributes.email = userEmail; + if (userCompanyName !== null) attributes.company_name = userCompanyName; + if (userPassword !== null) attributes.password = userPassword; + + // If no fields have changed, don't send the request + if (Object.keys(attributes).length === 0) { + return { error: "No changes detected" }; + } + try { const response = await fetch(url.toString(), { method: "PATCH", @@ -71,15 +85,11 @@ export const updateUser = async (formData: FormData) => { data: { type: "users", id: userId, - attributes: { - name: userName, - password: userPassword, - email: userEmail, - company_name: userCompanyName, - }, + attributes: attributes, }, }), }); + const data = await response.json(); revalidatePath("/users"); return parseStringify(data); diff --git a/ui/components/users/forms/edit-form.tsx b/ui/components/users/forms/edit-form.tsx index ece7080b5a..8f6788ac8f 100644 --- a/ui/components/users/forms/edit-form.tsx +++ b/ui/components/users/forms/edit-form.tsx @@ -25,11 +25,7 @@ export const EditForm = ({ userCompanyName?: string; setIsOpen: Dispatch>; }) => { - const formSchema = editUserFormSchema( - userName ?? "", - userEmail ?? "", - userCompanyName ?? "", - ); + const formSchema = editUserFormSchema(); const form = useForm>({ resolver: zodResolver(formSchema), @@ -48,16 +44,26 @@ export const EditForm = ({ const onSubmitClient = async (values: z.infer) => { const formData = new FormData(); - Object.entries(values).forEach( - ([key, value]) => value !== undefined && formData.append(key, value), - ); + // Check if the value is not undefined before appending to FormData + if (values.name !== undefined) { + formData.append("name", values.name); + } + if (values.email !== undefined) { + formData.append("email", values.email); + } + if (values.company_name !== undefined) { + formData.append("company_name", values.company_name); + } + + // Always include userId + formData.append("userId", userId); const data = await updateUser(formData); if (data?.errors && data.errors.length > 0) { const error = data.errors[0]; const errorMessage = `${error.detail}`; - // show error + // Show error toast({ variant: "destructive", title: "Oops! Something went wrong", diff --git a/ui/requirements.txt b/ui/requirements.txt deleted file mode 100644 index 4dcab051c2..0000000000 --- a/ui/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -Django==5.0.9 -djangorestframework==3.15.2 -django-cors-headers==4.3.1 diff --git a/ui/types/formSchemas.ts b/ui/types/formSchemas.ts index 013aa6375e..92f4cc0f99 100644 --- a/ui/types/formSchemas.ts +++ b/ui/types/formSchemas.ts @@ -182,36 +182,21 @@ export const editInviteFormSchema = z.object({ expires_at: z.string().optional(), }); -export const editUserFormSchema = ( - currentName: string, - currentEmail: string, - currentCompanyName: string, -) => +export const editUserFormSchema = () => z.object({ name: z .string() .min(3, { message: "The name must have at least 3 characters." }) .max(150, { message: "The name cannot exceed 150 characters." }) - .refine((val) => val !== currentName, { - message: "The new name must be different from the current one.", - }) .optional(), email: z .string() .email({ message: "Please enter a valid email address." }) - .refine((val) => val !== currentEmail, { - message: "The new email must be different from the current one.", - }) .optional(), password: z .string() .min(1, { message: "The password cannot be empty." }) .optional(), - company_name: z - .string() - .refine((val) => val !== currentCompanyName, { - message: "The new company name must be different from the current one.", - }) - .optional(), + company_name: z.string().optional(), userId: z.string(), });