diff --git a/.github/workflows/api-pull-request.yml b/.github/workflows/api-pull-request.yml index 01167bfb92..750ced6d87 100644 --- a/.github/workflows/api-pull-request.yml +++ b/.github/workflows/api-pull-request.yml @@ -6,6 +6,7 @@ on: - "master" - "v5.*" paths: + - ".github/workflows/api-pull-request.yml" - "api/**" pull_request: branches: @@ -14,7 +15,6 @@ on: paths: - "api/**" - env: POSTGRES_HOST: localhost POSTGRES_PORT: 5432 @@ -26,7 +26,8 @@ env: VALKEY_HOST: localhost VALKEY_PORT: 6379 VALKEY_DB: 0 - + API_WORKING_DIR: ./api + IMAGE_NAME: prowler-api jobs: test: @@ -171,3 +172,18 @@ jobs: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: flags: api + test-container-build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Build Container + uses: docker/build-push-action@v6 + with: + context: ${{ env.API_WORKING_DIR }} + push: false + tags: ${{ env.IMAGE_NAME }}:latest + outputs: type=docker + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/find-secrets.yml b/.github/workflows/find-secrets.yml index 3f2c135ddf..acf5f33217 100644 --- a/.github/workflows/find-secrets.yml +++ b/.github/workflows/find-secrets.yml @@ -11,7 +11,7 @@ jobs: with: fetch-depth: 0 - name: TruffleHog OSS - uses: trufflesecurity/trufflehog@v3.88.5 + uses: trufflesecurity/trufflehog@v3.88.6 with: path: ./ base: ${{ github.event.repository.default_branch }} diff --git a/.github/workflows/ui-pull-request.yml b/.github/workflows/ui-pull-request.yml index 6d0f81816e..79e4c5e54b 100644 --- a/.github/workflows/ui-pull-request.yml +++ b/.github/workflows/ui-pull-request.yml @@ -6,6 +6,7 @@ on: - "master" - "v5.*" paths: + - ".github/workflows/ui-pull-request.yml" - "ui/**" pull_request: branches: @@ -13,6 +14,9 @@ on: - "v5.*" paths: - 'ui/**' +env: + UI_WORKING_DIR: ./ui + IMAGE_NAME: prowler-ui jobs: test-and-coverage: @@ -39,3 +43,20 @@ jobs: - name: Build the application working-directory: ./ui run: npm run build + test-container-build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Build Container + uses: docker/build-push-action@v6 + with: + context: ${{ env.UI_WORKING_DIR }} + # Always build using `prod` target + target: prod + push: false + tags: ${{ env.IMAGE_NAME }}:latest + outputs: type=docker + build-args: | + NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_51LwpXXXX diff --git a/README.md b/README.md index 1cfaa15af1..cad7ef1311 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ It contains hundreds of controls covering CIS, NIST 800, NIST CSF, CISA, RBI, Fe | GCP | 77 | 13 | 4 | 3 | | Azure | 140 | 18 | 5 | 3 | | Kubernetes | 83 | 7 | 2 | 7 | +| Microsoft365 | 5 | 2 | 1 | 0 | > You can list the checks, services, compliance frameworks and categories with `prowler --list-checks`, `prowler --list-services`, `prowler --list-compliance` and `prowler --list-categories`. diff --git a/docs/developer-guide/provider.md b/docs/developer-guide/provider.md index ecc7c1493a..6f6b1ca0ae 100644 --- a/docs/developer-guide/provider.md +++ b/docs/developer-guide/provider.md @@ -175,6 +175,7 @@ Due to the complexity and differences of each provider use the rest of the provi - [GCP](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/gcp/gcp_provider.py) - [Azure](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/azure/azure_provider.py) - [Kubernetes](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/kubernetes/kubernetes_provider.py) +- [Microsoft365](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/microsoft365/microsoft365_provider.py) To facilitate understanding here is a pseudocode of how the most basic provider could be with examples. diff --git a/docs/developer-guide/services.md b/docs/developer-guide/services.md index 6424fa8269..d551a58269 100644 --- a/docs/developer-guide/services.md +++ b/docs/developer-guide/services.md @@ -237,3 +237,4 @@ It is really important to check if the current Prowler's permissions for each pr - AWS: https://docs.prowler.cloud/en/latest/getting-started/requirements/#aws-authentication - Azure: https://docs.prowler.cloud/en/latest/getting-started/requirements/#permissions - GCP: https://docs.prowler.cloud/en/latest/getting-started/requirements/#gcp-authentication +- Microsoft365: https://docs.prowler.cloud/en/latest/getting-started/requirements/#microsoft365-authentication diff --git a/docs/getting-started/requirements.md b/docs/getting-started/requirements.md index 19e9c29689..afda7da53e 100644 --- a/docs/getting-started/requirements.md +++ b/docs/getting-started/requirements.md @@ -102,3 +102,32 @@ Those credentials must be associated to a user or service account with proper pe ???+ note By default, `prowler` will scan all accessible GCP Projects, use flag `--project-ids` to specify the projects to be scanned. + +## Microsoft365 + +Prowler for Microsoft365 currently supports the following authentication types: + +- [Service principal application](https://learn.microsoft.com/en-us/entra/identity-platform/app-objects-and-service-principals?tabs=browser#service-principal-object) (recommended). +- Current az cli credentials stored. +- Interactive browser authentication. + + +???+ warning + For Prowler App only the Service Principal with an application authentication method is supported. + +### Service Principal authentication + +To allow Prowler assume the service principal identity to start the scan it is needed to configure the following environment variables: + +```console +export AZURE_CLIENT_ID="XXXXXXXXX" +export AZURE_CLIENT_SECRET="XXXXXXXXX" +export AZURE_TENANT_ID="XXXXXXXXX" +``` + +If you try to execute Prowler with the `--sp-env-auth` flag and those variables are empty or not exported, the execution is going to fail. +Follow the instructions in the [Create Prowler Service Principal](../tutorials/azure/create-prowler-service-principal.md) section to create a service principal. + +### Interactive Browser authentication + +To use `--browser-auth` the user needs to authenticate against Azure using the default browser to start the scan, also `--tenant-id` flag is required. diff --git a/docs/index.md b/docs/index.md index 648564bc18..58f76e1901 100644 --- a/docs/index.md +++ b/docs/index.md @@ -164,7 +164,7 @@ Prowler is available as a project in [PyPI](https://pypi.org/project/prowler/), * `Python >= 3.9` * `Python pip >= 21.0.0` - * AWS, GCP, Azure and/or Kubernetes credentials + * AWS, GCP, Azure, Microsoft365 and/or Kubernetes credentials _Commands_: @@ -417,7 +417,7 @@ While the scan is running, start exploring the findings in these sections: ### Prowler CLI -To run Prowler, you will need to specify the provider (e.g `aws`, `gcp`, `azure` or `kubernetes`): +To run Prowler, you will need to specify the provider (e.g `aws`, `gcp`, `azure`, `microsoft365` or `kubernetes`): ???+ note If no provider specified, AWS will be used for backward compatibility with most of v2 options. @@ -559,5 +559,23 @@ kubectl logs prowler-XXXXX --namespace prowler-ns ???+ note By default, `prowler` will scan all namespaces in your active Kubernetes context. Use the flag `--context` to specify the context to be scanned and `--namespaces` to specify the namespaces to be scanned. +#### Microsoft365 + +With Microsoft365 you need to specify which auth method is going to be used: + +```console +# To use service principal authentication +prowler microsoft365 --sp-env-auth + +# To use az cli authentication +prowler microsoft365 --az-cli-auth + +# To use browser authentication +prowler microsoft365 --browser-auth --tenant-id "XXXXXXXX" + +``` + +See more details about Microsoft365 Authentication in [Requirements](getting-started/requirements.md#microsoft365) + ## Prowler v2 Documentation For **Prowler v2 Documentation**, please check it out [here](https://github.com/prowler-cloud/prowler/blob/8818f47333a0c1c1a457453c87af0ea5b89a385f/README.md). diff --git a/docs/tutorials/azure/create-prowler-service-principal.md b/docs/tutorials/azure/create-prowler-service-principal.md index 025c9da9c8..02ab7ea629 100644 --- a/docs/tutorials/azure/create-prowler-service-principal.md +++ b/docs/tutorials/azure/create-prowler-service-principal.md @@ -4,7 +4,7 @@ To allow Prowler assume an identity to start the scan with the required privileg To create a Service Principal Application you can use the Azure Portal or the Azure CLI. -## From Azure Portal +## From Azure Portal / Entra Admin Center 1. Access to Microsoft Entra ID 2. In the left menu bar, go to "App registrations" diff --git a/docs/tutorials/microsoft365/authentication.md b/docs/tutorials/microsoft365/authentication.md new file mode 100644 index 0000000000..750352ed76 --- /dev/null +++ b/docs/tutorials/microsoft365/authentication.md @@ -0,0 +1,23 @@ +# Microsoft365 authentication + +By default Prowler uses MsGraph Python SDK identity package authentication methods using the class `ClientSecretCredential`. +This allows Prowler to authenticate against microsoft365 using the following methods: + +- Service principal authentication by environment variables (Enterprise Application) +- Current CLI credentials stored +- Interactive browser authentication + +To launch the tool first you need to specify which method is used through the following flags: + +```console +# To use service principal authentication +prowler microsoft365 --sp-env-auth + +# To use cli authentication +prowler microsoft365 --az-cli-auth + +# To use browser authentication +prowler microsoft365 --browser-auth --tenant-id "XXXXXXXX" +``` + +To use Prowler you need to set up also the permissions required to access your resources in your Microsoft365 account, to more details refer to [Requirements](../../getting-started/requirements.md) diff --git a/mkdocs.yml b/mkdocs.yml index b3ee70140e..6d99d72afd 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -94,6 +94,9 @@ nav: - In-Cluster Execution: tutorials/kubernetes/in-cluster.md - Non In-Cluster Execution: tutorials/kubernetes/outside-cluster.md - Miscellaneous: tutorials/kubernetes/misc.md + - Microsoft 365: + - Authentication: tutorials/microsoft365/authentication.md + - Create Prowler Service Principal: tutorials/microsoft365/create-prowler-service-principal.md - Developer Guide: - Introduction: developer-guide/introduction.md - Provider: developer-guide/provider.md diff --git a/poetry.lock b/poetry.lock index 9cce754b22..1697f1d1fd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1072,73 +1072,74 @@ files = [ [[package]] name = "coverage" -version = "7.6.10" +version = "7.6.12" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" files = [ - {file = "coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78"}, - {file = "coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c"}, - {file = "coverage-7.6.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3b204c11e2b2d883946fe1d97f89403aa1811df28ce0447439178cc7463448a"}, - {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32ee6d8491fcfc82652a37109f69dee9a830e9379166cb73c16d8dc5c2915165"}, - {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675cefc4c06e3b4c876b85bfb7c59c5e2218167bbd4da5075cbe3b5790a28988"}, - {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f4f620668dbc6f5e909a0946a877310fb3d57aea8198bde792aae369ee1c23b5"}, - {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4eea95ef275de7abaef630c9b2c002ffbc01918b726a39f5a4353916ec72d2f3"}, - {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2f0280519e42b0a17550072861e0bc8a80a0870de260f9796157d3fca2733c5"}, - {file = "coverage-7.6.10-cp310-cp310-win32.whl", hash = "sha256:bc67deb76bc3717f22e765ab3e07ee9c7a5e26b9019ca19a3b063d9f4b874244"}, - {file = "coverage-7.6.10-cp310-cp310-win_amd64.whl", hash = "sha256:0f460286cb94036455e703c66988851d970fdfd8acc2a1122ab7f4f904e4029e"}, - {file = "coverage-7.6.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ea3c8f04b3e4af80e17bab607c386a830ffc2fb88a5484e1df756478cf70d1d3"}, - {file = "coverage-7.6.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:507a20fc863cae1d5720797761b42d2d87a04b3e5aeb682ef3b7332e90598f43"}, - {file = "coverage-7.6.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37a84878285b903c0fe21ac8794c6dab58150e9359f1aaebbeddd6412d53132"}, - {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a534738b47b0de1995f85f582d983d94031dffb48ab86c95bdf88dc62212142f"}, - {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d7a2bf79378d8fb8afaa994f91bfd8215134f8631d27eba3e0e2c13546ce994"}, - {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6713ba4b4ebc330f3def51df1d5d38fad60b66720948112f114968feb52d3f99"}, - {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab32947f481f7e8c763fa2c92fd9f44eeb143e7610c4ca9ecd6a36adab4081bd"}, - {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7bbd8c8f1b115b892e34ba66a097b915d3871db7ce0e6b9901f462ff3a975377"}, - {file = "coverage-7.6.10-cp311-cp311-win32.whl", hash = "sha256:299e91b274c5c9cdb64cbdf1b3e4a8fe538a7a86acdd08fae52301b28ba297f8"}, - {file = "coverage-7.6.10-cp311-cp311-win_amd64.whl", hash = "sha256:489a01f94aa581dbd961f306e37d75d4ba16104bbfa2b0edb21d29b73be83609"}, - {file = "coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853"}, - {file = "coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078"}, - {file = "coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0"}, - {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50"}, - {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022"}, - {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b"}, - {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0"}, - {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852"}, - {file = "coverage-7.6.10-cp312-cp312-win32.whl", hash = "sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359"}, - {file = "coverage-7.6.10-cp312-cp312-win_amd64.whl", hash = "sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247"}, - {file = "coverage-7.6.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05fca8ba6a87aabdd2d30d0b6c838b50510b56cdcfc604d40760dae7153b73d9"}, - {file = "coverage-7.6.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e80eba8801c386f72e0712a0453431259c45c3249f0009aff537a517b52942b"}, - {file = "coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690"}, - {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec22b5e7fe7a0fa8509181c4aac1db48f3dd4d3a566131b313d1efc102892c18"}, - {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26bcf5c4df41cad1b19c84af71c22cbc9ea9a547fc973f1f2cc9a290002c8b3c"}, - {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd"}, - {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e"}, - {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694"}, - {file = "coverage-7.6.10-cp313-cp313-win32.whl", hash = "sha256:9e1747bab246d6ff2c4f28b4d186b205adced9f7bd9dc362051cc37c4a0c7bd6"}, - {file = "coverage-7.6.10-cp313-cp313-win_amd64.whl", hash = "sha256:254f1a3b1eef5f7ed23ef265eaa89c65c8c5b6b257327c149db1ca9d4a35f25e"}, - {file = "coverage-7.6.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ccf240eb719789cedbb9fd1338055de2761088202a9a0b73032857e53f612fe"}, - {file = "coverage-7.6.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c807ca74d5a5e64427c8805de15b9ca140bba13572d6d74e262f46f50b13273"}, - {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8"}, - {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0de1e902669dccbf80b0415fb6b43d27edca2fbd48c74da378923b05316098"}, - {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7b444c42bbc533aaae6b5a2166fd1a797cdb5eb58ee51a92bee1eb94a1e1cb"}, - {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0"}, - {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf"}, - {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2"}, - {file = "coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312"}, - {file = "coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d"}, - {file = "coverage-7.6.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:656c82b8a0ead8bba147de9a89bda95064874c91a3ed43a00e687f23cc19d53a"}, - {file = "coverage-7.6.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccc2b70a7ed475c68ceb548bf69cec1e27305c1c2606a5eb7c3afff56a1b3b27"}, - {file = "coverage-7.6.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5e37dc41d57ceba70956fa2fc5b63c26dba863c946ace9705f8eca99daecdc4"}, - {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0aa9692b4fdd83a4647eeb7db46410ea1322b5ed94cd1715ef09d1d5922ba87f"}, - {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa744da1820678b475e4ba3dfd994c321c5b13381d1041fe9c608620e6676e25"}, - {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0b1818063dc9e9d838c09e3a473c1422f517889436dd980f5d721899e66f315"}, - {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:59af35558ba08b758aec4d56182b222976330ef8d2feacbb93964f576a7e7a90"}, - {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7ed2f37cfce1ce101e6dffdfd1c99e729dd2ffc291d02d3e2d0af8b53d13840d"}, - {file = "coverage-7.6.10-cp39-cp39-win32.whl", hash = "sha256:4bcc276261505d82f0ad426870c3b12cb177752834a633e737ec5ee79bbdff18"}, - {file = "coverage-7.6.10-cp39-cp39-win_amd64.whl", hash = "sha256:457574f4599d2b00f7f637a0700a6422243b3565509457b2dbd3f50703e11f59"}, - {file = "coverage-7.6.10-pp39.pp310-none-any.whl", hash = "sha256:fd34e7b3405f0cc7ab03d54a334c17a9e802897580d964bd8c2001f4b9fd488f"}, - {file = "coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23"}, + {file = "coverage-7.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:704c8c8c6ce6569286ae9622e534b4f5b9759b6f2cd643f1c1a61f666d534fe8"}, + {file = "coverage-7.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad7525bf0241e5502168ae9c643a2f6c219fa0a283001cee4cf23a9b7da75879"}, + {file = "coverage-7.6.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06097c7abfa611c91edb9e6920264e5be1d6ceb374efb4986f38b09eed4cb2fe"}, + {file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:220fa6c0ad7d9caef57f2c8771918324563ef0d8272c94974717c3909664e674"}, + {file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3688b99604a24492bcfe1c106278c45586eb819bf66a654d8a9a1433022fb2eb"}, + {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d1a987778b9c71da2fc8948e6f2656da6ef68f59298b7e9786849634c35d2c3c"}, + {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cec6b9ce3bd2b7853d4a4563801292bfee40b030c05a3d29555fd2a8ee9bd68c"}, + {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ace9048de91293e467b44bce0f0381345078389814ff6e18dbac8fdbf896360e"}, + {file = "coverage-7.6.12-cp310-cp310-win32.whl", hash = "sha256:ea31689f05043d520113e0552f039603c4dd71fa4c287b64cb3606140c66f425"}, + {file = "coverage-7.6.12-cp310-cp310-win_amd64.whl", hash = "sha256:676f92141e3c5492d2a1596d52287d0d963df21bf5e55c8b03075a60e1ddf8aa"}, + {file = "coverage-7.6.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e18aafdfb3e9ec0d261c942d35bd7c28d031c5855dadb491d2723ba54f4c3015"}, + {file = "coverage-7.6.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66fe626fd7aa5982cdebad23e49e78ef7dbb3e3c2a5960a2b53632f1f703ea45"}, + {file = "coverage-7.6.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ef01d70198431719af0b1f5dcbefc557d44a190e749004042927b2a3fed0702"}, + {file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e92ae5a289a4bc4c0aae710c0948d3c7892e20fd3588224ebe242039573bf0"}, + {file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e695df2c58ce526eeab11a2e915448d3eb76f75dffe338ea613c1201b33bab2f"}, + {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d74c08e9aaef995f8c4ef6d202dbd219c318450fe2a76da624f2ebb9c8ec5d9f"}, + {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e995b3b76ccedc27fe4f477b349b7d64597e53a43fc2961db9d3fbace085d69d"}, + {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b1f097878d74fe51e1ddd1be62d8e3682748875b461232cf4b52ddc6e6db0bba"}, + {file = "coverage-7.6.12-cp311-cp311-win32.whl", hash = "sha256:1f7ffa05da41754e20512202c866d0ebfc440bba3b0ed15133070e20bf5aeb5f"}, + {file = "coverage-7.6.12-cp311-cp311-win_amd64.whl", hash = "sha256:e216c5c45f89ef8971373fd1c5d8d1164b81f7f5f06bbf23c37e7908d19e8558"}, + {file = "coverage-7.6.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b172f8e030e8ef247b3104902cc671e20df80163b60a203653150d2fc204d1ad"}, + {file = "coverage-7.6.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:641dfe0ab73deb7069fb972d4d9725bf11c239c309ce694dd50b1473c0f641c3"}, + {file = "coverage-7.6.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e549f54ac5f301e8e04c569dfdb907f7be71b06b88b5063ce9d6953d2d58574"}, + {file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959244a17184515f8c52dcb65fb662808767c0bd233c1d8a166e7cf74c9ea985"}, + {file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bda1c5f347550c359f841d6614fb8ca42ae5cb0b74d39f8a1e204815ebe25750"}, + {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ceeb90c3eda1f2d8c4c578c14167dbd8c674ecd7d38e45647543f19839dd6ea"}, + {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f16f44025c06792e0fb09571ae454bcc7a3ec75eeb3c36b025eccf501b1a4c3"}, + {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b076e625396e787448d27a411aefff867db2bffac8ed04e8f7056b07024eed5a"}, + {file = "coverage-7.6.12-cp312-cp312-win32.whl", hash = "sha256:00b2086892cf06c7c2d74983c9595dc511acca00665480b3ddff749ec4fb2a95"}, + {file = "coverage-7.6.12-cp312-cp312-win_amd64.whl", hash = "sha256:7ae6eabf519bc7871ce117fb18bf14e0e343eeb96c377667e3e5dd12095e0288"}, + {file = "coverage-7.6.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:488c27b3db0ebee97a830e6b5a3ea930c4a6e2c07f27a5e67e1b3532e76b9ef1"}, + {file = "coverage-7.6.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d1095bbee1851269f79fd8e0c9b5544e4c00c0c24965e66d8cba2eb5bb535fd"}, + {file = "coverage-7.6.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0533adc29adf6a69c1baa88c3d7dbcaadcffa21afbed3ca7a225a440e4744bf9"}, + {file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53c56358d470fa507a2b6e67a68fd002364d23c83741dbc4c2e0680d80ca227e"}, + {file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64cbb1a3027c79ca6310bf101014614f6e6e18c226474606cf725238cf5bc2d4"}, + {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79cac3390bfa9836bb795be377395f28410811c9066bc4eefd8015258a7578c6"}, + {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b148068e881faa26d878ff63e79650e208e95cf1c22bd3f77c3ca7b1d9821a3"}, + {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8bec2ac5da793c2685ce5319ca9bcf4eee683b8a1679051f8e6ec04c4f2fd7dc"}, + {file = "coverage-7.6.12-cp313-cp313-win32.whl", hash = "sha256:200e10beb6ddd7c3ded322a4186313d5ca9e63e33d8fab4faa67ef46d3460af3"}, + {file = "coverage-7.6.12-cp313-cp313-win_amd64.whl", hash = "sha256:2b996819ced9f7dbb812c701485d58f261bef08f9b85304d41219b1496b591ef"}, + {file = "coverage-7.6.12-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:299cf973a7abff87a30609879c10df0b3bfc33d021e1adabc29138a48888841e"}, + {file = "coverage-7.6.12-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b467a8c56974bf06e543e69ad803c6865249d7a5ccf6980457ed2bc50312703"}, + {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2458f275944db8129f95d91aee32c828a408481ecde3b30af31d552c2ce284a0"}, + {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a9d8be07fb0832636a0f72b80d2a652fe665e80e720301fb22b191c3434d924"}, + {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d47376a4f445e9743f6c83291e60adb1b127607a3618e3185bbc8091f0467b"}, + {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b95574d06aa9d2bd6e5cc35a5bbe35696342c96760b69dc4287dbd5abd4ad51d"}, + {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ecea0c38c9079570163d663c0433a9af4094a60aafdca491c6a3d248c7432827"}, + {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2251fabcfee0a55a8578a9d29cecfee5f2de02f11530e7d5c5a05859aa85aee9"}, + {file = "coverage-7.6.12-cp313-cp313t-win32.whl", hash = "sha256:eb5507795caabd9b2ae3f1adc95f67b1104971c22c624bb354232d65c4fc90b3"}, + {file = "coverage-7.6.12-cp313-cp313t-win_amd64.whl", hash = "sha256:f60a297c3987c6c02ffb29effc70eadcbb412fe76947d394a1091a3615948e2f"}, + {file = "coverage-7.6.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e7575ab65ca8399c8c4f9a7d61bbd2d204c8b8e447aab9d355682205c9dd948d"}, + {file = "coverage-7.6.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8161d9fbc7e9fe2326de89cd0abb9f3599bccc1287db0aba285cb68d204ce929"}, + {file = "coverage-7.6.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a1e465f398c713f1b212400b4e79a09829cd42aebd360362cd89c5bdc44eb87"}, + {file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f25d8b92a4e31ff1bd873654ec367ae811b3a943583e05432ea29264782dc32c"}, + {file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a936309a65cc5ca80fa9f20a442ff9e2d06927ec9a4f54bcba9c14c066323f2"}, + {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aa6f302a3a0b5f240ee201297fff0bbfe2fa0d415a94aeb257d8b461032389bd"}, + {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f973643ef532d4f9be71dd88cf7588936685fdb576d93a79fe9f65bc337d9d73"}, + {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:78f5243bb6b1060aed6213d5107744c19f9571ec76d54c99cc15938eb69e0e86"}, + {file = "coverage-7.6.12-cp39-cp39-win32.whl", hash = "sha256:69e62c5034291c845fc4df7f8155e8544178b6c774f97a99e2734b05eb5bed31"}, + {file = "coverage-7.6.12-cp39-cp39-win_amd64.whl", hash = "sha256:b01a840ecc25dce235ae4c1b6a0daefb2a203dba0e6e980637ee9c2f6ee0df57"}, + {file = "coverage-7.6.12-pp39.pp310-none-any.whl", hash = "sha256:7e39e845c4d764208e7b8f6a21c541ade741e2c41afabdfa1caa28687a3c98cf"}, + {file = "coverage-7.6.12-py3-none-any.whl", hash = "sha256:eb8668cfbc279a536c633137deeb9435d2962caec279c3f8cf8b91fff6ff8953"}, + {file = "coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2"}, ] [package.dependencies] @@ -5090,4 +5091,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.13" -content-hash = "b479cce4f4992dab6f81b8118082daad2fd301cf01cbced02c90bc132dd27285" +content-hash = "7182befee2c0494d066d4715935ee4aaf6a7dfe24e6cfea429be90fad03c4024" diff --git a/prowler/config/config.py b/prowler/config/config.py index 083e2f3bad..af9cb770fe 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.3.0" +prowler_version = "5.4.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" @@ -124,7 +124,10 @@ def load_and_validate_config_file(provider: str, config_file_path: str) -> dict: # Not to introduce a breaking change, allow the old format config file without any provider keys # and a new format with a key for each provider to include their configuration values within. - if any(key in config_file for key in ["aws", "gcp", "azure", "kubernetes"]): + if any( + key in config_file + for key in ["aws", "gcp", "azure", "kubernetes", "microsoft365"] + ): config = config_file.get(provider, {}) else: config = config_file if config_file else {} diff --git a/prowler/lib/outputs/compliance/cis/cis_microsoft365.py b/prowler/lib/outputs/compliance/cis/cis_microsoft365.py index c269829526..8cf4722ba7 100644 --- a/prowler/lib/outputs/compliance/cis/cis_microsoft365.py +++ b/prowler/lib/outputs/compliance/cis/cis_microsoft365.py @@ -42,7 +42,7 @@ class Microsoft365CIS(ComplianceOutput): compliance_row = Microsoft365CISModel( Provider=finding.provider, Description=compliance.Description, - SubscriptionId=finding.account_uid, + TenantId=finding.account_uid, Location=finding.region, AssessmentDate=str(finding.timestamp), Requirements_Id=requirement.Id, @@ -73,8 +73,8 @@ class Microsoft365CIS(ComplianceOutput): compliance_row = Microsoft365CISModel( Provider=compliance.Provider.lower(), Description=compliance.Description, - SubscriptionId="", - Location="", + TenantId=finding.account_uid, + Location=finding.region, AssessmentDate=str(finding.timestamp), Requirements_Id=requirement.Id, Requirements_Description=requirement.Description, diff --git a/prowler/lib/outputs/compliance/cis/models.py b/prowler/lib/outputs/compliance/cis/models.py index 630cab0910..c06ed1f849 100644 --- a/prowler/lib/outputs/compliance/cis/models.py +++ b/prowler/lib/outputs/compliance/cis/models.py @@ -73,7 +73,7 @@ class Microsoft365CISModel(BaseModel): Provider: str Description: str - SubscriptionId: str + TenantId: str Location: str AssessmentDate: str Requirements_Id: str diff --git a/prowler/providers/aws/aws_regions_by_service.json b/prowler/providers/aws/aws_regions_by_service.json index 04fd6e3efc..4e05963af0 100644 --- a/prowler/providers/aws/aws_regions_by_service.json +++ b/prowler/providers/aws/aws_regions_by_service.json @@ -6155,7 +6155,9 @@ "ap-southeast-2", "ap-southeast-3", "ap-southeast-4", + "ap-southeast-5", "ca-central-1", + "ca-west-1", "eu-central-1", "eu-central-2", "eu-north-1", @@ -6164,6 +6166,7 @@ "eu-west-1", "eu-west-2", "eu-west-3", + "il-central-1", "me-central-1", "me-south-1", "sa-east-1", diff --git a/prowler/providers/aws/services/kms/kms_key_not_publicly_accessible/kms_key_not_publicly_accessible.py b/prowler/providers/aws/services/kms/kms_key_not_publicly_accessible/kms_key_not_publicly_accessible.py index 843d22b3c9..408034647a 100644 --- a/prowler/providers/aws/services/kms/kms_key_not_publicly_accessible/kms_key_not_publicly_accessible.py +++ b/prowler/providers/aws/services/kms/kms_key_not_publicly_accessible/kms_key_not_publicly_accessible.py @@ -8,7 +8,9 @@ class kms_key_not_publicly_accessible(Check): findings = [] for key in kms_client.keys: if ( - key.manager == "CUSTOMER" and key.state == "Enabled" + key.manager == "CUSTOMER" + and key.state == "Enabled" + and key.policy is not None ): # only customer KMS have policies report = Check_Report_AWS(metadata=self.metadata(), resource=key) report.status = "PASS" diff --git a/prowler/providers/aws/services/kms/kms_service.py b/prowler/providers/aws/services/kms/kms_service.py index a4ef49ec57..1dc4988842 100644 --- a/prowler/providers/aws/services/kms/kms_service.py +++ b/prowler/providers/aws/services/kms/kms_service.py @@ -26,15 +26,20 @@ class KMS(AWSService): list_keys_paginator = regional_client.get_paginator("list_keys") for page in list_keys_paginator.paginate(): for key in page["Keys"]: - if not self.audit_resources or ( - is_resource_filtered(key["KeyArn"], self.audit_resources) - ): - self.keys.append( - Key( - id=key["KeyId"], - arn=key["KeyArn"], - region=regional_client.region, + try: + if not self.audit_resources or ( + is_resource_filtered(key["KeyArn"], self.audit_resources) + ): + self.keys.append( + Key( + id=key["KeyId"], + arn=key["KeyArn"], + region=regional_client.region, + ) ) + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}:{error.__traceback__.tb_lineno} -- {error}" ) except Exception as error: logger.error( @@ -45,8 +50,8 @@ class KMS(AWSService): logger.info("KMS - Describing Key...") try: for key in self.keys: + regional_client = self.regional_clients[key.region] try: - regional_client = self.regional_clients[key.region] response = regional_client.describe_key(KeyId=key.id) key.state = response["KeyMetadata"]["KeyState"] key.origin = response["KeyMetadata"]["Origin"] @@ -73,9 +78,14 @@ class KMS(AWSService): and "AWS" not in key.manager ): regional_client = self.regional_clients[key.region] - key.rotation_enabled = regional_client.get_key_rotation_status( - KeyId=key.id - )["KeyRotationEnabled"] + try: + key.rotation_enabled = regional_client.get_key_rotation_status( + KeyId=key.id + )["KeyRotationEnabled"] + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}:{error.__traceback__.tb_lineno} -- {error}" + ) except Exception as error: logger.error( f"{regional_client.region} -- {error.__class__.__name__}:{error.__traceback__.tb_lineno} -- {error}" @@ -89,11 +99,16 @@ class KMS(AWSService): key.manager and key.manager == "CUSTOMER" ): # only customer KMS have policies regional_client = self.regional_clients[key.region] - key.policy = json.loads( - regional_client.get_key_policy( - KeyId=key.id, PolicyName="default" - )["Policy"] - ) + try: + key.policy = json.loads( + regional_client.get_key_policy( + KeyId=key.id, PolicyName="default" + )["Policy"] + ) + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}:{error.__traceback__.tb_lineno} -- {error}" + ) except Exception as error: logger.error( f"{regional_client.region} -- {error.__class__.__name__}:{error.__traceback__.tb_lineno} -- {error}" @@ -101,20 +116,25 @@ class KMS(AWSService): def _list_resource_tags(self): logger.info("KMS - List Tags...") - for key in self.keys: - if ( - key.manager and key.manager == "CUSTOMER" - ): # only check customer KMS keys - try: - regional_client = self.regional_clients[key.region] - response = regional_client.list_resource_tags( - KeyId=key.id, - )["Tags"] - key.tags = response - except Exception as error: - logger.error( - f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" - ) + try: + for key in self.keys: + if ( + key.manager and key.manager == "CUSTOMER" + ): # only check customer KMS keys + try: + regional_client = self.regional_clients[key.region] + response = regional_client.list_resource_tags( + KeyId=key.id, + )["Tags"] + key.tags = response + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}:{error.__traceback__.tb_lineno} -- {error}" + ) class Key(BaseModel): diff --git a/pyproject.toml b/pyproject.toml index f9e447f738..05f233d132 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ packages = [ {include = "dashboard"} ] readme = "README.md" -version = "5.3.0" +version = "5.4.0" [tool.poetry.dependencies] alive-progress = "3.2.0" @@ -77,7 +77,7 @@ tzlocal = "5.2" [tool.poetry.group.dev.dependencies] bandit = "1.8.2" black = "24.10.0" -coverage = "7.6.10" +coverage = "7.6.12" docker = "7.1.0" flake8 = "7.1.1" freezegun = "1.5.1" diff --git a/tests/providers/aws/services/kms/kms_cmk_are_used/kms_cmk_are_used_test.py b/tests/providers/aws/services/kms/kms_cmk_are_used/kms_cmk_are_used_test.py index 7bf5fd5b07..2c8e162609 100644 --- a/tests/providers/aws/services/kms/kms_cmk_are_used/kms_cmk_are_used_test.py +++ b/tests/providers/aws/services/kms/kms_cmk_are_used/kms_cmk_are_used_test.py @@ -1,7 +1,9 @@ +from typing import Any, List from unittest import mock from boto3 import client from moto import mock_aws +import pytest from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider @@ -62,6 +64,65 @@ class Test_kms_cmk_are_used: assert result[0].resource_id == key["KeyId"] assert result[0].resource_arn == key["Arn"] + @pytest.mark.parametrize( + "no_of_keys_created,expected_no_of_results", + [ + (5, 3), + (7, 5), + (10, 8), + ], + ) + @mock_aws + def test_kms_cmk_are_used_when_describe_key_fails_on_2_keys_out_of_x_keys( + self, no_of_keys_created: int, expected_no_of_results: int + ) -> None: + # Generate KMS Client + kms_client = client("kms", region_name=AWS_REGION_US_EAST_1) + kms_client.__dict__["region"] = AWS_REGION_US_EAST_1 + # Create enabled KMS key + for i in range(no_of_keys_created): + kms_client.create_key( + Tags=[ + {"TagKey": "test", "TagValue": f"test{i}"}, + ], + ) + + orig_describe_key = kms_client.describe_key + + def mock_describe_key(KeyId: str, count: List[int] = [0]) -> Any: + if count[0] in [2, 4]: + count[0] += 1 + raise Exception("FakeClientError") + else: + count[0] += 1 + return orig_describe_key(KeyId=KeyId) + + kms_client.describe_key = mock_describe_key + + from prowler.providers.aws.services.kms.kms_service import KMS + + 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.aws_provider.AwsProvider.generate_regional_clients", + return_value={AWS_REGION_US_EAST_1: kms_client}, + ), mock.patch( + "prowler.providers.aws.services.kms.kms_cmk_are_used.kms_cmk_are_used.kms_client", + new=KMS(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.kms.kms_cmk_are_used.kms_cmk_are_used import ( + kms_cmk_are_used, + ) + + check = kms_cmk_are_used() + result = check.execute() + + assert len(result) == expected_no_of_results + @mock_aws def test_kms_key_with_deletion(self): # Generate KMS Client diff --git a/tests/providers/aws/services/kms/kms_cmk_rotation_enabled/kms_cmk_rotation_enabled_test.py b/tests/providers/aws/services/kms/kms_cmk_rotation_enabled/kms_cmk_rotation_enabled_test.py index 888a3da0f6..c5562cc6f0 100644 --- a/tests/providers/aws/services/kms/kms_cmk_rotation_enabled/kms_cmk_rotation_enabled_test.py +++ b/tests/providers/aws/services/kms/kms_cmk_rotation_enabled/kms_cmk_rotation_enabled_test.py @@ -1,7 +1,9 @@ +from typing import Any, List from unittest import mock from boto3 import client from moto import mock_aws +import pytest from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider @@ -66,6 +68,65 @@ class Test_kms_cmk_rotation_enabled: assert result[0].resource_id == key["KeyId"] assert result[0].resource_arn == key["Arn"] + @pytest.mark.parametrize( + "no_of_keys_created,expected_no_of_passes", + [ + (5, 3), + (7, 5), + (10, 8), + ], + ) + @mock_aws + def test_kms_cmk_rotation_enabled_when_get_key_rotation_status_fails_on_2_keys_out_of_x_keys( + self, no_of_keys_created: int, expected_no_of_passes: int + ) -> None: + # Generate KMS Client + kms_client = client("kms", region_name=AWS_REGION_US_EAST_1) + kms_client.__dict__["region"] = AWS_REGION_US_EAST_1 + # Creaty KMS key with rotation + for i in range(no_of_keys_created): + key = kms_client.create_key()["KeyMetadata"] + if i not in [2, 4]: + kms_client.enable_key_rotation(KeyId=key["KeyId"]) + + orig_get_key_rotation_status = kms_client.get_key_rotation_status + + def mock_get_key_rotation_status(KeyId: str, count: List[int] = [0]) -> Any: + if count[0] in [2, 4]: + count[0] += 1 + raise Exception("FakeClientError") + else: + count[0] += 1 + return orig_get_key_rotation_status(KeyId=KeyId) + + kms_client.get_key_rotation_status = mock_get_key_rotation_status + + from prowler.providers.aws.services.kms.kms_service import KMS + + 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.aws_provider.AwsProvider.generate_regional_clients", + return_value={AWS_REGION_US_EAST_1: kms_client}, + ), mock.patch( + "prowler.providers.aws.services.kms.kms_cmk_rotation_enabled.kms_cmk_rotation_enabled.kms_client", + new=KMS(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.kms.kms_cmk_rotation_enabled.kms_cmk_rotation_enabled import ( + kms_cmk_rotation_enabled, + ) + + check = kms_cmk_rotation_enabled() + result = check.execute() + + assert len(result) == no_of_keys_created + statuses = [r.status for r in result] + assert statuses.count("PASS") == expected_no_of_passes + @mock_aws def test_kms_cmk_rotation_disabled(self): # Generate KMS Client diff --git a/tests/providers/aws/services/kms/kms_key_not_publicly_accessible/kms_key_not_publicly_accessible_test.py b/tests/providers/aws/services/kms/kms_key_not_publicly_accessible/kms_key_not_publicly_accessible_test.py index 6e0caa22d5..c8abdaaf5d 100644 --- a/tests/providers/aws/services/kms/kms_key_not_publicly_accessible/kms_key_not_publicly_accessible_test.py +++ b/tests/providers/aws/services/kms/kms_key_not_publicly_accessible/kms_key_not_publicly_accessible_test.py @@ -1,6 +1,8 @@ import json +from typing import Any, List from unittest import mock +import pytest from boto3 import client from moto import mock_aws @@ -14,12 +16,15 @@ class Test_kms_key_not_publicly_accessible: 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.kms.kms_key_not_publicly_accessible.kms_key_not_publicly_accessible.kms_client", - new=KMS(aws_provider), + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.kms.kms_key_not_publicly_accessible.kms_key_not_publicly_accessible.kms_client", + new=KMS(aws_provider), + ), ): # Test Check from prowler.providers.aws.services.kms.kms_key_not_publicly_accessible.kms_key_not_publicly_accessible import ( @@ -36,18 +41,21 @@ class Test_kms_key_not_publicly_accessible: # Generate KMS Client kms_client = client("kms", region_name=AWS_REGION_US_EAST_1) # Creaty KMS key without policy - key = kms_client.create_key()["KeyMetadata"] + key = kms_client.create_key(MultiRegion=False)["KeyMetadata"] from prowler.providers.aws.services.kms.kms_service import KMS 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.kms.kms_key_not_publicly_accessible.kms_key_not_publicly_accessible.kms_client", - new=KMS(aws_provider), + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.kms.kms_key_not_publicly_accessible.kms_key_not_publicly_accessible.kms_client", + new=KMS(aws_provider), + ), ): # Test Check from prowler.providers.aws.services.kms.kms_key_not_publicly_accessible.kms_key_not_publicly_accessible import ( @@ -72,6 +80,7 @@ class Test_kms_key_not_publicly_accessible: kms_client = client("kms", region_name=AWS_REGION_US_EAST_1) # Creaty KMS key with public policy key = kms_client.create_key( + MultiRegion=False, Policy=json.dumps( { "Version": "2012-10-17", @@ -86,19 +95,22 @@ class Test_kms_key_not_publicly_accessible: } ], } - ) + ), )["KeyMetadata"] from prowler.providers.aws.services.kms.kms_service import KMS 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.kms.kms_key_not_publicly_accessible.kms_key_not_publicly_accessible.kms_client", - new=KMS(aws_provider), + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.kms.kms_key_not_publicly_accessible.kms_key_not_publicly_accessible.kms_client", + new=KMS(aws_provider), + ), ): # Test Check from prowler.providers.aws.services.kms.kms_key_not_publicly_accessible.kms_key_not_publicly_accessible import ( @@ -123,6 +135,7 @@ class Test_kms_key_not_publicly_accessible: kms_client = client("kms", region_name=AWS_REGION_US_EAST_1) # Creaty KMS key with public policy key = kms_client.create_key( + MultiRegion=False, Policy=json.dumps( { "Version": "2012-10-17", @@ -136,19 +149,22 @@ class Test_kms_key_not_publicly_accessible: } ], } - ) + ), )["KeyMetadata"] from prowler.providers.aws.services.kms.kms_service import KMS 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.kms.kms_key_not_publicly_accessible.kms_key_not_publicly_accessible.kms_client", - new=KMS(aws_provider), + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.kms.kms_key_not_publicly_accessible.kms_key_not_publicly_accessible.kms_client", + new=KMS(aws_provider), + ), ): # Test Check from prowler.providers.aws.services.kms.kms_key_not_publicly_accessible.kms_key_not_publicly_accessible import ( @@ -166,3 +182,66 @@ class Test_kms_key_not_publicly_accessible: ) assert result[0].resource_id == key["KeyId"] assert result[0].resource_arn == key["Arn"] + + @pytest.mark.parametrize( + "no_of_keys_created,expected_no_of_passes", + [ + (5, 3), + (7, 5), + (10, 8), + ], + ) + @mock_aws + def test_kms_key_not_publicly_accessible_when_get_key_policy_fails_on_2_keys_out_of_x_keys( + self, no_of_keys_created: int, expected_no_of_passes: int + ) -> None: + # Generate KMS Client + kms_client = client("kms", region_name=AWS_REGION_US_EAST_1) + kms_client.__dict__["region"] = AWS_REGION_US_EAST_1 + # Creaty KMS key with public policy + for i in range(no_of_keys_created): + kms_client.create_key(MultiRegion=False) + + orig_get_key_policy = kms_client.get_key_policy + + def mock_get_key_policy( + KeyId: str, PolicyName: str, count: List[int] = [0] + ) -> Any: + if count[0] in [2, 4]: + count[0] += 1 + raise Exception("FakeClientError") + else: + count[0] += 1 + return orig_get_key_policy(KeyId=KeyId, PolicyName=PolicyName) + + kms_client.get_key_policy = mock_get_key_policy + + from prowler.providers.aws.services.kms.kms_service import KMS + + 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.aws_provider.AwsProvider.generate_regional_clients", + return_value={AWS_REGION_US_EAST_1: kms_client}, + ), + mock.patch( + "prowler.providers.aws.services.kms.kms_key_not_publicly_accessible.kms_key_not_publicly_accessible.kms_client", + new=KMS(aws_provider), + ), + ): + # Test Check + from prowler.providers.aws.services.kms.kms_key_not_publicly_accessible.kms_key_not_publicly_accessible import ( + kms_key_not_publicly_accessible, + ) + + check = kms_key_not_publicly_accessible() + result = check.execute() + + assert len(result) == expected_no_of_passes + statuses = [r.status for r in result] + assert statuses.count("PASS") == expected_no_of_passes diff --git a/util/generate_compliance_json_from_csv_for_cis40_microsoft365.py b/util/generate_compliance_json_from_csv_for_cis40_microsoft365.py new file mode 100644 index 0000000000..c6ba8c101f --- /dev/null +++ b/util/generate_compliance_json_from_csv_for_cis40_microsoft365.py @@ -0,0 +1,74 @@ +import csv +import json +import sys + +# Convert a CSV file following the CIS 4.0 Microsoft365 Benchmark into a Prowler v3.0 Compliance JSON file +# CSV fields: +# Section #,Recommendation #,Profile,Title,Assessment Status,Description,Rationale Statement,Impact Statement,Remediation Procedure,Audit Procedure,Additional Information,CIS Controls,CIS Safeguards 1 (v8),CIS Safeguards 2 (v8),CIS Safeguards 3 (v8),v8 IG1,v8 IG2,v8 IG3,CIS Safeguards 1 (v7),CIS Safeguards 2 (v7),CIS Safeguards 3 (v7),v7 IG1,v7 IG2,v7 IG3,References,Default Value + +# Get the CSV filename to convert from +file_name = sys.argv[1] + +# Create the output JSON object +output = {"Framework": "CIS", "Version": "4.0", "Requirements": []} + +# Open the CSV file and read the rows +try: + with open(file_name, newline="", encoding="utf-8") as f: + reader = csv.reader(f, delimiter=",") + next(reader) # Skip the header row + for row in reader: + attribute = { + "Section": row[0], + "Profile": row[2], + "AssessmentStatus": row[4], + "Description": row[5], + "RationaleStatement": row[6], + "ImpactStatement": row[7], + "RemediationProcedure": row[8], + "AuditProcedure": row[9], + "AdditionalInformation": row[10], + "References": row[24], + } + if row[4] != "": + output["Requirements"].append( + { + "Id": row[1], + "Description": row[5], + "Checks": [], + "Attributes": [attribute], + } + ) +except UnicodeDecodeError: + # If there is an error reading the file with the default encoding, try with ISO-8859-1 + with open(file_name, newline="", encoding="ISO-8859-1") as f: + reader = csv.reader(f, delimiter=",") + next(reader) # Skip the header row + for row in reader: + attribute = { + "Section": row[0], + "Profile": row[2], + "AssessmentStatus": row[4], + "Description": row[5], + "RationaleStatement": row[6], + "ImpactStatement": row[7], + "RemediationProcedure": row[8], + "AuditProcedure": row[9], + "AdditionalInformation": row[10], + "References": row[24], + } + if row[4] != "": + output["Requirements"].append( + { + "Id": row[1], + "Description": row[5], + "Checks": [], + "Attributes": [attribute], + } + ) + +# Save the output JSON file +with open("cis_4.0_microsoft365.json", "w", encoding="utf-8") as outfile: + json.dump(output, outfile, indent=4, ensure_ascii=False) + +print("Archivo JSON generado exitosamente.")