diff --git a/.github/workflows/find-secrets.yml b/.github/workflows/find-secrets.yml index 607e87dbca..2d7839f9b5 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.82.6 + uses: trufflesecurity/trufflehog@v3.82.7 with: path: ./ base: ${{ github.event.repository.default_branch }} diff --git a/Dockerfile b/Dockerfile index 674c7b9723..096f4e29ee 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,9 +2,9 @@ FROM python:3.12-alpine LABEL maintainer="https://github.com/prowler-cloud/prowler" -# Update system dependencies +# Update system dependencies and install essential tools #hadolint ignore=DL3018 -RUN apk --no-cache upgrade && apk --no-cache add curl +RUN apk --no-cache upgrade && apk --no-cache add curl git # Create nonroot user RUN mkdir -p /home/prowler && \ @@ -13,18 +13,17 @@ RUN mkdir -p /home/prowler && \ chown -R prowler:prowler /home/prowler USER prowler -# Copy necessary files +# Copy necessary files WORKDIR /home/prowler COPY prowler/ /home/prowler/prowler/ COPY dashboard/ /home/prowler/dashboard/ COPY pyproject.toml /home/prowler COPY README.md /home/prowler -# Install dependencies +# Install Python dependencies ENV HOME='/home/prowler' ENV PATH="$HOME/.local/bin:$PATH" -#hadolint ignore=DL3013 -RUN pip install --no-cache-dir --upgrade pip && \ +RUN pip install --no-cache-dir --upgrade pip setuptools wheel && \ pip install --no-cache-dir . # Remove deprecated dash dependencies diff --git a/contrib/aws/multi-account-securityhub/README.md b/contrib/aws/multi-account-securityhub/README.md index dd28e6b9f9..c34fe98cc8 100644 --- a/contrib/aws/multi-account-securityhub/README.md +++ b/contrib/aws/multi-account-securityhub/README.md @@ -12,7 +12,11 @@ Originally based on [org-multi-account](https://github.com/prowler-cloud/prowler ## Architecture Explanation - The solution is designed to be very simple. Prowler is run via an ECS Task definition that launches a single Fargate container. This Task Definition is executed on a schedule using an EventBridge Rule. +The solution is designed to be very simple. Prowler is run via an ECS Task definition that launches a single Fargate container. This Task Definition is executed on a schedule using an EventBridge Rule. + +## Prerequisites + +This solution assumes that you have a VPC architecture with two redundant subnets that can reach the AWS API endpoints (e.g. PrivateLink, NAT Gateway, etc.). ## CloudFormation Templates @@ -59,9 +63,9 @@ The logs that are generated and sent to Cloudwatch are error logs, and assessmen ## Instructions 1. Create a Private ECR Repository in the account that will host the Prowler container. The Audit account is recommended, but any account can be used. - 2. Configure the .awsvariables file. Note the ROLE name chosen as it will be the CrossAccountRole. - 3. Follow the steps from "View Push Commands" to build and upload the container image. You need to have Docker and AWS CLI installed, and use the cli to login to the account first. After upload note the Image URI, as it is required for the CF-Prowler-ECS template. - 4. Make sure SecurityHub is enabled in every account in AWS Organizations, and that the SecurityHub integration is enabled as explained in [Prowler - Security Hub Integration](https://github.com/prowler-cloud/prowler#security-hub-integration) + 2. Configure the .awsvariables file. Note the ROLE name chosen as it will be the CrossAccountRole. + 3. Follow the steps from "View Push Commands" to build and upload the container image. Substitute step 2 with the build command provided in the Dockerfile. You need to have Docker and AWS CLI installed, and use the cli to login to the account first. After upload note the Image URI, as it is required for the CF-Prowler-ECS template. Ensure that you pay attention to the architecture while performing the docker build command. A common mistake is not specifying the architecture and then building on Apple silicon. Your task will fail with *exec /home/prowler/.local/bin/prowler: exec format error*. + 4. Make sure SecurityHub is enabled in every account in AWS Organizations, and that the SecurityHub integration is enabled as explained in [Prowler - Security Hub Integration](https://github.com/prowler-cloud/prowler#security-hub-integration) 5. Deploy **CF-Prowler-CrossAccountRole.yml** in the Master Account as a single stack. You will have to choose the CrossAccountRole name (ProwlerXA-Role by default) and the ProwlerTaskRoleName (ProwlerECSTask-Role by default) 6. Deploy **CF-Prowler-CrossAccountRole.yml** in every Member Account as a StackSet. Choose the same CrossAccountName and ProwlerTaskRoleName as the previous step. 7. Deploy **CF-Prowler-IAM.yml** in the account that will host the Prowler container (the same from step 1). The following template parameters must be provided: @@ -91,4 +95,4 @@ If you permission find errors in the CloudWatch logs, the culprit might be a [Se ## Upgrading Prowler Prowler version is controlled by the PROWLERVER argument in the Dockerfile, change it to the desired version and follow the ECR Push Commands to update the container image. -Old images can be deleted from the ECR Repository after the new image is confirmed to work. They will show as "untagged" as only one image can hold the "latest" tag. +Old images can be deleted from the ECR Repository after the new image is confirmed to work. They will show as "untagged" as only one image can hold the "latest" tag. diff --git a/contrib/aws/multi-account-securityhub/run-prowler-securityhub.sh b/contrib/aws/multi-account-securityhub/run-prowler-securityhub.sh index bb2ced55c5..51fef78f77 100755 --- a/contrib/aws/multi-account-securityhub/run-prowler-securityhub.sh +++ b/contrib/aws/multi-account-securityhub/run-prowler-securityhub.sh @@ -68,7 +68,7 @@ for accountId in ${ACCOUNTS_IN_ORGS}; do # Run Prowler echo -e "Assessing AWS Account: ${accountId}, using Role: ${ROLE} on $(date)" # Pipe stdout to /dev/null to reduce unnecessary Cloudwatch logs - prowler aws -R arn:"${PARTITION}":iam::"${accountId}":role/"${ROLE}" -q -S -f "${REGION}" > /dev/null + prowler aws -R arn:"${PARTITION}":iam::"${accountId}":role/"${ROLE}" --security-hub --send-sh-only-fails -f "${REGION}" > /dev/null TOTAL_SEC=$((SECONDS - START_TIME)) printf "Completed AWS Account: ${accountId} in %02dh:%02dm:%02ds" $((TOTAL_SEC / 3600)) $((TOTAL_SEC % 3600 / 60)) $((TOTAL_SEC % 60)) echo "" diff --git a/contrib/aws/multi-account-securityhub/templates/CF-Prowler-CrossAccountRole.yml b/contrib/aws/multi-account-securityhub/templates/CF-Prowler-CrossAccountRole.yml index f7b569a0af..03d5b134fc 100644 --- a/contrib/aws/multi-account-securityhub/templates/CF-Prowler-CrossAccountRole.yml +++ b/contrib/aws/multi-account-securityhub/templates/CF-Prowler-CrossAccountRole.yml @@ -60,24 +60,42 @@ Resources: Effect: Allow Resource: "*" Action: - - ds:ListAuthorizedApplications + - account:Get* + - appstream:Describe* + - appstream:List* + - backup:List* + - cloudtrail:GetInsightSelectors + - codeartifact:List* + - codebuild:BatchGet* + - cognito-idp:GetUserPoolMfaConfig + - dlm:Get* + - drs:Describe* + - ds:Describe* + - ds:Get* + - ds:List* + - dynamodb:GetResourcePolicy - ec2:GetEbsEncryptionByDefault + - ec2:GetSnapshotBlockPublicAccessState + - ec2:GetInstanceMetadataDefaults - ecr:Describe* + - ecr:GetRegistryScanningConfiguration - elasticfilesystem:DescribeBackupPolicy - glue:GetConnections - - glue:GetSecurityConfiguration + - glue:GetSecurityConfiguration* - glue:SearchTables - - lambda:GetFunction + - lambda:GetFunction* + - logs:FilterLogEvents + - lightsail:GetRelationalDatabases + - macie2:GetMacieSession - s3:GetAccountPublicAccessBlock - shield:DescribeProtection - shield:GetSubscriptionState - ssm:GetDocument + - ssm-incidents:List* - support:Describe* - tag:GetTagKeys - - PolicyName: Prowler-Security-Hub - PolicyDocument: - Version: 2012-10-17 - Statement: + - wellarchitected:List* + - Sid: AllowProwlerSecurityHub Effect: Allow Resource: "*" diff --git a/contrib/aws/multi-account-securityhub/templates/CF-Prowler-ECS.yml b/contrib/aws/multi-account-securityhub/templates/CF-Prowler-ECS.yml index 05b476f738..137b40d9a3 100644 --- a/contrib/aws/multi-account-securityhub/templates/CF-Prowler-ECS.yml +++ b/contrib/aws/multi-account-securityhub/templates/CF-Prowler-ECS.yml @@ -62,7 +62,7 @@ Resources: awslogs-stream-prefix: ecs Cpu: 1024 ExecutionRoleArn: !Ref ECSExecutionRole - Memory: 2048 + Memory: 8192 NetworkMode: awsvpc TaskRoleArn: !Ref ProwlerTaskRole Family: SecurityHubProwlerTask diff --git a/contrib/aws/multi-account-securityhub/templates/CF-Prowler-IAM.yml b/contrib/aws/multi-account-securityhub/templates/CF-Prowler-IAM.yml index 9f9535d26b..3ce87bf1ab 100644 --- a/contrib/aws/multi-account-securityhub/templates/CF-Prowler-IAM.yml +++ b/contrib/aws/multi-account-securityhub/templates/CF-Prowler-IAM.yml @@ -97,9 +97,15 @@ Outputs: ECSExecutionRoleARN: Description: ARN of the ECS Task Execution Role Value: !GetAtt ECSExecutionRole.Arn + Export: + Name: ECSExecutionRoleArn ProwlerTaskRoleARN: Description: ARN of the ECS Prowler Task Role Value: !GetAtt ProwlerTaskRole.Arn + Export: + Name: ProwlerTaskRoleArn ECSEventRoleARN: Description: ARN of the Eventbridge Task Role Value: !GetAtt ECSEventRole.Arn + Export: + Name: ECSEventRoleARN diff --git a/poetry.lock b/poetry.lock index 280f600a4c..57bea15051 100644 --- a/poetry.lock +++ b/poetry.lock @@ -758,17 +758,17 @@ files = [ [[package]] name = "boto3" -version = "1.35.28" +version = "1.35.29" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" files = [ - {file = "boto3-1.35.28-py3-none-any.whl", hash = "sha256:dc088b86a14f17d3cd2e96915c6ccfd31bce640dfe9180df579ed311bc6bf0fc"}, - {file = "boto3-1.35.28.tar.gz", hash = "sha256:8960fc458b9ba3c8a9890a607c31cee375db821f39aefaec9ff638248e81644a"}, + {file = "boto3-1.35.29-py3-none-any.whl", hash = "sha256:2244044cdfa8ac345d7400536dc15a4824835e7ec5c55bc267e118af66bb27db"}, + {file = "boto3-1.35.29.tar.gz", hash = "sha256:7bbb1ee649e09e956952285782cfdebd7e81fc78384f48dfab3d66c6eaf3f63f"}, ] [package.dependencies] -botocore = ">=1.35.28,<1.36.0" +botocore = ">=1.35.29,<1.36.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -1302,15 +1302,13 @@ wrapt = ">=1.10,<2" dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] [[package]] -name = "detect-secrets" +name = "detect_secrets" version = "1.5.0" description = "Tool for detecting secrets in the codebase" optional = false python-versions = "*" -files = [ - {file = "detect_secrets-1.5.0-py3-none-any.whl", hash = "sha256:e24e7b9b5a35048c313e983f76c4bd09dad89f045ff059e354f9943bf45aa060"}, - {file = "detect_secrets-1.5.0.tar.gz", hash = "sha256:6bb46dcc553c10df51475641bb30fd69d25645cc12339e46c824c1e0c388898a"}, -] +files = [] +develop = false [package.dependencies] pyyaml = "*" @@ -1320,6 +1318,12 @@ requests = "*" gibberish = ["gibberish-detector"] word-list = ["pyahocorasick"] +[package.source] +type = "git" +url = "https://github.com/Yelp/detect-secrets.git" +reference = "master" +resolved_reference = "462720710ec337300fab2b4f2290949c7ee141eb" + [[package]] name = "dill" version = "0.3.9" @@ -1410,13 +1414,13 @@ files = [ [[package]] name = "email-validator" -version = "2.1.1" +version = "2.2.0" description = "A robust email address syntax and deliverability validation library." optional = false python-versions = ">=3.8" files = [ - {file = "email_validator-2.1.1-py3-none-any.whl", hash = "sha256:97d882d174e2a65732fb43bfce81a3a834cbc1bde8bf419e30ef5ea976370a05"}, - {file = "email_validator-2.1.1.tar.gz", hash = "sha256:200a70680ba08904be6d1eef729205cc0d687634399a5924d842533efb824b84"}, + {file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"}, + {file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"}, ] [package.dependencies] @@ -2594,13 +2598,13 @@ test = ["pytest", "pytest-cov"] [[package]] name = "moto" -version = "5.0.15" +version = "5.0.16" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "moto-5.0.15-py2.py3-none-any.whl", hash = "sha256:fa1e92ffb55dbfb9fa92a2115a88c32481b75aa3fbd24075d1f29af2f9becffa"}, - {file = "moto-5.0.15.tar.gz", hash = "sha256:57aa8c2af417cc64a0ddfe63e5bcd1ada90f5079b73cdd1f74c4e9fb30a1a7e6"}, + {file = "moto-5.0.16-py2.py3-none-any.whl", hash = "sha256:4ce1f34830307f7b3d553d77a7ef26066ab3b70006203d4226b048c9d11a3be4"}, + {file = "moto-5.0.16.tar.gz", hash = "sha256:f4afb176a964cd7a70da9bc5e053d43109614ce3cab26044bcbb53610435dff4"}, ] [package.dependencies] @@ -3463,18 +3467,19 @@ test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] [[package]] name = "py-ocsf-models" -version = "0.1.1" +version = "0.2.0" description = "This is a Python implementation of the OCSF models. The models are used to represent the data of the OCSF Schema defined in https://schema.ocsf.io/." optional = false python-versions = "<3.13,>=3.9" files = [ - {file = "py_ocsf_models-0.1.1-py3-none-any.whl", hash = "sha256:c6ea465fda85470b938a48da65b1f19664f6d83820ebe849ef5551094e6768de"}, - {file = "py_ocsf_models-0.1.1.tar.gz", hash = "sha256:b0f2d4495a2596793f75e61a1ba218edea3a2d17a2b9911d46ee0fa623cc657b"}, + {file = "py_ocsf_models-0.2.0-py3-none-any.whl", hash = "sha256:ac75fd21077694b343ebaad3479194db113c274879b114277560ff287d5cd7b5"}, + {file = "py_ocsf_models-0.2.0.tar.gz", hash = "sha256:3e12648d05329e6776a0e6b1ffea87a3eb60aa7d8cb2c4afd69e5724f443ce03"}, ] [package.dependencies] -email-validator = "2.1.1" -pydantic = "1.10.15" +cryptography = "43.0.1" +email-validator = "2.2.0" +pydantic = "1.10.18" [[package]] name = "py-partiql-parser" @@ -3539,47 +3544,54 @@ files = [ [[package]] name = "pydantic" -version = "1.10.15" +version = "1.10.18" description = "Data validation and settings management using python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:22ed12ee588b1df028a2aa5d66f07bf8f8b4c8579c2e96d5a9c1f96b77f3bb55"}, - {file = "pydantic-1.10.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:75279d3cac98186b6ebc2597b06bcbc7244744f6b0b44a23e4ef01e5683cc0d2"}, - {file = "pydantic-1.10.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50f1666a9940d3d68683c9d96e39640f709d7a72ff8702987dab1761036206bb"}, - {file = "pydantic-1.10.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82790d4753ee5d00739d6cb5cf56bceb186d9d6ce134aca3ba7befb1eedbc2c8"}, - {file = "pydantic-1.10.15-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d207d5b87f6cbefbdb1198154292faee8017d7495a54ae58db06762004500d00"}, - {file = "pydantic-1.10.15-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e49db944fad339b2ccb80128ffd3f8af076f9f287197a480bf1e4ca053a866f0"}, - {file = "pydantic-1.10.15-cp310-cp310-win_amd64.whl", hash = "sha256:d3b5c4cbd0c9cb61bbbb19ce335e1f8ab87a811f6d589ed52b0254cf585d709c"}, - {file = "pydantic-1.10.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c3d5731a120752248844676bf92f25a12f6e45425e63ce22e0849297a093b5b0"}, - {file = "pydantic-1.10.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c365ad9c394f9eeffcb30a82f4246c0006417f03a7c0f8315d6211f25f7cb654"}, - {file = "pydantic-1.10.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3287e1614393119c67bd4404f46e33ae3be3ed4cd10360b48d0a4459f420c6a3"}, - {file = "pydantic-1.10.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be51dd2c8596b25fe43c0a4a59c2bee4f18d88efb8031188f9e7ddc6b469cf44"}, - {file = "pydantic-1.10.15-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6a51a1dd4aa7b3f1317f65493a182d3cff708385327c1c82c81e4a9d6d65b2e4"}, - {file = "pydantic-1.10.15-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4e316e54b5775d1eb59187f9290aeb38acf620e10f7fd2f776d97bb788199e53"}, - {file = "pydantic-1.10.15-cp311-cp311-win_amd64.whl", hash = "sha256:0d142fa1b8f2f0ae11ddd5e3e317dcac060b951d605fda26ca9b234b92214986"}, - {file = "pydantic-1.10.15-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7ea210336b891f5ea334f8fc9f8f862b87acd5d4a0cbc9e3e208e7aa1775dabf"}, - {file = "pydantic-1.10.15-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3453685ccd7140715e05f2193d64030101eaad26076fad4e246c1cc97e1bb30d"}, - {file = "pydantic-1.10.15-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bea1f03b8d4e8e86702c918ccfd5d947ac268f0f0cc6ed71782e4b09353b26f"}, - {file = "pydantic-1.10.15-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:005655cabc29081de8243126e036f2065bd7ea5b9dff95fde6d2c642d39755de"}, - {file = "pydantic-1.10.15-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:af9850d98fc21e5bc24ea9e35dd80a29faf6462c608728a110c0a30b595e58b7"}, - {file = "pydantic-1.10.15-cp37-cp37m-win_amd64.whl", hash = "sha256:d31ee5b14a82c9afe2bd26aaa405293d4237d0591527d9129ce36e58f19f95c1"}, - {file = "pydantic-1.10.15-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5e09c19df304b8123938dc3c53d3d3be6ec74b9d7d0d80f4f4b5432ae16c2022"}, - {file = "pydantic-1.10.15-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7ac9237cd62947db00a0d16acf2f3e00d1ae9d3bd602b9c415f93e7a9fc10528"}, - {file = "pydantic-1.10.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:584f2d4c98ffec420e02305cf675857bae03c9d617fcfdc34946b1160213a948"}, - {file = "pydantic-1.10.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbc6989fad0c030bd70a0b6f626f98a862224bc2b1e36bfc531ea2facc0a340c"}, - {file = "pydantic-1.10.15-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d573082c6ef99336f2cb5b667b781d2f776d4af311574fb53d908517ba523c22"}, - {file = "pydantic-1.10.15-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6bd7030c9abc80134087d8b6e7aa957e43d35714daa116aced57269a445b8f7b"}, - {file = "pydantic-1.10.15-cp38-cp38-win_amd64.whl", hash = "sha256:3350f527bb04138f8aff932dc828f154847fbdc7a1a44c240fbfff1b57f49a12"}, - {file = "pydantic-1.10.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:51d405b42f1b86703555797270e4970a9f9bd7953f3990142e69d1037f9d9e51"}, - {file = "pydantic-1.10.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a980a77c52723b0dc56640ced396b73a024d4b74f02bcb2d21dbbac1debbe9d0"}, - {file = "pydantic-1.10.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67f1a1fb467d3f49e1708a3f632b11c69fccb4e748a325d5a491ddc7b5d22383"}, - {file = "pydantic-1.10.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:676ed48f2c5bbad835f1a8ed8a6d44c1cd5a21121116d2ac40bd1cd3619746ed"}, - {file = "pydantic-1.10.15-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:92229f73400b80c13afcd050687f4d7e88de9234d74b27e6728aa689abcf58cc"}, - {file = "pydantic-1.10.15-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2746189100c646682eff0bce95efa7d2e203420d8e1c613dc0c6b4c1d9c1fde4"}, - {file = "pydantic-1.10.15-cp39-cp39-win_amd64.whl", hash = "sha256:394f08750bd8eaad714718812e7fab615f873b3cdd0b9d84e76e51ef3b50b6b7"}, - {file = "pydantic-1.10.15-py3-none-any.whl", hash = "sha256:28e552a060ba2740d0d2aabe35162652c1459a0b9069fe0db7f4ee0e18e74d58"}, - {file = "pydantic-1.10.15.tar.gz", hash = "sha256:ca832e124eda231a60a041da4f013e3ff24949d94a01154b137fc2f2a43c3ffb"}, + {file = "pydantic-1.10.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e405ffcc1254d76bb0e760db101ee8916b620893e6edfbfee563b3c6f7a67c02"}, + {file = "pydantic-1.10.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e306e280ebebc65040034bff1a0a81fd86b2f4f05daac0131f29541cafd80b80"}, + {file = "pydantic-1.10.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11d9d9b87b50338b1b7de4ebf34fd29fdb0d219dc07ade29effc74d3d2609c62"}, + {file = "pydantic-1.10.18-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b661ce52c7b5e5f600c0c3c5839e71918346af2ef20062705ae76b5c16914cab"}, + {file = "pydantic-1.10.18-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c20f682defc9ef81cd7eaa485879ab29a86a0ba58acf669a78ed868e72bb89e0"}, + {file = "pydantic-1.10.18-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c5ae6b7c8483b1e0bf59e5f1843e4fd8fd405e11df7de217ee65b98eb5462861"}, + {file = "pydantic-1.10.18-cp310-cp310-win_amd64.whl", hash = "sha256:74fe19dda960b193b0eb82c1f4d2c8e5e26918d9cda858cbf3f41dd28549cb70"}, + {file = "pydantic-1.10.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:72fa46abace0a7743cc697dbb830a41ee84c9db8456e8d77a46d79b537efd7ec"}, + {file = "pydantic-1.10.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ef0fe7ad7cbdb5f372463d42e6ed4ca9c443a52ce544472d8842a0576d830da5"}, + {file = "pydantic-1.10.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a00e63104346145389b8e8f500bc6a241e729feaf0559b88b8aa513dd2065481"}, + {file = "pydantic-1.10.18-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae6fa2008e1443c46b7b3a5eb03800121868d5ab6bc7cda20b5df3e133cde8b3"}, + {file = "pydantic-1.10.18-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9f463abafdc92635da4b38807f5b9972276be7c8c5121989768549fceb8d2588"}, + {file = "pydantic-1.10.18-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3445426da503c7e40baccefb2b2989a0c5ce6b163679dd75f55493b460f05a8f"}, + {file = "pydantic-1.10.18-cp311-cp311-win_amd64.whl", hash = "sha256:467a14ee2183bc9c902579bb2f04c3d3dac00eff52e252850509a562255b2a33"}, + {file = "pydantic-1.10.18-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:efbc8a7f9cb5fe26122acba1852d8dcd1e125e723727c59dcd244da7bdaa54f2"}, + {file = "pydantic-1.10.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:24a4a159d0f7a8e26bf6463b0d3d60871d6a52eac5bb6a07a7df85c806f4c048"}, + {file = "pydantic-1.10.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b74be007703547dc52e3c37344d130a7bfacca7df112a9e5ceeb840a9ce195c7"}, + {file = "pydantic-1.10.18-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcb20d4cb355195c75000a49bb4a31d75e4295200df620f454bbc6bdf60ca890"}, + {file = "pydantic-1.10.18-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:46f379b8cb8a3585e3f61bf9ae7d606c70d133943f339d38b76e041ec234953f"}, + {file = "pydantic-1.10.18-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cbfbca662ed3729204090c4d09ee4beeecc1a7ecba5a159a94b5a4eb24e3759a"}, + {file = "pydantic-1.10.18-cp312-cp312-win_amd64.whl", hash = "sha256:c6d0a9f9eccaf7f438671a64acf654ef0d045466e63f9f68a579e2383b63f357"}, + {file = "pydantic-1.10.18-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3d5492dbf953d7d849751917e3b2433fb26010d977aa7a0765c37425a4026ff1"}, + {file = "pydantic-1.10.18-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe734914977eed33033b70bfc097e1baaffb589517863955430bf2e0846ac30f"}, + {file = "pydantic-1.10.18-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15fdbe568beaca9aacfccd5ceadfb5f1a235087a127e8af5e48df9d8a45ae85c"}, + {file = "pydantic-1.10.18-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c3e742f62198c9eb9201781fbebe64533a3bbf6a76a91b8d438d62b813079dbc"}, + {file = "pydantic-1.10.18-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:19a3bd00b9dafc2cd7250d94d5b578edf7a0bd7daf102617153ff9a8fa37871c"}, + {file = "pydantic-1.10.18-cp37-cp37m-win_amd64.whl", hash = "sha256:2ce3fcf75b2bae99aa31bd4968de0474ebe8c8258a0110903478bd83dfee4e3b"}, + {file = "pydantic-1.10.18-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:335a32d72c51a313b33fa3a9b0fe283503272ef6467910338e123f90925f0f03"}, + {file = "pydantic-1.10.18-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:34a3613c7edb8c6fa578e58e9abe3c0f5e7430e0fc34a65a415a1683b9c32d9a"}, + {file = "pydantic-1.10.18-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9ee4e6ca1d9616797fa2e9c0bfb8815912c7d67aca96f77428e316741082a1b"}, + {file = "pydantic-1.10.18-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23e8ec1ce4e57b4f441fc91e3c12adba023fedd06868445a5b5f1d48f0ab3682"}, + {file = "pydantic-1.10.18-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:44ae8a3e35a54d2e8fa88ed65e1b08967a9ef8c320819a969bfa09ce5528fafe"}, + {file = "pydantic-1.10.18-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5389eb3b48a72da28c6e061a247ab224381435256eb541e175798483368fdd3"}, + {file = "pydantic-1.10.18-cp38-cp38-win_amd64.whl", hash = "sha256:069b9c9fc645474d5ea3653788b544a9e0ccd3dca3ad8c900c4c6eac844b4620"}, + {file = "pydantic-1.10.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:80b982d42515632eb51f60fa1d217dfe0729f008e81a82d1544cc392e0a50ddf"}, + {file = "pydantic-1.10.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:aad8771ec8dbf9139b01b56f66386537c6fe4e76c8f7a47c10261b69ad25c2c9"}, + {file = "pydantic-1.10.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941a2eb0a1509bd7f31e355912eb33b698eb0051730b2eaf9e70e2e1589cae1d"}, + {file = "pydantic-1.10.18-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65f7361a09b07915a98efd17fdec23103307a54db2000bb92095457ca758d485"}, + {file = "pydantic-1.10.18-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6951f3f47cb5ca4da536ab161ac0163cab31417d20c54c6de5ddcab8bc813c3f"}, + {file = "pydantic-1.10.18-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7a4c5eec138a9b52c67f664c7d51d4c7234c5ad65dd8aacd919fb47445a62c86"}, + {file = "pydantic-1.10.18-cp39-cp39-win_amd64.whl", hash = "sha256:49e26c51ca854286bffc22b69787a8d4063a62bf7d83dc21d44d2ff426108518"}, + {file = "pydantic-1.10.18-py3-none-any.whl", hash = "sha256:06a189b81ffc52746ec9c8c007f16e5167c8b0a696e1a726369327e3db7b2a82"}, + {file = "pydantic-1.10.18.tar.gz", hash = "sha256:baebdff1907d1d96a139c25136a9bb7d17e118f133a76a2ef3b845e831e3403a"}, ] [package.dependencies] @@ -4302,24 +4314,24 @@ python-versions = ">=3.6" files = [ {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:d92f81886165cb14d7b067ef37e142256f1c6a90a65cd156b063a43da1708cfd"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:b5edda50e5e9e15e54a6a8a0070302b00c518a9d32accc2346ad6c984aacd279"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:7048c338b6c86627afb27faecf418768acb6331fc24cfa56c93e8c9780f815fa"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win32.whl", hash = "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa"}, @@ -4327,7 +4339,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:3fcc54cb0c8b811ff66082de1680b4b14cf8a81dce0d4fbf665c2265a81e07a1"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3"}, @@ -4335,7 +4347,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:665f58bfd29b167039f714c6998178d27ccd83984084c286110ef26b230f259f"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d"}, @@ -4343,7 +4355,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9eb5dee2772b0f704ca2e45b1713e4e5198c18f515b52743576d196348f374d3"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880"}, @@ -5060,4 +5072,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.13" -content-hash = "6ab3e45b4316275b0c26103a5dff489527d927bb4d9e4ff6a847849cb106d837" +content-hash = "0b367fa80501022efe43dc1beaa7f3da278fb64ffaddece72a0e88b09a0e53a2" diff --git a/prowler/config/config.py b/prowler/config/config.py index 4eb9b07223..994f51ea7c 100644 --- a/prowler/config/config.py +++ b/prowler/config/config.py @@ -11,7 +11,7 @@ from prowler.lib.logger import logger timestamp = datetime.today() timestamp_utc = datetime.now(timezone.utc).replace(tzinfo=timezone.utc) -prowler_version = "4.4.0" +prowler_version = "4.5.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 a577f052fc..1e93bb87fb 100644 --- a/prowler/config/config.yaml +++ b/prowler/config/config.yaml @@ -57,6 +57,11 @@ aws: 8088, ] + # AWS ECS Configuration + # aws.ecs_service_fargate_latest_platform_version + fargate_linux_latest_version: "1.4.0" + fargate_windows_latest_version: "1.0.0" + # AWS VPC Configuration (vpc_endpoint_connections_trust_boundaries, vpc_endpoint_services_allowed_principals_trust_boundaries) # AWS SSM Configuration (aws.ssm_documents_set_as_public) # Single account environment: No action required. The AWS account number will be automatically added by the checks. @@ -330,8 +335,8 @@ aws: elbv2_min_azs: 2 - # Known secrets to ignore on detection - # this will include a list of regex patterns to ignore on detection + # AWS Secrets Configuration + # Patterns to ignore in the secrets checks secrets_ignore_patterns: [] # Azure Configuration diff --git a/prowler/lib/outputs/ocsf/ocsf.py b/prowler/lib/outputs/ocsf/ocsf.py index b948185b8c..b9eb78ce63 100644 --- a/prowler/lib/outputs/ocsf/ocsf.py +++ b/prowler/lib/outputs/ocsf/ocsf.py @@ -9,7 +9,6 @@ from py_ocsf_models.events.findings.detection_finding import ( from py_ocsf_models.events.findings.finding import ActivityID, FindingInformation from py_ocsf_models.objects.account import Account, TypeID from py_ocsf_models.objects.cloud import Cloud -from py_ocsf_models.objects.container import Container from py_ocsf_models.objects.group import Group from py_ocsf_models.objects.metadata import Metadata from py_ocsf_models.objects.organization import Organization @@ -37,7 +36,7 @@ class OCSF(Output): - transform(findings: List[Finding]) -> None: Transforms the findings into the OCSF Detection Finding format. - batch_write_data_to_file() -> None: Writes the findings to a file using the OCSF Detection Finding format using the `Output._file_descriptor`. - get_account_type_id_by_provider(provider: str) -> TypeID: Returns the TypeID based on the provider. - - get_finding_status_id(status: str, muted: bool) -> StatusID: Returns the StatusID based on the status and muted values. + - get_finding_status_id(muted: bool) -> StatusID: Returns the StatusID based on the muted value. References: - OCSF: https://schema.ocsf.io/1.2.0/classes/detection_finding @@ -59,21 +58,24 @@ class OCSF(Output): finding_severity = getattr( SeverityID, finding.severity.capitalize(), SeverityID.Unknown ) - finding_status = self.get_finding_status_id( - finding.status, finding.muted - ) + finding_status = self.get_finding_status_id(finding.muted) detection_finding = DetectionFinding( + message=finding.status_extended, activity_id=finding_activity.value, activity_name=finding_activity.name, finding_info=FindingInformation( - created_time=finding.timestamp, + created_time_dt=finding.timestamp, + created_time=int(finding.timestamp.timestamp()), desc=finding.description, title=finding.check_title, uid=finding.finding_uid, + name=finding.resource_name, product_uid="prowler", + types=[finding.check_type], ), - event_time=finding.timestamp, + time_dt=finding.timestamp, + time=int(finding.timestamp.timestamp()), remediation=Remediation( desc=finding.remediation_recommendation_text, references=list( @@ -96,31 +98,51 @@ class OCSF(Output): status_code=finding.status, status_detail=finding.status_extended, risk_details=finding.risk, - resources=[ - ResourceDetails( - labels=unroll_dict_to_list(finding.resource_tags), - name=finding.resource_name, - uid=finding.resource_uid, - group=Group(name=finding.service_name), - type=finding.resource_type, - # TODO: this should be included only if using the Cloud profile - cloud_partition=finding.partition, - region=finding.region, - data={"details": finding.resource_details}, - ) - ], + resources=( + [ + ResourceDetails( + labels=unroll_dict_to_list(finding.resource_tags), + name=finding.resource_name, + uid=finding.resource_uid, + group=Group(name=finding.service_name), + type=finding.resource_type, + # TODO: this should be included only if using the Cloud profile + cloud_partition=finding.partition, + region=finding.region, + data={"details": finding.resource_details}, + ) + ] + if finding.provider != "kubernetes" + else [ + ResourceDetails( + labels=unroll_dict_to_list(finding.resource_tags), + name=finding.resource_name, + uid=finding.resource_uid, + group=Group(name=finding.service_name), + type=finding.resource_type, + data={"details": finding.resource_details}, + namespace=finding.region.replace("namespace: ", ""), + ) + ] + ), metadata=Metadata( event_code=finding.check_id, product=Product( + uid="prowler", name="Prowler", vendor_name="Prowler", version=finding.prowler_version, ), + profiles=( + ["cloud", "datetime"] + if finding.provider != "kubernetes" + else ["container", "datetime"] + ), + tenant_uid=finding.account_organization_uid, ), type_uid=DetectionFindingTypeID.Create, - type_name=DetectionFindingTypeID.Create.name, + type_name=f"Detection Finding: {DetectionFindingTypeID.Create.name}", unmapped={ - "check_type": finding.check_type, "related_url": finding.related_url, "categories": finding.categories, "depends_on": finding.depends_on, @@ -129,26 +151,19 @@ class OCSF(Output): "compliance": finding.compliance, }, ) - - if finding.provider == "kubernetes": - detection_finding.container = Container( - name=finding.resource_name, - uid=finding.resource_uid, - ) - # TODO: Get the PID of the namespace (we only have the name of the namespace) - # detection_finding.namespace_pid=, - else: + if finding.provider != "kubernetes": detection_finding.cloud = Cloud( account=Account( name=finding.account_name, type_id=cloud_account_type.value, - type=cloud_account_type.name, + type=cloud_account_type.name.replace("_", " "), uid=finding.account_uid, labels=unroll_dict_to_list(finding.account_tags), ), org=Organization( uid=finding.account_organization_uid, name=finding.account_organization_name, + # TODO: add the org unit id and name ), provider=finding.provider, region=finding.region, @@ -208,20 +223,17 @@ class OCSF(Output): return type_id @staticmethod - def get_finding_status_id(status: str, muted: bool) -> StatusID: + def get_finding_status_id(muted: bool) -> StatusID: """ - Returns the StatusID based on the status and muted values. + Returns the StatusID based on the muted value. Args: - status (str): The status value muted (bool): The muted value Returns: - StatusID: The StatusID based on the status and muted values + StatusID: The StatusID based on the muted value """ - status_id = StatusID.Other - if status == "FAIL": - status_id = StatusID.New + status_id = StatusID.New if muted: status_id = StatusID.Suppressed return status_id diff --git a/prowler/lib/scan/scan.py b/prowler/lib/scan/scan.py index 48cd9335fb..27d2005aa7 100644 --- a/prowler/lib/scan/scan.py +++ b/prowler/lib/scan/scan.py @@ -1,6 +1,8 @@ +import datetime from typing import Generator from prowler.lib.check.check import execute, import_check, update_audit_metadata +from prowler.lib.check.utils import recover_checks_from_provider from prowler.lib.logger import logger from prowler.lib.outputs.finding import Finding from prowler.providers.common.models import Audit_Metadata @@ -18,8 +20,9 @@ class Scan: _service_checks_completed: dict[str, set[str]] _progress: float = 0.0 _findings: list = [] + _duration: int = 0 - def __init__(self, provider: Provider, checks_to_execute: list[str]): + def __init__(self, provider: Provider, checks_to_execute: list[str] = None): """ Scan is the class that executes the checks and yields the progress and the findings. @@ -29,11 +32,31 @@ class Scan: """ self._provider = provider # Remove duplicated checks and sort them - self._checks_to_execute = sorted(list(set(checks_to_execute))) + self._checks_to_execute = ( + sorted(list(set(checks_to_execute))) + if checks_to_execute + else sorted( + [check[0] for check in recover_checks_from_provider(provider.type)] + ) + ) - self._number_of_checks_to_execute = len(checks_to_execute) + # TODO This should be done depending on the scan args (future feature) + # Discard threat detection checks + if "cloudtrail_threat_detection_enumeration" in self._checks_to_execute: + self._checks_to_execute.remove("cloudtrail_threat_detection_enumeration") + if ( + "cloudtrail_threat_detection_privilege_escalation" + in self._checks_to_execute + ): + self._checks_to_execute.remove( + "cloudtrail_threat_detection_privilege_escalation" + ) - service_checks_to_execute = get_service_checks_to_execute(checks_to_execute) + self._number_of_checks_to_execute = len(self._checks_to_execute) + + service_checks_to_execute = get_service_checks_to_execute( + self._checks_to_execute + ) service_checks_completed = dict() self._service_checks_to_execute = service_checks_to_execute @@ -61,6 +84,10 @@ class Scan: self._number_of_checks_completed / self._number_of_checks_to_execute * 100 ) + @property + def duration(self) -> int: + return self._duration + @property def findings(self) -> list: return self._findings @@ -95,6 +122,8 @@ class Scan: audit_progress=0, ) + start_time = datetime.datetime.now() + for check_name in checks_to_execute: try: # Recover service from check name @@ -149,7 +178,6 @@ class Scan: ] yield self.progress, findings - # If check does not exists in the provider or is from another provider except ModuleNotFoundError: logger.error( @@ -159,6 +187,8 @@ class Scan: logger.error( f"{check_name} - {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + # Update the scan duration when all checks are completed + self._duration = int((datetime.datetime.now() - start_time).total_seconds()) except Exception as error: logger.error( f"{check_name} - {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" diff --git a/prowler/lib/utils/utils.py b/prowler/lib/utils/utils.py index 205c8e3bbd..cbca7defb7 100644 --- a/prowler/lib/utils/utils.py +++ b/prowler/lib/utils/utils.py @@ -133,7 +133,7 @@ def detect_secrets_scan( {"name": "SoftlayerDetector"}, {"name": "SquareOAuthDetector"}, {"name": "StripeDetector"}, - # {"name": "TelegramBotTokenDetector"}, https://github.com/Yelp/detect-secrets/pull/878 + {"name": "TelegramBotTokenDetector"}, {"name": "TwilioKeyDetector"}, ], "filters_used": [ diff --git a/prowler/providers/aws/aws_regions_by_service.json b/prowler/providers/aws/aws_regions_by_service.json index 7b52653059..5432759e8d 100644 --- a/prowler/providers/aws/aws_regions_by_service.json +++ b/prowler/providers/aws/aws_regions_by_service.json @@ -1263,6 +1263,7 @@ "regions": { "aws": [ "ap-northeast-1", + "ap-northeast-2", "ap-south-1", "ap-southeast-1", "ap-southeast-2", @@ -1273,6 +1274,7 @@ "eu-west-3", "sa-east-1", "us-east-1", + "us-east-2", "us-west-2" ], "aws-cn": [], @@ -7520,6 +7522,7 @@ "ap-southeast-2", "ca-central-1", "eu-central-1", + "eu-central-2", "eu-west-1", "eu-west-2", "eu-west-3", @@ -7530,6 +7533,7 @@ ], "aws-cn": [], "aws-us-gov": [ + "us-gov-east-1", "us-gov-west-1" ] } @@ -7691,6 +7695,7 @@ "eu-central-1", "eu-north-1", "eu-south-1", + "eu-south-2", "eu-west-1", "eu-west-2", "eu-west-3", @@ -10975,6 +10980,7 @@ "ap-east-1", "ap-northeast-1", "ap-northeast-2", + "ap-northeast-3", "ap-south-1", "ap-southeast-1", "ap-southeast-2", diff --git a/prowler/providers/aws/services/autoscaling/applicationautoscaling_client.py b/prowler/providers/aws/services/autoscaling/applicationautoscaling_client.py new file mode 100644 index 0000000000..561af71441 --- /dev/null +++ b/prowler/providers/aws/services/autoscaling/applicationautoscaling_client.py @@ -0,0 +1,6 @@ +from prowler.providers.aws.services.autoscaling.autoscaling_service import ( + ApplicationAutoScaling, +) +from prowler.providers.common.provider import Provider + +applicationautoscaling_client = ApplicationAutoScaling(Provider.get_global_provider()) diff --git a/prowler/providers/aws/services/autoscaling/autoscaling_service.py b/prowler/providers/aws/services/autoscaling/autoscaling_service.py index b5665d1c63..93a84340e1 100644 --- a/prowler/providers/aws/services/autoscaling/autoscaling_service.py +++ b/prowler/providers/aws/services/autoscaling/autoscaling_service.py @@ -5,7 +5,6 @@ from prowler.lib.scan_filters.scan_filters import is_resource_filtered from prowler.providers.aws.lib.service.service import AWSService -################## AutoScaling class AutoScaling(AWSService): def __init__(self, provider): # Call AWSService's __init__ @@ -74,6 +73,49 @@ class AutoScaling(AWSService): ) +# Global list for service namespaces needed for Describe Scalable Targets +SERVICE_NAMESPACES = ["dynamodb"] + + +class ApplicationAutoScaling(AWSService): + def __init__(self, provider): + super().__init__("application-autoscaling", provider) + self.scalable_targets = [] + self.__threading_call__(self._describe_scalable_targets) + + def _describe_scalable_targets(self, regional_client): + logger.info("ApplicationAutoScaling - Describing Scalable Targets...") + try: + describe_scalable_targets_paginator = regional_client.get_paginator( + "describe_scalable_targets" + ) + for service_namespace in SERVICE_NAMESPACES: + logger.info(f"Processing ServiceNamespace: {service_namespace}") + for page in describe_scalable_targets_paginator.paginate( + ServiceNamespace=service_namespace + ): + for target in page.get("ScalableTargets", []): + if not self.audit_resources or ( + is_resource_filtered( + target["ScalableTargetARN"], + self.audit_resources, + ) + ): + self.scalable_targets.append( + ScalableTarget( + arn=target.get("ScalableTargetARN", ""), + resource_id=target.get("ResourceId"), + service_namespace=target.get("ServiceNamespace"), + scalable_dimension=target.get("ScalableDimension"), + region=regional_client.region, + ) + ) + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + class LaunchConfiguration(BaseModel): arn: str name: str @@ -88,3 +130,11 @@ class Group(BaseModel): region: str availability_zones: list tags: list = [] + + +class ScalableTarget(BaseModel): + arn: str + resource_id: str + service_namespace: str + scalable_dimension: str + region: str 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 fe6e965be7..be32be559f 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 @@ -40,21 +40,24 @@ class cloudtrail_threat_detection_enumeration(Check): ): event_log = json.loads(event_log["CloudTrailEvent"]) if ( - event_log["userIdentity"]["arn"], - event_log["userIdentity"]["type"], - ) not in potential_enumeration: + "arn" in event_log["userIdentity"] + ): # Ignore event logs without ARN since they are AWS services + if ( + event_log["userIdentity"]["arn"], + event_log["userIdentity"]["type"], + ) not in potential_enumeration: + potential_enumeration[ + ( + event_log["userIdentity"]["arn"], + event_log["userIdentity"]["type"], + ) + ] = set() potential_enumeration[ ( event_log["userIdentity"]["arn"], event_log["userIdentity"]["type"], ) - ] = set() - potential_enumeration[ - ( - event_log["userIdentity"]["arn"], - event_log["userIdentity"]["type"], - ) - ].add(event_name) + ].add(event_name) for aws_identity, actions in potential_enumeration.items(): identity_threshold = round(len(actions) / len(enumeration_actions), 2) 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 b295534f90..a87184c0f1 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 @@ -41,21 +41,24 @@ class cloudtrail_threat_detection_privilege_escalation(Check): ): event_log = json.loads(event_log["CloudTrailEvent"]) if ( - event_log["userIdentity"]["arn"], - event_log["userIdentity"]["type"], - ) not in potential_privilege_escalation: + "arn" in event_log["userIdentity"] + ): # Ignore event logs without ARN since they are AWS services + if ( + event_log["userIdentity"]["arn"], + event_log["userIdentity"]["type"], + ) not in potential_privilege_escalation: + potential_privilege_escalation[ + ( + event_log["userIdentity"]["arn"], + event_log["userIdentity"]["type"], + ) + ] = set() potential_privilege_escalation[ ( event_log["userIdentity"]["arn"], event_log["userIdentity"]["type"], ) - ] = set() - potential_privilege_escalation[ - ( - event_log["userIdentity"]["arn"], - event_log["userIdentity"]["type"], - ) - ].add(event_name) + ].add(event_name) for aws_identity, actions in potential_privilege_escalation.items(): identity_threshold = round( len(actions) / len(privilege_escalation_actions), 2 diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_changes_to_network_acls_alarm_configured/cloudwatch_changes_to_network_acls_alarm_configured.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_changes_to_network_acls_alarm_configured/cloudwatch_changes_to_network_acls_alarm_configured.py index 63905b4c2d..85ac6ca2f1 100644 --- a/prowler/providers/aws/services/cloudwatch/cloudwatch_changes_to_network_acls_alarm_configured/cloudwatch_changes_to_network_acls_alarm_configured.py +++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_changes_to_network_acls_alarm_configured/cloudwatch_changes_to_network_acls_alarm_configured.py @@ -15,24 +15,24 @@ class cloudwatch_changes_to_network_acls_alarm_configured(Check): def execute(self): pattern = r"\$\.eventName\s*=\s*.?CreateNetworkAcl.+\$\.eventName\s*=\s*.?CreateNetworkAclEntry.+\$\.eventName\s*=\s*.?DeleteNetworkAcl.+\$\.eventName\s*=\s*.?DeleteNetworkAclEntry.+\$\.eventName\s*=\s*.?ReplaceNetworkAclEntry.+\$\.eventName\s*=\s*.?ReplaceNetworkAclAssociation.?" findings = [] - if ( - cloudtrail_client.trails is not None - and logs_client.metric_filters is not None - and cloudwatch_client.metric_alarms is not None - ): - report = Check_Report_AWS(self.metadata()) - report.status = "FAIL" - report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." - report.region = logs_client.region - report.resource_id = logs_client.audited_account - report.resource_arn = logs_client.log_group_arn_template - report = check_cloudwatch_log_metric_filter( - pattern, - cloudtrail_client.trails, - logs_client.metric_filters, - cloudwatch_client.metric_alarms, - report, - ) + + report = check_cloudwatch_log_metric_filter( + pattern, + cloudtrail_client.trails, + logs_client.metric_filters, + cloudwatch_client.metric_alarms, + self.metadata(), + ) + + if report is not None: + if report == Check_Report_AWS(self.metadata()): + report.status = "FAIL" + report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." + report.region = logs_client.region + report.resource_id = logs_client.audited_account + report.resource_arn = logs_client.log_group_arn_template + report.resource_tags = [] findings.append(report) + return findings diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_changes_to_network_gateways_alarm_configured/cloudwatch_changes_to_network_gateways_alarm_configured.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_changes_to_network_gateways_alarm_configured/cloudwatch_changes_to_network_gateways_alarm_configured.py index 11de46ec26..b7072981da 100644 --- a/prowler/providers/aws/services/cloudwatch/cloudwatch_changes_to_network_gateways_alarm_configured/cloudwatch_changes_to_network_gateways_alarm_configured.py +++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_changes_to_network_gateways_alarm_configured/cloudwatch_changes_to_network_gateways_alarm_configured.py @@ -15,24 +15,24 @@ class cloudwatch_changes_to_network_gateways_alarm_configured(Check): def execute(self): pattern = r"\$\.eventName\s*=\s*.?CreateCustomerGateway.+\$\.eventName\s*=\s*.?DeleteCustomerGateway.+\$\.eventName\s*=\s*.?AttachInternetGateway.+\$\.eventName\s*=\s*.?CreateInternetGateway.+\$\.eventName\s*=\s*.?DeleteInternetGateway.+\$\.eventName\s*=\s*.?DetachInternetGateway.?" findings = [] - if ( - cloudtrail_client.trails is not None - and logs_client.metric_filters is not None - and cloudwatch_client.metric_alarms is not None - ): - report = Check_Report_AWS(self.metadata()) - report.status = "FAIL" - report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." - report.region = logs_client.region - report.resource_id = logs_client.audited_account - report.resource_arn = logs_client.log_group_arn_template - report = check_cloudwatch_log_metric_filter( - pattern, - cloudtrail_client.trails, - logs_client.metric_filters, - cloudwatch_client.metric_alarms, - report, - ) + + report = check_cloudwatch_log_metric_filter( + pattern, + cloudtrail_client.trails, + logs_client.metric_filters, + cloudwatch_client.metric_alarms, + self.metadata(), + ) + + if report is not None: + if report == Check_Report_AWS(self.metadata()): + report.status = "FAIL" + report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." + report.region = logs_client.region + report.resource_id = logs_client.audited_account + report.resource_arn = logs_client.log_group_arn_template + report.resource_tags = [] findings.append(report) + return findings diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_changes_to_network_route_tables_alarm_configured/cloudwatch_changes_to_network_route_tables_alarm_configured.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_changes_to_network_route_tables_alarm_configured/cloudwatch_changes_to_network_route_tables_alarm_configured.py index 3f1f2d04fb..701cad8da1 100644 --- a/prowler/providers/aws/services/cloudwatch/cloudwatch_changes_to_network_route_tables_alarm_configured/cloudwatch_changes_to_network_route_tables_alarm_configured.py +++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_changes_to_network_route_tables_alarm_configured/cloudwatch_changes_to_network_route_tables_alarm_configured.py @@ -15,24 +15,24 @@ class cloudwatch_changes_to_network_route_tables_alarm_configured(Check): def execute(self): pattern = r"\$\.eventSource\s*=\s*.?ec2.amazonaws.com.+\$\.eventName\s*=\s*.?CreateRoute.+\$\.eventName\s*=\s*.?CreateRouteTable.+\$\.eventName\s*=\s*.?ReplaceRoute.+\$\.eventName\s*=\s*.?ReplaceRouteTableAssociation.+\$\.eventName\s*=\s*.?DeleteRouteTable.+\$\.eventName\s*=\s*.?DeleteRoute.+\$\.eventName\s*=\s*.?DisassociateRouteTable.?" findings = [] - if ( - cloudtrail_client.trails is not None - and logs_client.metric_filters is not None - and cloudwatch_client.metric_alarms is not None - ): - report = Check_Report_AWS(self.metadata()) - report.status = "FAIL" - report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." - report.region = logs_client.region - report.resource_id = logs_client.audited_account - report.resource_arn = logs_client.log_group_arn_template - report = check_cloudwatch_log_metric_filter( - pattern, - cloudtrail_client.trails, - logs_client.metric_filters, - cloudwatch_client.metric_alarms, - report, - ) + + report = check_cloudwatch_log_metric_filter( + pattern, + cloudtrail_client.trails, + logs_client.metric_filters, + cloudwatch_client.metric_alarms, + self.metadata(), + ) + + if report is not None: + if report == Check_Report_AWS(self.metadata()): + report.status = "FAIL" + report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." + report.region = logs_client.region + report.resource_id = logs_client.audited_account + report.resource_arn = logs_client.log_group_arn_template + report.resource_tags = [] findings.append(report) + return findings diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_changes_to_vpcs_alarm_configured/cloudwatch_changes_to_vpcs_alarm_configured.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_changes_to_vpcs_alarm_configured/cloudwatch_changes_to_vpcs_alarm_configured.py index 5a56070828..3b323c6488 100644 --- a/prowler/providers/aws/services/cloudwatch/cloudwatch_changes_to_vpcs_alarm_configured/cloudwatch_changes_to_vpcs_alarm_configured.py +++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_changes_to_vpcs_alarm_configured/cloudwatch_changes_to_vpcs_alarm_configured.py @@ -15,24 +15,24 @@ class cloudwatch_changes_to_vpcs_alarm_configured(Check): def execute(self): pattern = r"\$\.eventName\s*=\s*.?CreateVpc.+\$\.eventName\s*=\s*.?DeleteVpc.+\$\.eventName\s*=\s*.?ModifyVpcAttribute.+\$\.eventName\s*=\s*.?AcceptVpcPeeringConnection.+\$\.eventName\s*=\s*.?CreateVpcPeeringConnection.+\$\.eventName\s*=\s*.?DeleteVpcPeeringConnection.+\$\.eventName\s*=\s*.?RejectVpcPeeringConnection.+\$\.eventName\s*=\s*.?AttachClassicLinkVpc.+\$\.eventName\s*=\s*.?DetachClassicLinkVpc.+\$\.eventName\s*=\s*.?DisableVpcClassicLink.+\$\.eventName\s*=\s*.?EnableVpcClassicLink.?" findings = [] - if ( - cloudtrail_client.trails is not None - and logs_client.metric_filters is not None - and cloudwatch_client.metric_alarms is not None - ): - report = Check_Report_AWS(self.metadata()) - report.status = "FAIL" - report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." - report.region = logs_client.region - report.resource_id = logs_client.audited_account - report.resource_arn = logs_client.log_group_arn_template - report = check_cloudwatch_log_metric_filter( - pattern, - cloudtrail_client.trails, - logs_client.metric_filters, - cloudwatch_client.metric_alarms, - report, - ) + + report = check_cloudwatch_log_metric_filter( + pattern, + cloudtrail_client.trails, + logs_client.metric_filters, + cloudwatch_client.metric_alarms, + self.metadata(), + ) + + if report is not None: + if report == Check_Report_AWS(self.metadata()): + report.status = "FAIL" + report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." + report.region = logs_client.region + report.resource_id = logs_client.audited_account + report.resource_arn = logs_client.log_group_arn_template + report.resource_tags = [] findings.append(report) + return findings diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_cross_account_sharing_disabled/cloudwatch_cross_account_sharing_disabled.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_cross_account_sharing_disabled/cloudwatch_cross_account_sharing_disabled.py index 065a9d569b..5e2d897df2 100644 --- a/prowler/providers/aws/services/cloudwatch/cloudwatch_cross_account_sharing_disabled/cloudwatch_cross_account_sharing_disabled.py +++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_cross_account_sharing_disabled/cloudwatch_cross_account_sharing_disabled.py @@ -16,6 +16,7 @@ class cloudwatch_cross_account_sharing_disabled(Check): if role.name == "CloudWatch-CrossAccountSharingRole": report.resource_arn = role.arn report.resource_id = role.name + report.resource_tags = role.tags report.status = "FAIL" report.status_extended = ( "CloudWatch has allowed cross-account sharing." diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_no_secrets_in_logs/cloudwatch_log_group_no_secrets_in_logs.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_no_secrets_in_logs/cloudwatch_log_group_no_secrets_in_logs.py index f459f7d045..cb583ad35b 100644 --- a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_no_secrets_in_logs/cloudwatch_log_group_no_secrets_in_logs.py +++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_no_secrets_in_logs/cloudwatch_log_group_no_secrets_in_logs.py @@ -24,6 +24,7 @@ class cloudwatch_log_group_no_secrets_in_logs(Check): report.region = log_group.region report.resource_id = log_group.name report.resource_arn = log_group.arn + report.resource_tags = log_group.tags log_group_secrets = [] if log_group.log_streams: for log_stream_name in log_group.log_streams: diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled/cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled/cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled.py index 72079b8597..a195867e39 100644 --- a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled/cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled.py +++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled/cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled.py @@ -17,24 +17,24 @@ class cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_change def execute(self): pattern = r"\$\.eventSource\s*=\s*.?config.amazonaws.com.+\$\.eventName\s*=\s*.?StopConfigurationRecorder.+\$\.eventName\s*=\s*.?DeleteDeliveryChannel.+\$\.eventName\s*=\s*.?PutDeliveryChannel.+\$\.eventName\s*=\s*.?PutConfigurationRecorder.?" findings = [] - if ( - cloudtrail_client.trails is not None - and logs_client.metric_filters is not None - and cloudwatch_client.metric_alarms is not None - ): - report = Check_Report_AWS(self.metadata()) - report.status = "FAIL" - report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." - report.region = logs_client.region - report.resource_id = logs_client.audited_account - report.resource_arn = logs_client.log_group_arn_template - report = check_cloudwatch_log_metric_filter( - pattern, - cloudtrail_client.trails, - logs_client.metric_filters, - cloudwatch_client.metric_alarms, - report, - ) + + report = check_cloudwatch_log_metric_filter( + pattern, + cloudtrail_client.trails, + logs_client.metric_filters, + cloudwatch_client.metric_alarms, + self.metadata(), + ) + + if report is not None: + if report == Check_Report_AWS(self.metadata()): + report.status = "FAIL" + report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." + report.region = logs_client.region + report.resource_id = logs_client.audited_account + report.resource_arn = logs_client.log_group_arn_template + report.resource_tags = [] findings.append(report) + return findings diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled/cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled/cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled.py index 0addc30196..5df1b6640a 100644 --- a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled/cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled.py +++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled/cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled.py @@ -17,24 +17,24 @@ class cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_change def execute(self): pattern = r"\$\.eventName\s*=\s*.?CreateTrail.+\$\.eventName\s*=\s*.?UpdateTrail.+\$\.eventName\s*=\s*.?DeleteTrail.+\$\.eventName\s*=\s*.?StartLogging.+\$\.eventName\s*=\s*.?StopLogging.?" findings = [] - if ( - cloudtrail_client.trails is not None - and logs_client.metric_filters is not None - and cloudwatch_client.metric_alarms is not None - ): - report = Check_Report_AWS(self.metadata()) - report.status = "FAIL" - report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." - report.region = logs_client.region - report.resource_id = logs_client.audited_account - report.resource_arn = logs_client.log_group_arn_template - report = check_cloudwatch_log_metric_filter( - pattern, - cloudtrail_client.trails, - logs_client.metric_filters, - cloudwatch_client.metric_alarms, - report, - ) + + report = check_cloudwatch_log_metric_filter( + pattern, + cloudtrail_client.trails, + logs_client.metric_filters, + cloudwatch_client.metric_alarms, + self.metadata(), + ) + + if report is not None: + if report == Check_Report_AWS(self.metadata()): + report.status = "FAIL" + report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." + report.region = logs_client.region + report.resource_id = logs_client.audited_account + report.resource_arn = logs_client.log_group_arn_template + report.resource_tags = [] findings.append(report) + return findings diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_authentication_failures/cloudwatch_log_metric_filter_authentication_failures.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_authentication_failures/cloudwatch_log_metric_filter_authentication_failures.py index fe6f538caf..df242c8298 100644 --- a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_authentication_failures/cloudwatch_log_metric_filter_authentication_failures.py +++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_authentication_failures/cloudwatch_log_metric_filter_authentication_failures.py @@ -15,24 +15,24 @@ class cloudwatch_log_metric_filter_authentication_failures(Check): def execute(self): pattern = r"\$\.eventName\s*=\s*.?ConsoleLogin.+\$\.errorMessage\s*=\s*.?Failed authentication.?" findings = [] - if ( - cloudtrail_client.trails is not None - and logs_client.metric_filters is not None - and cloudwatch_client.metric_alarms is not None - ): - report = Check_Report_AWS(self.metadata()) - report.status = "FAIL" - report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." - report.region = logs_client.region - report.resource_id = logs_client.audited_account - report.resource_arn = logs_client.log_group_arn_template - report = check_cloudwatch_log_metric_filter( - pattern, - cloudtrail_client.trails, - logs_client.metric_filters, - cloudwatch_client.metric_alarms, - report, - ) + + report = check_cloudwatch_log_metric_filter( + pattern, + cloudtrail_client.trails, + logs_client.metric_filters, + cloudwatch_client.metric_alarms, + self.metadata(), + ) + + if report is not None: + if report == Check_Report_AWS(self.metadata()): + report.status = "FAIL" + report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." + report.region = logs_client.region + report.resource_id = logs_client.audited_account + report.resource_arn = logs_client.log_group_arn_template + report.resource_tags = [] findings.append(report) + return findings diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_aws_organizations_changes/cloudwatch_log_metric_filter_aws_organizations_changes.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_aws_organizations_changes/cloudwatch_log_metric_filter_aws_organizations_changes.py index d9053d3160..7fd56b3afb 100644 --- a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_aws_organizations_changes/cloudwatch_log_metric_filter_aws_organizations_changes.py +++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_aws_organizations_changes/cloudwatch_log_metric_filter_aws_organizations_changes.py @@ -15,24 +15,24 @@ class cloudwatch_log_metric_filter_aws_organizations_changes(Check): def execute(self): pattern = r"\$\.eventSource\s*=\s*.?organizations\.amazonaws\.com.+\$\.eventName\s*=\s*.?AcceptHandshake.+\$\.eventName\s*=\s*.?AttachPolicy.+\$\.eventName\s*=\s*.?CancelHandshake.+\$\.eventName\s*=\s*.?CreateAccount.+\$\.eventName\s*=\s*.?CreateOrganization.+\$\.eventName\s*=\s*.?CreateOrganizationalUnit.+\$\.eventName\s*=\s*.?CreatePolicy.+\$\.eventName\s*=\s*.?DeclineHandshake.+\$\.eventName\s*=\s*.?DeleteOrganization.+\$\.eventName\s*=\s*.?DeleteOrganizationalUnit.+\$\.eventName\s*=\s*.?DeletePolicy.+\$\.eventName\s*=\s*.?EnableAllFeatures.+\$\.eventName\s*=\s*.?EnablePolicyType.+\$\.eventName\s*=\s*.?InviteAccountToOrganization.+\$\.eventName\s*=\s*.?LeaveOrganization.+\$\.eventName\s*=\s*.?DetachPolicy.+\$\.eventName\s*=\s*.?DisablePolicyType.+\$\.eventName\s*=\s*.?MoveAccount.+\$\.eventName\s*=\s*.?RemoveAccountFromOrganization.+\$\.eventName\s*=\s*.?UpdateOrganizationalUnit.+\$\.eventName\s*=\s*.?UpdatePolicy.?" findings = [] - if ( - cloudtrail_client.trails is not None - and logs_client.metric_filters is not None - and cloudwatch_client.metric_alarms is not None - ): - report = Check_Report_AWS(self.metadata()) - report.status = "FAIL" - report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." - report.region = logs_client.region - report.resource_id = logs_client.audited_account - report.resource_arn = logs_client.log_group_arn_template - report = check_cloudwatch_log_metric_filter( - pattern, - cloudtrail_client.trails, - logs_client.metric_filters, - cloudwatch_client.metric_alarms, - report, - ) + + report = check_cloudwatch_log_metric_filter( + pattern, + cloudtrail_client.trails, + logs_client.metric_filters, + cloudwatch_client.metric_alarms, + self.metadata(), + ) + + if report is not None: + if report == Check_Report_AWS(self.metadata()): + report.status = "FAIL" + report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." + report.region = logs_client.region + report.resource_id = logs_client.audited_account + report.resource_arn = logs_client.log_group_arn_template + report.resource_tags = [] findings.append(report) + return findings diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk/cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk/cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk.py index 63f99e1de1..cc7f2f06e4 100644 --- a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk/cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk.py +++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk/cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk.py @@ -15,24 +15,24 @@ class cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk(Chec def execute(self): pattern = r"\$\.eventSource\s*=\s*.?kms.amazonaws.com.+\$\.eventName\s*=\s*.?DisableKey.+\$\.eventName\s*=\s*.?ScheduleKeyDeletion.?" findings = [] - if ( - cloudtrail_client.trails is not None - and logs_client.metric_filters is not None - and cloudwatch_client.metric_alarms is not None - ): - report = Check_Report_AWS(self.metadata()) - report.status = "FAIL" - report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." - report.region = logs_client.region - report.resource_id = logs_client.audited_account - report.resource_arn = logs_client.log_group_arn_template - report = check_cloudwatch_log_metric_filter( - pattern, - cloudtrail_client.trails, - logs_client.metric_filters, - cloudwatch_client.metric_alarms, - report, - ) + + report = check_cloudwatch_log_metric_filter( + pattern, + cloudtrail_client.trails, + logs_client.metric_filters, + cloudwatch_client.metric_alarms, + self.metadata(), + ) + + if report is not None: + if report == Check_Report_AWS(self.metadata()): + report.status = "FAIL" + report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." + report.region = logs_client.region + report.resource_id = logs_client.audited_account + report.resource_arn = logs_client.log_group_arn_template + report.resource_tags = [] findings.append(report) + return findings diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_for_s3_bucket_policy_changes/cloudwatch_log_metric_filter_for_s3_bucket_policy_changes.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_for_s3_bucket_policy_changes/cloudwatch_log_metric_filter_for_s3_bucket_policy_changes.py index 46489bab68..2730a19205 100644 --- a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_for_s3_bucket_policy_changes/cloudwatch_log_metric_filter_for_s3_bucket_policy_changes.py +++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_for_s3_bucket_policy_changes/cloudwatch_log_metric_filter_for_s3_bucket_policy_changes.py @@ -15,25 +15,24 @@ class cloudwatch_log_metric_filter_for_s3_bucket_policy_changes(Check): def execute(self): pattern = r"\$\.eventSource\s*=\s*.?s3.amazonaws.com.+\$\.eventName\s*=\s*.?PutBucketAcl.+\$\.eventName\s*=\s*.?PutBucketPolicy.+\$\.eventName\s*=\s*.?PutBucketCors.+\$\.eventName\s*=\s*.?PutBucketLifecycle.+\$\.eventName\s*=\s*.?PutBucketReplication.+\$\.eventName\s*=\s*.?DeleteBucketPolicy.+\$\.eventName\s*=\s*.?DeleteBucketCors.+\$\.eventName\s*=\s*.?DeleteBucketLifecycle.+\$\.eventName\s*=\s*.?DeleteBucketReplication.?" findings = [] - if ( - cloudtrail_client.trails is not None - and logs_client.metric_filters is not None - and cloudwatch_client.metric_alarms is not None - ): - report = Check_Report_AWS(self.metadata()) - report.status = "FAIL" - report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." - report.region = logs_client.region - report.resource_id = logs_client.audited_account - report.resource_arn = logs_client.log_group_arn_template - report = check_cloudwatch_log_metric_filter( - pattern, - cloudtrail_client.trails, - logs_client.metric_filters, - cloudwatch_client.metric_alarms, - report, - ) + report = check_cloudwatch_log_metric_filter( + pattern, + cloudtrail_client.trails, + logs_client.metric_filters, + cloudwatch_client.metric_alarms, + self.metadata(), + ) + + if report is not None: + if report == Check_Report_AWS(self.metadata()): + report.status = "FAIL" + report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." + report.region = logs_client.region + report.resource_id = logs_client.audited_account + report.resource_arn = logs_client.log_group_arn_template + report.resource_tags = [] findings.append(report) + return findings diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_policy_changes/cloudwatch_log_metric_filter_policy_changes.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_policy_changes/cloudwatch_log_metric_filter_policy_changes.py index 0b38eb7bfa..a1879c3c04 100644 --- a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_policy_changes/cloudwatch_log_metric_filter_policy_changes.py +++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_policy_changes/cloudwatch_log_metric_filter_policy_changes.py @@ -15,24 +15,24 @@ class cloudwatch_log_metric_filter_policy_changes(Check): def execute(self): pattern = r"\$\.eventName\s*=\s*.?DeleteGroupPolicy.+\$\.eventName\s*=\s*.?DeleteRolePolicy.+\$\.eventName\s*=\s*.?DeleteUserPolicy.+\$\.eventName\s*=\s*.?PutGroupPolicy.+\$\.eventName\s*=\s*.?PutRolePolicy.+\$\.eventName\s*=\s*.?PutUserPolicy.+\$\.eventName\s*=\s*.?CreatePolicy.+\$\.eventName\s*=\s*.?DeletePolicy.+\$\.eventName\s*=\s*.?CreatePolicyVersion.+\$\.eventName\s*=\s*.?DeletePolicyVersion.+\$\.eventName\s*=\s*.?AttachRolePolicy.+\$\.eventName\s*=\s*.?DetachRolePolicy.+\$\.eventName\s*=\s*.?AttachUserPolicy.+\$\.eventName\s*=\s*.?DetachUserPolicy.+\$\.eventName\s*=\s*.?AttachGroupPolicy.+\$\.eventName\s*=\s*.?DetachGroupPolicy.?" findings = [] - if ( - cloudtrail_client.trails is not None - and logs_client.metric_filters is not None - and cloudwatch_client.metric_alarms is not None - ): - report = Check_Report_AWS(self.metadata()) - report.status = "FAIL" - report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." - report.region = logs_client.region - report.resource_id = logs_client.audited_account - report.resource_arn = logs_client.log_group_arn_template - report = check_cloudwatch_log_metric_filter( - pattern, - cloudtrail_client.trails, - logs_client.metric_filters, - cloudwatch_client.metric_alarms, - report, - ) + + report = check_cloudwatch_log_metric_filter( + pattern, + cloudtrail_client.trails, + logs_client.metric_filters, + cloudwatch_client.metric_alarms, + self.metadata(), + ) + + if report is not None: + if report == Check_Report_AWS(self.metadata()): + report.status = "FAIL" + report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." + report.region = logs_client.region + report.resource_id = logs_client.audited_account + report.resource_arn = logs_client.log_group_arn_template + report.resource_tags = [] findings.append(report) + return findings diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_root_usage/cloudwatch_log_metric_filter_root_usage.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_root_usage/cloudwatch_log_metric_filter_root_usage.py index 9f99ad4c13..fd18c8b6c5 100644 --- a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_root_usage/cloudwatch_log_metric_filter_root_usage.py +++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_root_usage/cloudwatch_log_metric_filter_root_usage.py @@ -15,24 +15,24 @@ class cloudwatch_log_metric_filter_root_usage(Check): def execute(self): pattern = r"\$\.userIdentity\.type\s*=\s*.?Root.+\$\.userIdentity\.invokedBy NOT EXISTS.+\$\.eventType\s*!=\s*.?AwsServiceEvent.?" findings = [] - if ( - cloudtrail_client.trails is not None - and logs_client.metric_filters is not None - and cloudwatch_client.metric_alarms is not None - ): - report = Check_Report_AWS(self.metadata()) - report.status = "FAIL" - report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." - report.region = logs_client.region - report.resource_id = logs_client.audited_account - report.resource_arn = logs_client.log_group_arn_template - report = check_cloudwatch_log_metric_filter( - pattern, - cloudtrail_client.trails, - logs_client.metric_filters, - cloudwatch_client.metric_alarms, - report, - ) + + report = check_cloudwatch_log_metric_filter( + pattern, + cloudtrail_client.trails, + logs_client.metric_filters, + cloudwatch_client.metric_alarms, + self.metadata(), + ) + + if report is not None: + if report == Check_Report_AWS(self.metadata()): + report.status = "FAIL" + report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." + report.region = logs_client.region + report.resource_id = logs_client.audited_account + report.resource_arn = logs_client.log_group_arn_template + report.resource_tags = [] findings.append(report) + return findings diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_security_group_changes/cloudwatch_log_metric_filter_security_group_changes.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_security_group_changes/cloudwatch_log_metric_filter_security_group_changes.py index 6240f20383..fa0a60af77 100644 --- a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_security_group_changes/cloudwatch_log_metric_filter_security_group_changes.py +++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_security_group_changes/cloudwatch_log_metric_filter_security_group_changes.py @@ -15,24 +15,24 @@ class cloudwatch_log_metric_filter_security_group_changes(Check): def execute(self): pattern = r"\$\.eventName\s*=\s*.?AuthorizeSecurityGroupIngress.+\$\.eventName\s*=\s*.?AuthorizeSecurityGroupEgress.+\$\.eventName\s*=\s*.?RevokeSecurityGroupIngress.+\$\.eventName\s*=\s*.?RevokeSecurityGroupEgress.+\$\.eventName\s*=\s*.?CreateSecurityGroup.+\$\.eventName\s*=\s*.?DeleteSecurityGroup.?" findings = [] - if ( - cloudtrail_client.trails is not None - and logs_client.metric_filters is not None - and cloudwatch_client.metric_alarms is not None - ): - report = Check_Report_AWS(self.metadata()) - report.status = "FAIL" - report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." - report.region = logs_client.region - report.resource_id = logs_client.audited_account - report.resource_arn = logs_client.log_group_arn_template - report = check_cloudwatch_log_metric_filter( - pattern, - cloudtrail_client.trails, - logs_client.metric_filters, - cloudwatch_client.metric_alarms, - report, - ) + + report = check_cloudwatch_log_metric_filter( + pattern, + cloudtrail_client.trails, + logs_client.metric_filters, + cloudwatch_client.metric_alarms, + self.metadata(), + ) + + if report is not None: + if report == Check_Report_AWS(self.metadata()): + report.status = "FAIL" + report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." + report.region = logs_client.region + report.resource_id = logs_client.audited_account + report.resource_arn = logs_client.log_group_arn_template + report.resource_tags = [] findings.append(report) + return findings diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_sign_in_without_mfa/cloudwatch_log_metric_filter_sign_in_without_mfa.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_sign_in_without_mfa/cloudwatch_log_metric_filter_sign_in_without_mfa.py index f6657d125a..e754273767 100644 --- a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_sign_in_without_mfa/cloudwatch_log_metric_filter_sign_in_without_mfa.py +++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_sign_in_without_mfa/cloudwatch_log_metric_filter_sign_in_without_mfa.py @@ -15,24 +15,24 @@ class cloudwatch_log_metric_filter_sign_in_without_mfa(Check): def execute(self): pattern = r"\$\.eventName\s*=\s*.?ConsoleLogin.+\$\.additionalEventData\.MFAUsed\s*!=\s*.?Yes.?" findings = [] - if ( - cloudtrail_client.trails is not None - and logs_client.metric_filters is not None - and cloudwatch_client.metric_alarms is not None - ): - report = Check_Report_AWS(self.metadata()) - report.status = "FAIL" - report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." - report.region = logs_client.region - report.resource_id = logs_client.audited_account - report.resource_arn = logs_client.log_group_arn_template - report = check_cloudwatch_log_metric_filter( - pattern, - cloudtrail_client.trails, - logs_client.metric_filters, - cloudwatch_client.metric_alarms, - report, - ) + + report = check_cloudwatch_log_metric_filter( + pattern, + cloudtrail_client.trails, + logs_client.metric_filters, + cloudwatch_client.metric_alarms, + self.metadata(), + ) + + if report is not None: + if report == Check_Report_AWS(self.metadata()): + report.status = "FAIL" + report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." + report.region = logs_client.region + report.resource_id = logs_client.audited_account + report.resource_arn = logs_client.log_group_arn_template + report.resource_tags = [] findings.append(report) + return findings diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_unauthorized_api_calls/cloudwatch_log_metric_filter_unauthorized_api_calls.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_unauthorized_api_calls/cloudwatch_log_metric_filter_unauthorized_api_calls.py index 2cd8057bfc..8f19587ef5 100644 --- a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_unauthorized_api_calls/cloudwatch_log_metric_filter_unauthorized_api_calls.py +++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_unauthorized_api_calls/cloudwatch_log_metric_filter_unauthorized_api_calls.py @@ -15,24 +15,24 @@ class cloudwatch_log_metric_filter_unauthorized_api_calls(Check): def execute(self): pattern = r"\$\.errorCode\s*=\s*.?\*UnauthorizedOperation.+\$\.errorCode\s*=\s*.?AccessDenied\*.?" findings = [] - if ( - cloudtrail_client.trails is not None - and logs_client.metric_filters is not None - and cloudwatch_client.metric_alarms is not None - ): - report = Check_Report_AWS(self.metadata()) - report.status = "FAIL" - report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." - report.region = logs_client.region - report.resource_id = logs_client.audited_account - report.resource_arn = logs_client.log_group_arn_template - report = check_cloudwatch_log_metric_filter( - pattern, - cloudtrail_client.trails, - logs_client.metric_filters, - cloudwatch_client.metric_alarms, - report, - ) + + report = check_cloudwatch_log_metric_filter( + pattern, + cloudtrail_client.trails, + logs_client.metric_filters, + cloudwatch_client.metric_alarms, + self.metadata(), + ) + + if report is not None: + if report == Check_Report_AWS(self.metadata()): + report.status = "FAIL" + report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated." + report.region = logs_client.region + report.resource_id = logs_client.audited_account + report.resource_arn = logs_client.log_group_arn_template + report.resource_tags = [] findings.append(report) + return findings diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_service.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_service.py index 4a63b9d4cd..d7c9d3fd9e 100644 --- a/prowler/providers/aws/services/cloudwatch/cloudwatch_service.py +++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_service.py @@ -82,10 +82,10 @@ class Logs(AWSService): # Call AWSService's __init__ super().__init__(__class__.__name__, provider) self.log_group_arn_template = f"arn:{self.audited_partition}:logs:{self.region}:{self.audited_account}:log-group" - self.metric_filters = [] self.log_groups = [] - self.__threading_call__(self._describe_metric_filters) self.__threading_call__(self._describe_log_groups) + self.metric_filters = [] + self.__threading_call__(self._describe_metric_filters) if self.log_groups: if ( "cloudwatch_log_group_no_secrets_in_logs" @@ -111,13 +111,20 @@ class Logs(AWSService): ): if self.metric_filters is None: self.metric_filters = [] + + log_group = None + for lg in self.log_groups: + if lg.name == filter["logGroupName"]: + log_group = lg + break + self.metric_filters.append( MetricFilter( arn=arn, name=filter["filterName"], metric=filter["metricTransformations"][0]["metricName"], pattern=filter.get("filterPattern", ""), - log_group=filter["logGroupName"], + log_group=log_group, region=regional_client.region, ) ) @@ -127,7 +134,7 @@ class Logs(AWSService): f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) if not self.metric_filters: - self.metric_filters = [] + self.metric_filters = None else: logger.error( f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" @@ -242,15 +249,6 @@ class MetricAlarm(BaseModel): tags: Optional[list] = [] -class MetricFilter(BaseModel): - arn: str - name: str - metric: str - pattern: str - log_group: str - region: str - - class LogGroup(BaseModel): arn: str name: str @@ -264,6 +262,15 @@ class LogGroup(BaseModel): tags: Optional[list] = [] +class MetricFilter(BaseModel): + arn: str + name: str + metric: str + pattern: str + log_group: Optional[LogGroup] + region: str + + def convert_to_cloudwatch_timestamp_format(epoch_time): date_time = datetime.fromtimestamp( epoch_time / 1000, datetime.now(timezone.utc).astimezone().tzinfo diff --git a/prowler/providers/aws/services/cloudwatch/lib/metric_filters.py b/prowler/providers/aws/services/cloudwatch/lib/metric_filters.py index a8743b6c0f..027b1a4e1f 100644 --- a/prowler/providers/aws/services/cloudwatch/lib/metric_filters.py +++ b/prowler/providers/aws/services/cloudwatch/lib/metric_filters.py @@ -8,28 +8,34 @@ def check_cloudwatch_log_metric_filter( trails: list, metric_filters: list, metric_alarms: list, - report: Check_Report_AWS, + metadata: dict, ): + report = None # 1. Iterate for CloudWatch Log Group in CloudTrail trails log_groups = [] - if trails is not None: + if trails is not None and metric_filters is not None and metric_alarms is not None: + report = Check_Report_AWS(metadata) for trail in trails.values(): if trail.log_group_arn: log_groups.append(trail.log_group_arn.split(":")[6]) # 2. Describe metric filters for previous log groups for metric_filter in metric_filters: - if metric_filter.log_group in log_groups: - if re.search(metric_filter_pattern, metric_filter.pattern, flags=re.DOTALL): - report.resource_id = metric_filter.log_group - report.resource_arn = metric_filter.arn - report.region = metric_filter.region - report.status = "FAIL" - report.status_extended = f"CloudWatch log group {metric_filter.log_group} found with metric filter {metric_filter.name} but no alarms associated." - # 3. Check if there is an alarm for the metric - for alarm in metric_alarms: - if alarm.metric == metric_filter.metric: - report.status = "PASS" - report.status_extended = f"CloudWatch log group {metric_filter.log_group} found with metric filter {metric_filter.name} and alarms set." - break + if metric_filter.log_group.name in log_groups and re.search( + metric_filter_pattern, metric_filter.pattern, flags=re.DOTALL + ): + report.resource_id = metric_filter.log_group.name + report.resource_arn = metric_filter.log_group.arn + report.region = metric_filter.log_group.region + report.resource_tags = getattr(metric_filter.log_group, "tags", []) + report.status = "FAIL" + report.status_extended = f"CloudWatch log group {metric_filter.log_group.name} found with metric filter {metric_filter.name} but no alarms associated." + # 3. Check if there is an alarm for the metric + for alarm in metric_alarms: + if alarm.metric == metric_filter.metric: + report.status = "PASS" + report.status_extended = f"CloudWatch log group {metric_filter.log_group.name} found with metric filter {metric_filter.name} and alarms set." + break + if report.status == "PASS": + break return report diff --git a/prowler/providers/aws/services/cognito/cognito_user_pool_waf_acl_attached/cognito_user_pool_waf_acl_attached.py b/prowler/providers/aws/services/cognito/cognito_user_pool_waf_acl_attached/cognito_user_pool_waf_acl_attached.py index 53c702b88d..e3afc9f74d 100644 --- a/prowler/providers/aws/services/cognito/cognito_user_pool_waf_acl_attached/cognito_user_pool_waf_acl_attached.py +++ b/prowler/providers/aws/services/cognito/cognito_user_pool_waf_acl_attached/cognito_user_pool_waf_acl_attached.py @@ -16,7 +16,7 @@ class cognito_user_pool_waf_acl_attached(Check): report.status_extended = ( f"Cognito User Pool {pool.name} is not associated with a WAF Web ACL." ) - for acl in wafv2_client.web_acls: + for acl in wafv2_client.web_acls.values(): if pool.arn in acl.user_pools: report.status = "PASS" report.status_extended = f"Cognito User Pool {pool.name} is associated with the WAF Web ACL {acl.name}." diff --git a/prowler/providers/aws/services/dynamodb/dynamodb_service.py b/prowler/providers/aws/services/dynamodb/dynamodb_service.py index 27861b2244..edf5bf0c37 100644 --- a/prowler/providers/aws/services/dynamodb/dynamodb_service.py +++ b/prowler/providers/aws/services/dynamodb/dynamodb_service.py @@ -11,7 +11,6 @@ from prowler.providers.aws.lib.service.service import AWSService class DynamoDB(AWSService): def __init__(self, provider): - # Call AWSService's __init__ super().__init__(__class__.__name__, provider) self.tables = {} self.__threading_call__(self._list_tables) @@ -49,6 +48,9 @@ class DynamoDB(AWSService): properties = regional_client.describe_table(TableName=table.name)[ "Table" ] + table.billing_mode = properties.get("BillingModeSummary", {}).get( + "BillingMode", "PROVISIONED" + ) if "SSEDescription" in properties: if "SSEType" in properties["SSEDescription"]: table.encryption_type = properties["SSEDescription"]["SSEType"] @@ -152,7 +154,6 @@ class DynamoDB(AWSService): class DAX(AWSService): def __init__(self, provider): - # Call AWSService's __init__ super().__init__(__class__.__name__, provider) self.clusters = [] self.__threading_call__(self._describe_clusters) @@ -217,6 +218,7 @@ class DAX(AWSService): class Table(BaseModel): name: str + billing_mode: str = "PROVISIONED" encryption_type: Optional[str] kms_arn: Optional[str] pitr: bool = False diff --git a/prowler/providers/aws/services/dynamodb/dynamodb_table_autoscaling_enabled/__init__.py b/prowler/providers/aws/services/dynamodb/dynamodb_table_autoscaling_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/dynamodb/dynamodb_table_autoscaling_enabled/dynamodb_table_autoscaling_enabled.metadata.json b/prowler/providers/aws/services/dynamodb/dynamodb_table_autoscaling_enabled/dynamodb_table_autoscaling_enabled.metadata.json new file mode 100644 index 0000000000..bb77b56de1 --- /dev/null +++ b/prowler/providers/aws/services/dynamodb/dynamodb_table_autoscaling_enabled/dynamodb_table_autoscaling_enabled.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "dynamodb_table_autoscaling_enabled", + "CheckTitle": "Check if DynamoDB tables automatically scale capacity with demand.", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "dynamodb", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:dynamodb:region:account-id:table/table-name", + "Severity": "medium", + "ResourceType": "AwsDynamoDbTable", + "Description": "This check ensures that DynamoDB tables can scale their read and write capacity as needed, either using on-demand capacity mode or provisioned mode with auto scaling configured.", + "Risk": "If DynamoDB tables do not automatically scale capacity with demand, they may experience throttling exceptions, leading to reduced availability and performance of applications.", + "RelatedUrl": "https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/AutoScaling.Console.html#AutoScaling.Console.ExistingTable", + "Remediation": { + "Code": { + "CLI": "aws dynamodb update-table --table-name --billing-mode PAY_PER_REQUEST", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/dynamodb-controls.html#dynamodb-1", + "Terraform": "" + }, + "Recommendation": { + "Text": "Enable DynamoDB automatic scaling on existing tables by configuring on-demand capacity mode or provisioned mode with auto scaling.", + "Url": "https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/AutoScaling.Console.html#AutoScaling.Console.ExistingTable" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/dynamodb/dynamodb_table_autoscaling_enabled/dynamodb_table_autoscaling_enabled.py b/prowler/providers/aws/services/dynamodb/dynamodb_table_autoscaling_enabled/dynamodb_table_autoscaling_enabled.py new file mode 100644 index 0000000000..606a674354 --- /dev/null +++ b/prowler/providers/aws/services/dynamodb/dynamodb_table_autoscaling_enabled/dynamodb_table_autoscaling_enabled.py @@ -0,0 +1,67 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.autoscaling.applicationautoscaling_client import ( + applicationautoscaling_client, +) +from prowler.providers.aws.services.dynamodb.dynamodb_client import dynamodb_client + + +class dynamodb_table_autoscaling_enabled(Check): + def execute(self): + findings = [] + scalable_targets = applicationautoscaling_client.scalable_targets + dynamodb_scalable_targets = [ + target + for target in scalable_targets + if target.service_namespace == "dynamodb" + and target.resource_id.startswith("table/") + ] + autoscaling_mapping = {} + for target in dynamodb_scalable_targets: + table_name = target.resource_id.split("/")[1] + if table_name not in autoscaling_mapping: + autoscaling_mapping[table_name] = {} + autoscaling_mapping[table_name][target.scalable_dimension] = target + + for table_arn, table in dynamodb_client.tables.items(): + report = Check_Report_AWS(self.metadata()) + report.region = table.region + report.resource_id = table.name + report.resource_arn = table_arn + report.resource_tags = table.tags + report.status = "PASS" + report.status_extended = ( + f"DynamoDB table {table.name} automatically scales capacity on demand." + ) + if table.billing_mode == "PROVISIONED": + read_autoscaling = False + write_autoscaling = False + + if table.name in autoscaling_mapping: + if ( + "dynamodb:table:ReadCapacityUnits" + in autoscaling_mapping[table.name] + ): + read_autoscaling = True + if ( + "dynamodb:table:WriteCapacityUnits" + in autoscaling_mapping[table.name] + ): + write_autoscaling = True + + if read_autoscaling and write_autoscaling: + report.status = "PASS" + report.status_extended = f"DynamoDB table {table.name} is in provisioned mode with auto scaling enabled for both read and write capacity units." + else: + missing_autoscaling = [] + if not read_autoscaling: + missing_autoscaling.append("read") + if not write_autoscaling: + missing_autoscaling.append("write") + + if missing_autoscaling: + report.status = "FAIL" + report.status_extended = f"DynamoDB table {table.name} is in provisioned mode without auto scaling enabled for {', '.join(missing_autoscaling)}." + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/ecs/ecs_cluster_container_insights_enabled/__init__.py b/prowler/providers/aws/services/ecs/ecs_cluster_container_insights_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/ecs/ecs_cluster_container_insights_enabled/ecs_cluster_container_insights_enabled.metadata.json b/prowler/providers/aws/services/ecs/ecs_cluster_container_insights_enabled/ecs_cluster_container_insights_enabled.metadata.json new file mode 100644 index 0000000000..6f24b9b645 --- /dev/null +++ b/prowler/providers/aws/services/ecs/ecs_cluster_container_insights_enabled/ecs_cluster_container_insights_enabled.metadata.json @@ -0,0 +1,34 @@ +{ + "Provider": "aws", + "CheckID": "ecs_cluster_container_insights_enabled", + "CheckTitle": "ECS clusters should use Container Insights", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "ecs", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:ecs:{region}:{account-id}:cluster/{cluster-name}", + "Severity": "medium", + "ResourceType": "AwsEcsCluster", + "Description": "This control checks if ECS clusters use Container Insights. This control fails if Container Insights are not set up for a cluster.", + "Risk": "Without Container Insights, important performance metrics and diagnostic information from containerized applications may not be captured, which can hinder monitoring and troubleshooting.", + "RelatedUrl": "https://docs.aws.amazon.com/config/latest/developerguide/ecs-container-insights-enabled.html", + "Remediation": { + "Code": { + "CLI": "aws ecs update-cluster-settings --cluster --settings name=containerInsights,value=enabled", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/ecs-controls.html#ecs-12", + "Terraform": "" + }, + "Recommendation": { + "Text": "Enable Container Insights for your ECS clusters to collect and monitor key performance metrics and diagnostic data from your containers.", + "Url": "https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/deploy-container-insights-ECS-cluster.html" + } + }, + "Categories": [ + "logging" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/ecs/ecs_cluster_container_insights_enabled/ecs_cluster_container_insights_enabled.py b/prowler/providers/aws/services/ecs/ecs_cluster_container_insights_enabled/ecs_cluster_container_insights_enabled.py new file mode 100644 index 0000000000..3c3ce272ef --- /dev/null +++ b/prowler/providers/aws/services/ecs/ecs_cluster_container_insights_enabled/ecs_cluster_container_insights_enabled.py @@ -0,0 +1,27 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.ecs.ecs_client import ecs_client + + +class ecs_cluster_container_insights_enabled(Check): + def execute(self): + findings = [] + for cluster in ecs_client.clusters.values(): + report = Check_Report_AWS(self.metadata()) + report.region = cluster.region + report.resource_id = cluster.name + report.resource_arn = cluster.arn + report.resource_tags = cluster.tags + report.status = "FAIL" + report.status_extended = ( + f"ECS cluster {cluster.name} does not have container insights enabled." + ) + if cluster.settings: + for setting in cluster.settings: + if ( + setting["name"] == "containerInsights" + and setting["value"] == "enabled" + ): + report.status = "PASS" + report.status_extended = f"ECS cluster {cluster.name} has container insights enabled." + findings.append(report) + return findings diff --git a/prowler/providers/aws/services/ecs/ecs_service.py b/prowler/providers/aws/services/ecs/ecs_service.py index ea2c1b4309..5e2e80cebc 100644 --- a/prowler/providers/aws/services/ecs/ecs_service.py +++ b/prowler/providers/aws/services/ecs/ecs_service.py @@ -8,7 +8,6 @@ from prowler.lib.scan_filters.scan_filters import is_resource_filtered from prowler.providers.aws.lib.service.service import AWSService -################################ ECS class ECS(AWSService): def __init__(self, provider): # Call AWSService's __init__ @@ -35,7 +34,7 @@ class ECS(AWSService): ): self.task_definitions[task_definition] = TaskDefinition( # we want the family name without the revision - name=sub(":.*", "", task_definition.split("/")[1]), + name=sub(":.*", "", task_definition.split("/")[-1]), arn=task_definition, revision=task_definition.split(":")[-1], region=regional_client.region, @@ -111,7 +110,7 @@ class ECS(AWSService): service_desc = describe_response["services"][0] service_arn = service_desc["serviceArn"] service_obj = Service( - name=sub(":.*", "", service_arn.split("/")[2]), + name=sub(":.*", "", service_arn.split("/")[-1]), arn=service_arn, region=cluster.region, assign_public_ip=( @@ -120,6 +119,9 @@ class ECS(AWSService): .get("assignPublicIp", "DISABLED") == "ENABLED" ), + launch_type=service_desc.get("launchType", ""), + platform_version=service_desc.get("platformVersion", ""), + platform_family=service_desc.get("platformFamily", ""), tags=service_desc.get("tags", []), ) cluster.services[service_arn] = service_obj @@ -139,7 +141,7 @@ class ECS(AWSService): is_resource_filtered(cluster, self.audit_resources) ): self.clusters[cluster] = Cluster( - name=sub(":.*", "", cluster.split("/")[1]), + name=sub(":.*", "", cluster.split("/")[-1]), arn=cluster, region=regional_client.region, ) @@ -158,6 +160,7 @@ class ECS(AWSService): "TAGS", ], ) + cluster.settings = response["clusters"][0].get("settings", []) cluster.tags = response["clusters"][0].get("tags", []) except Exception as error: logger.error( @@ -194,6 +197,9 @@ class Service(BaseModel): name: str arn: str region: str + launch_type: str = "" + platform_version: Optional[str] + platform_family: Optional[str] assign_public_ip: Optional[bool] tags: Optional[list] = [] @@ -203,4 +209,5 @@ class Cluster(BaseModel): arn: str region: str services: dict = {} + settings: Optional[list] = [] tags: Optional[list] = [] diff --git a/prowler/providers/aws/services/ecs/ecs_service_fargate_latest_platform_version/__init__.py b/prowler/providers/aws/services/ecs/ecs_service_fargate_latest_platform_version/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/ecs/ecs_service_fargate_latest_platform_version/ecs_service_fargate_latest_platform_version.metadata.json b/prowler/providers/aws/services/ecs/ecs_service_fargate_latest_platform_version/ecs_service_fargate_latest_platform_version.metadata.json new file mode 100644 index 0000000000..b3d25e494c --- /dev/null +++ b/prowler/providers/aws/services/ecs/ecs_service_fargate_latest_platform_version/ecs_service_fargate_latest_platform_version.metadata.json @@ -0,0 +1,34 @@ +{ + "Provider": "aws", + "CheckID": "ecs_service_fargate_latest_platform_version", + "CheckTitle": "ECS Fargate services should run on the latest Fargate platform version", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "ecs", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:ecs:{region}:{account-id}:service/{service-name}", + "Severity": "medium", + "ResourceType": "AwsEcsService", + "Description": "This control checks if Amazon ECS Fargate services are running the latest Fargate platform version. The control fails if the platform version is not the latest.", + "Risk": "Not running the latest Fargate platform version may expose your services to security vulnerabilities and bugs that are resolved in newer versions.", + "RelatedUrl": "https://docs.aws.amazon.com/config/latest/developerguide/ecs-fargate-latest-platform-version.html", + "Remediation": { + "Code": { + "CLI": "aws ecs update-service --cluster --service --platform-version LATEST", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/ecs-controls.html#ecs-10", + "Terraform": "" + }, + "Recommendation": { + "Text": "Update your ECS Fargate services to the latest platform version to ensure they are running in a secure and optimized environment.", + "Url": "https://docs.aws.amazon.com/AmazonECS/latest/developerguide/Welcome.html" + } + }, + "Categories": [ + "vulnerabilities" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/ecs/ecs_service_fargate_latest_platform_version/ecs_service_fargate_latest_platform_version.py b/prowler/providers/aws/services/ecs/ecs_service_fargate_latest_platform_version/ecs_service_fargate_latest_platform_version.py new file mode 100644 index 0000000000..e6a20e6b4c --- /dev/null +++ b/prowler/providers/aws/services/ecs/ecs_service_fargate_latest_platform_version/ecs_service_fargate_latest_platform_version.py @@ -0,0 +1,38 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.ecs.ecs_client import ecs_client + + +class ecs_service_fargate_latest_platform_version(Check): + def execute(self): + findings = [] + for service in ecs_client.services.values(): + if service.launch_type == "FARGATE": + report = Check_Report_AWS(self.metadata()) + report.region = service.region + report.resource_id = service.name + report.resource_arn = service.arn + report.resource_tags = service.tags + fargate_latest_linux_version = ecs_client.audit_config.get( + "fargate_linux_latest_version", "1.4.0" + ) + fargate_latest_windows_version = ecs_client.audit_config.get( + "fargate_windows_latest_version", "1.0.0" + ) + report.status = "PASS" + report.status_extended = f"ECS Service {service.name} is using latest FARGATE {service.platform_family} version {fargate_latest_linux_version if service.platform_family == 'Linux' else fargate_latest_windows_version}." + if ( + service.platform_version != "LATEST" + and ( + service.platform_family == "Linux" + and service.platform_version != fargate_latest_linux_version + ) + or ( + service.platform_family == "Windows" + and service.platform_version != fargate_latest_windows_version + ) + ): + report.status = "FAIL" + report.status_extended = f"ECS Service {service.name} is not using latest FARGATE {service.platform_family} version {fargate_latest_linux_version if service.platform_family == 'Linux' else fargate_latest_windows_version}, currently using {service.platform_version}." + + findings.append(report) + return findings diff --git a/prowler/providers/aws/services/efs/efs_access_point_enforce_root_directory/__init__.py b/prowler/providers/aws/services/efs/efs_access_point_enforce_root_directory/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/efs/efs_access_point_enforce_root_directory/efs_access_point_enforce_root_directory.metadata.json b/prowler/providers/aws/services/efs/efs_access_point_enforce_root_directory/efs_access_point_enforce_root_directory.metadata.json new file mode 100644 index 0000000000..b02871bdd3 --- /dev/null +++ b/prowler/providers/aws/services/efs/efs_access_point_enforce_root_directory/efs_access_point_enforce_root_directory.metadata.json @@ -0,0 +1,34 @@ +{ + "Provider": "aws", + "CheckID": "efs_access_point_enforce_root_directory", + "CheckTitle": "EFS access points should enforce a root directory", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "efs", + "SubServiceName": "access-point", + "ResourceIdTemplate": "arn:aws:elasticfilesystem:{region}:{account-id}:access-point/{access-point-id}", + "Severity": "medium", + "ResourceType": "AwsEfsAccessPoint", + "Description": "This control checks if Amazon EFS access points are configured to enforce a root directory. The control fails if the value of Path is set to / (the default root directory of the file system).", + "Risk": "Access points without enforced root directories can potentially expose the entire file system's root directory to clients, which may result in unauthorized access.", + "RelatedUrl": "https://docs.aws.amazon.com/config/latest/developerguide/efs-access-point-enforce-root-directory.html", + "Remediation": { + "Code": { + "CLI": "aws efs update-access-point --access-point-id --root-directory Path=", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/efs-controls.html#efs-3", + "Terraform": "" + }, + "Recommendation": { + "Text": "Update the EFS access point to enforce a non-root directory. This ensures clients can only access a specified subdirectory.", + "Url": "https://docs.aws.amazon.com/efs/latest/ug/enforce-root-directory-access-point.html" + } + }, + "Categories": [ + "vulnerabilities" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/efs/efs_access_point_enforce_root_directory/efs_access_point_enforce_root_directory.py b/prowler/providers/aws/services/efs/efs_access_point_enforce_root_directory/efs_access_point_enforce_root_directory.py new file mode 100644 index 0000000000..8e7641cc9e --- /dev/null +++ b/prowler/providers/aws/services/efs/efs_access_point_enforce_root_directory/efs_access_point_enforce_root_directory.py @@ -0,0 +1,25 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.efs.efs_client import efs_client + + +class efs_access_point_enforce_root_directory(Check): + def execute(self): + findings = [] + for fs in efs_client.filesystems.values(): + if fs.access_points: + report = Check_Report_AWS(self.metadata()) + report.region = fs.region + report.resource_id = fs.id + report.resource_arn = fs.arn + report.resource_tags = fs.tags + report.status = "PASS" + report.status_extended = f"EFS {fs.id} does not have any access point allowing access to the root directory." + access_points = [] + for access_point in fs.access_points: + if access_point.root_directory_path == "/": + access_points.append(access_point) + if access_points: + report.status = "FAIL" + report.status_extended = f"EFS {fs.id} has access points which allow access to the root directory: {', '.join([ap.id for ap in access_points])}." + findings.append(report) + return findings diff --git a/prowler/providers/aws/services/efs/efs_access_point_enforce_user_identity/__init__.py b/prowler/providers/aws/services/efs/efs_access_point_enforce_user_identity/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/efs/efs_access_point_enforce_user_identity/efs_access_point_enforce_user_identity.metadata.json b/prowler/providers/aws/services/efs/efs_access_point_enforce_user_identity/efs_access_point_enforce_user_identity.metadata.json new file mode 100644 index 0000000000..fa20750f87 --- /dev/null +++ b/prowler/providers/aws/services/efs/efs_access_point_enforce_user_identity/efs_access_point_enforce_user_identity.metadata.json @@ -0,0 +1,34 @@ +{ + "Provider": "aws", + "CheckID": "efs_access_point_enforce_user_identity", + "CheckTitle": "EFS access points should enforce a user identity", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "efs", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:elasticfilesystem:{region}:{account-id}:access-point/{access-point-id}", + "Severity": "medium", + "ResourceType": "AwsEfsAccessPoint", + "Description": "This control checks whether Amazon EFS access points are configured to enforce a user identity. This control fails if a POSIX user identity is not defined while creating the EFS access point.", + "Risk": "Without enforcing a user identity, access to the file system can become less controlled, leading to potential unauthorized access or modifications.", + "RelatedUrl": "https://docs.aws.amazon.com/config/latest/developerguide/efs-access-point-enforce-user-identity.html", + "Remediation": { + "Code": { + "CLI": "aws efs create-access-point --file-system-id --posix-user Uid=,Gid=", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/efs-controls.html#efs-4", + "Terraform": "" + }, + "Recommendation": { + "Text": "Create or update the EFS access point to enforce a user identity using POSIX attributes for Uid and Gid.", + "Url": "https://docs.aws.amazon.com/efs/latest/ug/enforce-identity-access-points.html" + } + }, + "Categories": [ + "trustboundaries" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/efs/efs_access_point_enforce_user_identity/efs_access_point_enforce_user_identity.py b/prowler/providers/aws/services/efs/efs_access_point_enforce_user_identity/efs_access_point_enforce_user_identity.py new file mode 100644 index 0000000000..33b8bcace3 --- /dev/null +++ b/prowler/providers/aws/services/efs/efs_access_point_enforce_user_identity/efs_access_point_enforce_user_identity.py @@ -0,0 +1,28 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.efs.efs_client import efs_client + + +class efs_access_point_enforce_user_identity(Check): + def execute(self): + findings = [] + for fs in efs_client.filesystems.values(): + if fs.access_points: + report = Check_Report_AWS(self.metadata()) + report.region = fs.region + report.resource_id = fs.id + report.resource_arn = fs.arn + report.resource_tags = fs.tags + report.status = "PASS" + report.status_extended = ( + f"EFS {fs.id} has all access points with defined POSIX user." + ) + + access_points = [] + for access_point in fs.access_points: + if not access_point.posix_user: + access_points.append(access_point) + if access_points: + report.status = "FAIL" + report.status_extended = f"EFS {fs.id} has access points with no POSIX user: {', '.join([ap.id for ap in access_points])}." + findings.append(report) + return findings diff --git a/prowler/providers/aws/services/efs/efs_encryption_at_rest_enabled/efs_encryption_at_rest_enabled.py b/prowler/providers/aws/services/efs/efs_encryption_at_rest_enabled/efs_encryption_at_rest_enabled.py index baedd5f6b7..ceecaf9216 100644 --- a/prowler/providers/aws/services/efs/efs_encryption_at_rest_enabled/efs_encryption_at_rest_enabled.py +++ b/prowler/providers/aws/services/efs/efs_encryption_at_rest_enabled/efs_encryption_at_rest_enabled.py @@ -5,7 +5,7 @@ from prowler.providers.aws.services.efs.efs_client import efs_client class efs_encryption_at_rest_enabled(Check): def execute(self): findings = [] - for fs in efs_client.filesystems: + for fs in efs_client.filesystems.values(): report = Check_Report_AWS(self.metadata()) report.region = fs.region report.resource_id = fs.id diff --git a/prowler/providers/aws/services/efs/efs_have_backup_enabled/efs_have_backup_enabled.py b/prowler/providers/aws/services/efs/efs_have_backup_enabled/efs_have_backup_enabled.py index ae0e19de86..65ab9d0dd4 100644 --- a/prowler/providers/aws/services/efs/efs_have_backup_enabled/efs_have_backup_enabled.py +++ b/prowler/providers/aws/services/efs/efs_have_backup_enabled/efs_have_backup_enabled.py @@ -5,7 +5,7 @@ from prowler.providers.aws.services.efs.efs_client import efs_client class efs_have_backup_enabled(Check): def execute(self): findings = [] - for fs in efs_client.filesystems: + for fs in efs_client.filesystems.values(): report = Check_Report_AWS(self.metadata()) report.region = fs.region report.resource_id = fs.id diff --git a/prowler/providers/aws/services/efs/efs_mount_target_not_publicly_accessible/__init__.py b/prowler/providers/aws/services/efs/efs_mount_target_not_publicly_accessible/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/efs/efs_mount_target_not_publicly_accessible/efs_mount_target_not_publicly_accessible.metadata.json b/prowler/providers/aws/services/efs/efs_mount_target_not_publicly_accessible/efs_mount_target_not_publicly_accessible.metadata.json new file mode 100644 index 0000000000..99dc4ae0d6 --- /dev/null +++ b/prowler/providers/aws/services/efs/efs_mount_target_not_publicly_accessible/efs_mount_target_not_publicly_accessible.metadata.json @@ -0,0 +1,34 @@ +{ + "Provider": "aws", + "CheckID": "efs_mount_target_not_publicly_accessible", + "CheckTitle": "EFS mount targets should not be publicly accessible", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "efs", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:elasticfilesystem:{region}:{account-id}:file-system/{filesystem-id}", + "Severity": "medium", + "ResourceType": "AwsEfsFileSystem", + "Description": "This control checks whether an Amazon EFS mount target is associated with a public subnet since it can be accessed from the internet.", + "Risk": "Mount targets in public subnets may expose your EFS to unauthorized access or attacks.", + "RelatedUrl": "https://docs.aws.amazon.com/config/latest/developerguide/efs-mount-target-public-accessible.html", + "Remediation": { + "Code": { + "CLI": "aws efs create-mount-target --file-system-id --subnet-id --security-groups ", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/efs-controls.html#efs-6", + "Terraform": "" + }, + "Recommendation": { + "Text": "Recreate the EFS mount target in a private subnet to ensure it is not publicly accessible.", + "Url": "https://docs.aws.amazon.com/efs/latest/ug/accessing-fs.html" + } + }, + "Categories": [ + "internet-exposed" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/efs/efs_mount_target_not_publicly_accessible/efs_mount_target_not_publicly_accessible.py b/prowler/providers/aws/services/efs/efs_mount_target_not_publicly_accessible/efs_mount_target_not_publicly_accessible.py new file mode 100644 index 0000000000..ede0b25329 --- /dev/null +++ b/prowler/providers/aws/services/efs/efs_mount_target_not_publicly_accessible/efs_mount_target_not_publicly_accessible.py @@ -0,0 +1,28 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.efs.efs_client import efs_client +from prowler.providers.aws.services.vpc.vpc_client import vpc_client + + +class efs_mount_target_not_publicly_accessible(Check): + def execute(self): + findings = [] + for fs in efs_client.filesystems.values(): + report = Check_Report_AWS(self.metadata()) + report.region = fs.region + report.resource_id = fs.id + report.resource_arn = fs.arn + report.resource_tags = fs.tags + report.status = "PASS" + report.status_extended = ( + f"EFS {fs.id} does not have any public mount targets." + ) + mount_targets = [] + for mt in fs.mount_targets: + if vpc_client.vpc_subnets[mt.subnet_id].public: + mount_targets.append(mt) + if mount_targets: + report.status = "FAIL" + report.status_extended = f"EFS {fs.id} has public mount targets: {', '.join([mt.id for mt in mount_targets])}" + + findings.append(report) + return findings diff --git a/prowler/providers/aws/services/efs/efs_not_publicly_accessible/efs_not_publicly_accessible.py b/prowler/providers/aws/services/efs/efs_not_publicly_accessible/efs_not_publicly_accessible.py index 73d89b938e..2a3f44c578 100644 --- a/prowler/providers/aws/services/efs/efs_not_publicly_accessible/efs_not_publicly_accessible.py +++ b/prowler/providers/aws/services/efs/efs_not_publicly_accessible/efs_not_publicly_accessible.py @@ -6,7 +6,7 @@ from prowler.providers.aws.services.iam.lib.policy import is_policy_public class efs_not_publicly_accessible(Check): def execute(self): findings = [] - for fs in efs_client.filesystems: + for fs in efs_client.filesystems.values(): report = Check_Report_AWS(self.metadata()) report.region = fs.region report.resource_id = fs.id diff --git a/prowler/providers/aws/services/efs/efs_service.py b/prowler/providers/aws/services/efs/efs_service.py index 5cf51ce827..ef13e9e3aa 100644 --- a/prowler/providers/aws/services/efs/efs_service.py +++ b/prowler/providers/aws/services/efs/efs_service.py @@ -14,9 +14,13 @@ class EFS(AWSService): def __init__(self, provider): # Call AWSService's __init__ super().__init__(__class__.__name__, provider) - self.filesystems = [] + self.filesystems = {} self.__threading_call__(self._describe_file_systems) - self._describe_file_system_policies() + self.__threading_call__( + self._describe_file_system_policies, self.filesystems.values() + ) + self.__threading_call__(self._describe_mount_targets, self.filesystems.values()) + self.__threading_call__(self._describe_access_points, self.filesystems.values()) def _describe_file_systems(self, regional_client): logger.info("EFS - Describing file systems...") @@ -31,55 +35,137 @@ class EFS(AWSService): if not self.audit_resources or ( is_resource_filtered(efs_arn, self.audit_resources) ): - self.filesystems.append( - FileSystem( - id=efs_id, - arn=efs_arn, - region=regional_client.region, - policy=None, - backup_policy=None, - encrypted=efs["Encrypted"], - tags=efs.get("Tags"), - ) + self.filesystems[efs_arn] = FileSystem( + id=efs_id, + arn=efs_arn, + region=regional_client.region, + encrypted=efs["Encrypted"], + tags=efs.get("Tags"), ) except Exception as error: logger.error( f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) - def _describe_file_system_policies(self): + def _describe_file_system_policies(self, filesystem): logger.info("EFS - Describing file system policies...") try: - for filesystem in self.filesystems: - for region, client in self.regional_clients.items(): - if filesystem.region == region: - try: - filesystem.backup_policy = client.describe_backup_policy( - FileSystemId=filesystem.id - )["BackupPolicy"]["Status"] - except ClientError as e: - if e.response["Error"]["Code"] == "PolicyNotFound": - filesystem.backup_policy = "DISABLED" - try: - fs_policy = client.describe_file_system_policy( - FileSystemId=filesystem.id + client = self.regional_clients[filesystem.region] + try: + filesystem.backup_policy = client.describe_backup_policy( + FileSystemId=filesystem.id + )["BackupPolicy"]["Status"] + except ClientError as error: + if error.response["Error"]["Code"] == "PolicyNotFound": + filesystem.backup_policy = "DISABLED" + logger.warning( + f"{client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + else: + logger.error( + f"{client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + try: + fs_policy = client.describe_file_system_policy( + FileSystemId=filesystem.id + ) + if "Policy" in fs_policy: + filesystem.policy = json.loads(fs_policy["Policy"]) + except ClientError as error: + if error.response["Error"]["Code"] == "PolicyNotFound": + filesystem.policy = {} + logger.warning( + f"{client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + else: + logger.error( + f"{client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + except Exception as error: + logger.error( + f"{client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def _describe_mount_targets(self, filesystem): + logger.info("EFS - Describing mount targets...") + try: + client = self.regional_clients[filesystem.region] + describe_mount_target_paginator = client.get_paginator( + "describe_mount_targets" + ) + for page in describe_mount_target_paginator.paginate( + FileSystemId=filesystem.id + ): + for mount_target in page["MountTargets"]: + mount_target_id = mount_target["MountTargetId"] + mount_target_arn = f"arn:{self.audited_partition}:elasticfilesystem:{client.region}:{self.audited_account}:mount-target/{mount_target_id}" + if not self.audit_resources or ( + is_resource_filtered(mount_target_arn, self.audit_resources) + ): + self.filesystems[filesystem.arn].mount_targets.append( + MountTarget( + id=mount_target_id, + file_system_id=mount_target["FileSystemId"], + subnet_id=mount_target["SubnetId"], ) - if "Policy" in fs_policy: - filesystem.policy = json.loads(fs_policy["Policy"]) - except ClientError as e: - if e.response["Error"]["Code"] == "PolicyNotFound": - filesystem.policy = {} + ) + except Exception as error: + logger.error( + f"{client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def _describe_access_points(self, filesystem): + logger.info("EFS - Describing access points...") + try: + client = self.regional_clients[filesystem.region] + describe_access_point_paginator = client.get_paginator( + "describe_access_points" + ) + for page in describe_access_point_paginator.paginate( + FileSystemId=filesystem.id + ): + for access_point in page["AccessPoints"]: + access_point_id = access_point["AccessPointId"] + access_point_arn = access_point["AccessPointArn"] + if not self.audit_resources or ( + is_resource_filtered(access_point_arn, self.audit_resources) + ): + self.filesystems[filesystem.arn].access_points.append( + AccessPoint( + id=access_point_id, + file_system_id=access_point["FileSystemId"], + root_directory_path=access_point["RootDirectory"][ + "Path" + ], + posix_user=access_point.get("PosixUser", {}), + ) + ) except Exception as error: logger.error( f"{client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) +class MountTarget(BaseModel): + id: str + file_system_id: str + subnet_id: str + + +class AccessPoint(BaseModel): + id: str + file_system_id: str + root_directory_path: str + posix_user: dict = {} + + class FileSystem(BaseModel): id: str arn: str region: str - policy: Optional[dict] - backup_policy: Optional[str] + policy: Optional[dict] = {} + backup_policy: Optional[str] = "DISABLED" encrypted: bool + mount_targets: list[MountTarget] = [] + access_points: list[AccessPoint] = [] tags: Optional[list] = [] diff --git a/prowler/providers/aws/services/elasticbeanstalk/__init__.py b/prowler/providers/aws/services/elasticbeanstalk/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/elasticbeanstalk/elasticbeanstalk_client.py b/prowler/providers/aws/services/elasticbeanstalk/elasticbeanstalk_client.py new file mode 100644 index 0000000000..bea628d978 --- /dev/null +++ b/prowler/providers/aws/services/elasticbeanstalk/elasticbeanstalk_client.py @@ -0,0 +1,6 @@ +from prowler.providers.aws.services.elasticbeanstalk.elasticbeanstalk_service import ( + ElasticBeanstalk, +) +from prowler.providers.common.provider import Provider + +elasticbeanstalk_client = ElasticBeanstalk(Provider.get_global_provider()) diff --git a/prowler/providers/aws/services/elasticbeanstalk/elasticbeanstalk_service.py b/prowler/providers/aws/services/elasticbeanstalk/elasticbeanstalk_service.py new file mode 100644 index 0000000000..3f8faa0e64 --- /dev/null +++ b/prowler/providers/aws/services/elasticbeanstalk/elasticbeanstalk_service.py @@ -0,0 +1,106 @@ +from typing import Optional + +from pydantic import BaseModel + +from prowler.lib.logger import logger +from prowler.lib.scan_filters.scan_filters import is_resource_filtered +from prowler.providers.aws.lib.service.service import AWSService + + +class ElasticBeanstalk(AWSService): + def __init__(self, provider): + # Call AWSService's __init__ + super().__init__(__class__.__name__, provider) + self.environments = {} + self.__threading_call__(self._describe_environments) + self.__threading_call__( + self._describe_configuration_settings, self.environments.values() + ) + self.__threading_call__( + self._list_tags_for_resource, self.environments.values() + ) + + def _describe_environments(self, regional_client): + logger.info("ElasticBeanstalk - Describing environments...") + try: + describe_environment_paginator = regional_client.get_paginator( + "describe_environments" + ) + for page in describe_environment_paginator.paginate(): + for environment in page["Environments"]: + environment_arn = environment["EnvironmentArn"] + if not self.audit_resources or ( + is_resource_filtered(environment_arn, self.audit_resources) + ): + self.environments[environment_arn] = Environment( + id=environment["EnvironmentId"], + arn=environment_arn, + application_name=environment["ApplicationName"], + name=environment["EnvironmentName"], + region=regional_client.region, + ) + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def _describe_configuration_settings(self, environment): + logger.info("ElasticBeanstalk - Describing configuration settings...") + try: + regional_client = self.regional_clients[environment.region] + configuration_settings = regional_client.describe_configuration_settings( + ApplicationName=environment.application_name, + EnvironmentName=environment.name, + ) + option_settings = configuration_settings["ConfigurationSettings"][0].get( + "OptionSettings", {} + ) + for option in option_settings: + if ( + option["Namespace"] == "aws:elasticbeanstalk:healthreporting:system" + and option["OptionName"] == "SystemType" + ): + environment.health_reporting = option.get("Value", "basic") + elif ( + option["Namespace"] == "aws:elasticbeanstalk:managedactions" + and option["OptionName"] == "ManagedActionsEnabled" + ): + environment.managed_platform_updates = option.get("Value", "false") + elif ( + option["Namespace"] == "aws:elasticbeanstalk:cloudwatch:logs" + and option["OptionName"] == "StreamLogs" + ): + environment.cloudwatch_stream_logs = option.get("Value", "false") + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def _list_tags_for_resource(self, resource: any): + logger.info("ElasticBeanstalk - List Tags...") + try: + regional_client = self.regional_clients[resource.region] + response = regional_client.list_tags_for_resource(ResourceArn=resource.arn)[ + "ResourceTags" + ] + resource.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 Environment(BaseModel): + id: str + name: str + arn: str + region: str + application_name: str + health_reporting: Optional[str] + managed_platform_updates: Optional[str] + cloudwatch_stream_logs: Optional[str] + tags: Optional[list] = [] diff --git a/prowler/providers/aws/services/elbv2/elbv2_waf_acl_attached/elbv2_waf_acl_attached.py b/prowler/providers/aws/services/elbv2/elbv2_waf_acl_attached/elbv2_waf_acl_attached.py index 464b4c7fcc..7944c44c50 100644 --- a/prowler/providers/aws/services/elbv2/elbv2_waf_acl_attached/elbv2_waf_acl_attached.py +++ b/prowler/providers/aws/services/elbv2/elbv2_waf_acl_attached/elbv2_waf_acl_attached.py @@ -18,7 +18,7 @@ class elbv2_waf_acl_attached(Check): report.status_extended = ( f"ELBv2 ALB {lb.name} is not protected by WAF Web ACL." ) - for acl in wafv2_client.web_acls: + for acl in wafv2_client.web_acls.values(): if lb_arn in acl.albs: report.status = "PASS" report.status_extended = f"ELBv2 ALB {lb.name} is protected by WAFv2 Web ACL {acl.name}." diff --git a/prowler/providers/aws/services/glue/glue_ml_transform_encrypted_at_rest/__init__.py b/prowler/providers/aws/services/glue/glue_ml_transform_encrypted_at_rest/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/glue/glue_ml_transform_encrypted_at_rest/glue_ml_transform_encrypted_at_rest.metadata.json b/prowler/providers/aws/services/glue/glue_ml_transform_encrypted_at_rest/glue_ml_transform_encrypted_at_rest.metadata.json new file mode 100644 index 0000000000..0b8cbc25b0 --- /dev/null +++ b/prowler/providers/aws/services/glue/glue_ml_transform_encrypted_at_rest/glue_ml_transform_encrypted_at_rest.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "glue_ml_transform_encrypted_at_rest", + "CheckTitle": "Check if Glue ML Transform Encryption at Rest is Enabled", + "CheckType": [], + "ServiceName": "glue", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:glue:region:account-id:mlTransform/transform-id", + "Severity": "medium", + "ResourceType": "Other", + "Description": "This control checks whether an AWS Glue machine learning transform is encrypted at rest. The control fails if the machine learning transform isn't encrypted at rest.", + "Risk": "Data at rest refers to data that's stored in persistent, non-volatile storage for any duration. Encrypting data at rest helps you protect its confidentiality, which reduces the risk that an unauthorized user can access it.", + "RelatedUrl": "https://docs.aws.amazon.com/glue/latest/dg/encryption-at-rest.html", + "Remediation": { + "Code": { + "CLI": "aws glue update-ml-transform --transform-id --encryption-at-rest {\"Enabled\":true,\"KmsKey\":\"\"}", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/glue-controls.html#glue-3", + "Terraform": "" + }, + "Recommendation": { + "Text": "Enable encryption at rest for Glue ML Transforms using AWS KMS keys.", + "Url": "https://docs.aws.amazon.com/glue/latest/dg/encryption-at-rest.html" + } + }, + "Categories": [ + "encryption" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/glue/glue_ml_transform_encrypted_at_rest/glue_ml_transform_encrypted_at_rest.py b/prowler/providers/aws/services/glue/glue_ml_transform_encrypted_at_rest/glue_ml_transform_encrypted_at_rest.py new file mode 100644 index 0000000000..fe56aa6113 --- /dev/null +++ b/prowler/providers/aws/services/glue/glue_ml_transform_encrypted_at_rest/glue_ml_transform_encrypted_at_rest.py @@ -0,0 +1,28 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.glue.glue_client import glue_client + + +class glue_ml_transform_encrypted_at_rest(Check): + def execute(self): + findings = [] + + for ml_transform_arn, ml_transform in glue_client.ml_transforms.items(): + report = Check_Report_AWS(self.metadata()) + report.resource_id = ml_transform.id + report.resource_arn = ml_transform_arn + report.region = ml_transform.region + report.resource_tags = ml_transform.tags + report.status = "PASS" + report.status_extended = ( + f"Glue ML Transform {ml_transform.name} is encrypted at rest." + ) + + if ml_transform.user_data_encryption == "DISABLED": + report.status = "FAIL" + report.status_extended = ( + f"Glue ML Transform {ml_transform.name} is not encrypted at rest." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/glue/glue_service.py b/prowler/providers/aws/services/glue/glue_service.py index a11b137fcd..8085368b1a 100644 --- a/prowler/providers/aws/services/glue/glue_service.py +++ b/prowler/providers/aws/services/glue/glue_service.py @@ -27,6 +27,9 @@ class Glue(AWSService): self.jobs = [] self.__threading_call__(self._get_jobs) self.__threading_call__(self._list_tags, self.jobs) + self.ml_transforms = {} + self.__threading_call__(self._get_ml_transforms) + self.__threading_call__(self._list_tags, self.ml_transforms.values()) def _get_data_catalog_arn_template(self, region): return f"arn:{self.audited_partition}:glue:{region}:{self.audited_account}:data-catalog" @@ -219,6 +222,30 @@ class Glue(AWSService): f"{resource.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + def _get_ml_transforms(self, regional_client): + logger.info("Glue - Getting ML Transforms...") + try: + transforms = regional_client.get_ml_transforms()["Transforms"] + for transform in transforms: + ml_transform_arn = f"arn:{self.audited_partition}:glue:{regional_client.region}:{self.audited_account}:mlTransform/{transform['TransformId']}" + if not self.audit_resources or is_resource_filtered( + ml_transform_arn, self.audit_resources + ): + self.ml_transforms[ml_transform_arn] = MLTransform( + arn=ml_transform_arn, + id=transform["TransformId"], + name=transform["Name"], + user_data_encryption=transform.get("TransformEncryption", {}) + .get("MlUserDataEncryption", {}) + .get("MlUserDataEncryptionMode", "DISABLED"), + region=regional_client.region, + ) + + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + class Connection(BaseModel): name: str @@ -272,3 +299,12 @@ class SecurityConfig(BaseModel): jb_encryption: str jb_key_arn: Optional[str] region: str + + +class MLTransform(BaseModel): + arn: str + id: str + name: str + user_data_encryption: str + region: str + tags: Optional[list] diff --git a/prowler/providers/aws/services/guardduty/guardduty_ec2_malware_protection_enabled/__init__.py b/prowler/providers/aws/services/guardduty/guardduty_ec2_malware_protection_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/guardduty/guardduty_ec2_malware_protection_enabled/guardduty_ec2_malware_protection_enabled.metadata.json b/prowler/providers/aws/services/guardduty/guardduty_ec2_malware_protection_enabled/guardduty_ec2_malware_protection_enabled.metadata.json new file mode 100644 index 0000000000..fa4b977512 --- /dev/null +++ b/prowler/providers/aws/services/guardduty/guardduty_ec2_malware_protection_enabled/guardduty_ec2_malware_protection_enabled.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "guardduty_ec2_malware_protection_enabled", + "CheckTitle": "Ensure that GuardDuty Malware Protection for EC2 is enabled.", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "guardduty", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:guardduty:region:account-id/detector-id", + "Severity": "high", + "ResourceType": "AwsGuardDutyDetector", + "Description": "GuardDuty Malware Protection for EC2 helps you detect the potential presence of malware by scanning the Amazon Elastic Block Store (Amazon EBS) volumes that are attached to Amazon Elastic Compute Cloud (Amazon EC2) instances and container workloads.", + "Risk": "Malware can compromise your EC2 instances and container workloads, leading to data breaches, data exfiltration, and other security incidents.", + "RelatedUrl": "https://docs.aws.amazon.com/guardduty/latest/ug/malware-protection.html", + "Remediation": { + "Code": { + "CLI": "aws guardduty update-detector --detector-id --data-sources MalwareProtection={ScanEc2InstanceWithFindings={EbsVolumes=true}}", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/guardduty-controls.html#guardduty-8", + "Terraform": "" + }, + "Recommendation": { + "Text": "Enable Malware Protection for EC2 in GuardDuty.", + "Url": "https://docs.aws.amazon.com/guardduty/latest/ug/configure-malware-protection-single-account.html" + } + }, + "Categories": [], + "Notes": "", + "DependsOn": [], + "RelatedTo": [] +} diff --git a/prowler/providers/aws/services/guardduty/guardduty_ec2_malware_protection_enabled/guardduty_ec2_malware_protection_enabled.py b/prowler/providers/aws/services/guardduty/guardduty_ec2_malware_protection_enabled/guardduty_ec2_malware_protection_enabled.py new file mode 100644 index 0000000000..aacf2c08f1 --- /dev/null +++ b/prowler/providers/aws/services/guardduty/guardduty_ec2_malware_protection_enabled/guardduty_ec2_malware_protection_enabled.py @@ -0,0 +1,21 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.guardduty.guardduty_client import guardduty_client + + +class guardduty_ec2_malware_protection_enabled(Check): + def execute(self): + findings = [] + for detector in guardduty_client.detectors: + if detector.status: + report = Check_Report_AWS(self.metadata()) + report.region = detector.region + report.resource_id = detector.id + report.resource_arn = detector.arn + report.resource_tags = detector.tags + report.status = "FAIL" + report.status_extended = f"GuardDuty detector {detector.id} does not have Malware Protection for EC2 enabled." + if detector.ec2_malware_protection: + report.status = "PASS" + report.status_extended = f"GuardDuty detector {detector.id} has Malware Protection for EC2 enabled." + findings.append(report) + return findings diff --git a/prowler/providers/aws/services/guardduty/guardduty_eks_audit_log_enabled/__init__.py b/prowler/providers/aws/services/guardduty/guardduty_eks_audit_log_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/guardduty/guardduty_eks_audit_log_enabled/guardduty_eks_audit_log_enabled.metadata.json b/prowler/providers/aws/services/guardduty/guardduty_eks_audit_log_enabled/guardduty_eks_audit_log_enabled.metadata.json new file mode 100644 index 0000000000..02da3ced92 --- /dev/null +++ b/prowler/providers/aws/services/guardduty/guardduty_eks_audit_log_enabled/guardduty_eks_audit_log_enabled.metadata.json @@ -0,0 +1,34 @@ +{ + "Provider": "aws", + "CheckID": "guardduty_eks_audit_log_enabled", + "CheckTitle": "GuardDuty EKS Audit Log Monitoring Enabled", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices/Runtime Behavior Analysis" + ], + "ServiceName": "guardduty", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:guardduty:region:account-id/detector-id", + "Severity": "high", + "ResourceType": "AwsGuardDutyDetector", + "Description": "Checks whether GuardDuty EKS Audit Log Monitoring is enabled as source in a detector.", + "Risk": "Without GuardDuty EKS Audit Log Monitoring enabled, you may not be able to detect potentially suspicious activities in your Amazon Elastic Kubernetes Service (Amazon EKS) clusters.", + "RelatedUrl": "https://docs.aws.amazon.com/guardduty/latest/ug/kubernetes-protection.html", + "Remediation": { + "Code": { + "CLI": "aws guardduty update-detector --detector-id --data-sources Kubernetes={AuditLogs={Enable=true}}", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/guardduty-controls.html#guardduty-5", + "Terraform": "" + }, + "Recommendation": { + "Text": "Enable GuardDuty EKS Audit Log Monitoring to detect potentially suspicious activities in your Amazon Elastic Kubernetes Service (Amazon EKS) clusters.", + "Url": "https://docs.aws.amazon.com/guardduty/latest/ug/eks-protection-enable-standalone-account.html" + } + }, + "Categories": [ + "logging" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/guardduty/guardduty_eks_audit_log_enabled/guardduty_eks_audit_log_enabled.py b/prowler/providers/aws/services/guardduty/guardduty_eks_audit_log_enabled/guardduty_eks_audit_log_enabled.py new file mode 100644 index 0000000000..2cbba4148a --- /dev/null +++ b/prowler/providers/aws/services/guardduty/guardduty_eks_audit_log_enabled/guardduty_eks_audit_log_enabled.py @@ -0,0 +1,21 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.guardduty.guardduty_client import guardduty_client + + +class guardduty_eks_audit_log_enabled(Check): + def execute(self): + findings = [] + for detector in guardduty_client.detectors: + if detector.status: + report = Check_Report_AWS(self.metadata()) + report.region = detector.region + report.resource_id = detector.id + report.resource_arn = detector.arn + report.resource_tags = detector.tags + report.status = "FAIL" + report.status_extended = f"GuardDuty detector {detector.id} does not have EKS Audit Log Monitoring enabled." + if detector.eks_audit_log_protection: + report.status = "PASS" + report.status_extended = f"GuardDuty detector {detector.id} has EKS Audit Log Monitoring enabled." + findings.append(report) + return findings diff --git a/prowler/providers/aws/services/guardduty/guardduty_lambda_protection_enabled/__init__.py b/prowler/providers/aws/services/guardduty/guardduty_lambda_protection_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/guardduty/guardduty_lambda_protection_enabled/guardduty_lambda_protection_enabled.metadata.json b/prowler/providers/aws/services/guardduty/guardduty_lambda_protection_enabled/guardduty_lambda_protection_enabled.metadata.json new file mode 100644 index 0000000000..57fda8b12f --- /dev/null +++ b/prowler/providers/aws/services/guardduty/guardduty_lambda_protection_enabled/guardduty_lambda_protection_enabled.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "guardduty_lambda_protection_enabled", + "CheckTitle": "Check if GuardDuty Lambda Protection is enabled.", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "guardduty", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:guardduty:region:account-id/detector-id", + "Severity": "high", + "ResourceType": "", + "Description": "GuardDuty Lambda Protection helps you identify potential security threats when an AWS Lambda function gets invoked. After you enable Lambda Protection, GuardDuty starts monitoring Lambda network activity logs associated with the Lambda functions in your AWS account.", + "Risk": "If Lambda Protection is not enabled, GuardDuty will not be able to monitor Lambda network activity logs and may miss potential security threats.", + "RelatedUrl": "https://docs.aws.amazon.com/guardduty/latest/ug/lambda-protection.html", + "Remediation": { + "Code": { + "CLI": "aws guardduty update-detector --detector-id --features Name=LAMBDA_NETWORK_LOGS,Status=ENABLED", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/guardduty-controls.html#guardduty-6", + "Terraform": "" + }, + "Recommendation": { + "Text": "Enable Lambda Protection in your GuardDuty detector to start monitoring Lambda Network Activity in your account.", + "Url": "https://docs.aws.amazon.com/guardduty/latest/ug/configure-lambda-protection-standalone-acc.html" + } + }, + "Categories": [], + "Notes": "", + "DependsOn": [], + "RelatedTo": [] +} diff --git a/prowler/providers/aws/services/guardduty/guardduty_lambda_protection_enabled/guardduty_lambda_protection_enabled.py b/prowler/providers/aws/services/guardduty/guardduty_lambda_protection_enabled/guardduty_lambda_protection_enabled.py new file mode 100644 index 0000000000..b304f8f788 --- /dev/null +++ b/prowler/providers/aws/services/guardduty/guardduty_lambda_protection_enabled/guardduty_lambda_protection_enabled.py @@ -0,0 +1,21 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.guardduty.guardduty_client import guardduty_client + + +class guardduty_lambda_protection_enabled(Check): + def execute(self): + findings = [] + for detector in guardduty_client.detectors: + if detector.status: + report = Check_Report_AWS(self.metadata()) + report.region = detector.region + report.resource_id = detector.id + report.resource_arn = detector.arn + report.resource_tags = detector.tags + report.status = "FAIL" + report.status_extended = f"GuardDuty detector {detector.id} does not have Lambda Protection enabled." + if detector.lambda_protection: + report.status = "PASS" + report.status_extended = f"GuardDuty detector {detector.id} has Lambda Protection enabled." + findings.append(report) + return findings diff --git a/prowler/providers/aws/services/guardduty/guardduty_rds_protection_enabled/guardduty_rds_protection_enabled.metadata.json b/prowler/providers/aws/services/guardduty/guardduty_rds_protection_enabled/guardduty_rds_protection_enabled.metadata.json index 445e334de8..e47f2cbd5a 100644 --- a/prowler/providers/aws/services/guardduty/guardduty_rds_protection_enabled/guardduty_rds_protection_enabled.metadata.json +++ b/prowler/providers/aws/services/guardduty/guardduty_rds_protection_enabled/guardduty_rds_protection_enabled.metadata.json @@ -15,7 +15,7 @@ "RelatedUrl": "https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/guard-duty-rds-protection.html", "Remediation": { "Code": { - "CLI": "", + "CLI": "aws guardduty update-detector --detector-id --features Name=RDS_LOGIN_EVENTS,Status=ENABLED", "NativeIaC": "", "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/guardduty-controls.html#guardduty-9", "Terraform": "" diff --git a/prowler/providers/aws/services/guardduty/guardduty_s3_protection_enabled/guardduty_s3_protection_enabled.metadata.json b/prowler/providers/aws/services/guardduty/guardduty_s3_protection_enabled/guardduty_s3_protection_enabled.metadata.json index 94687e6c99..62c7200f7d 100644 --- a/prowler/providers/aws/services/guardduty/guardduty_s3_protection_enabled/guardduty_s3_protection_enabled.metadata.json +++ b/prowler/providers/aws/services/guardduty/guardduty_s3_protection_enabled/guardduty_s3_protection_enabled.metadata.json @@ -15,7 +15,7 @@ "RelatedUrl": "https://docs.aws.amazon.com/guardduty/latest/ug/s3_detection.html", "Remediation": { "Code": { - "CLI": "aws guardduty update-detector --detector-id --data-sources '{\"S3Logs\": {\"Enable\": true}}'", + "CLI": "aws guardduty update-detector --detector-id --data-sources S3Logs={Enable=true}}'", "NativeIaC": "", "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/guardduty-controls.html#guardduty-10", "Terraform": "" diff --git a/prowler/providers/aws/services/guardduty/guardduty_service.py b/prowler/providers/aws/services/guardduty/guardduty_service.py index 0ee0f7d26c..5669ecfec6 100644 --- a/prowler/providers/aws/services/guardduty/guardduty_service.py +++ b/prowler/providers/aws/services/guardduty/guardduty_service.py @@ -13,7 +13,7 @@ class GuardDuty(AWSService): super().__init__(__class__.__name__, provider) self.detectors = [] self.__threading_call__(self._list_detectors) - self._get_detector() + self.__threading_call__(self._get_detector, self.detectors) self._list_findings() self._list_members() self._get_administrator_account() @@ -33,7 +33,10 @@ class GuardDuty(AWSService): ): self.detectors.append( Detector( - id=detector, arn=arn, region=regional_client.region + id=detector, + arn=arn, + region=regional_client.region, + enabled_in_account=True, ) ) if not detectors: @@ -50,38 +53,53 @@ class GuardDuty(AWSService): f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) - def _get_detector(self): + def _get_detector(self, detector): logger.info("GuardDuty - getting detector info...") try: - for detector in self.detectors: - try: - if detector.id and detector.enabled_in_account: - regional_client = self.regional_clients[detector.region] - detector_info = regional_client.get_detector( - DetectorId=detector.id - ) - if ( - "Status" in detector_info - and detector_info["Status"] == "ENABLED" - ): - detector.status = True + if detector.id and detector.enabled_in_account: + detector_info = self.regional_clients[detector.region].get_detector( + DetectorId=detector.id + ) + if detector_info.get("Status", "DISABLED") == "ENABLED": + detector.status = True - data_sources = detector_info.get("DataSources", {}) - s3_logs = data_sources.get("S3Logs", {}) - if s3_logs.get("Status") == "ENABLED": - detector.s3_protection = True + data_sources = detector_info.get("DataSources", {}) - for feat in detector_info.get("Features", []): - if ( - feat.get("Name") == "RDS_LOGIN_EVENTS" - and feat.get("Status", "DISABLED") == "ENABLED" - ): - detector.rds_protection = True + s3_logs = data_sources.get("S3Logs", {}) + if s3_logs.get("Status", "DISABLED") == "ENABLED": + detector.s3_protection = True + + detector.eks_audit_log_protection = ( + True + if data_sources.get("Kubernetes", {}) + .get("AuditLogs", {}) + .get("Status", "DISABLED") + == "ENABLED" + else False + ) + + detector.ec2_malware_protection = ( + True + if data_sources.get("MalwareProtection", {}) + .get("ScanEc2InstanceWithFindings", {}) + .get("EbsVolumes", {}) + .get("Status", "DISABLED") + == "ENABLED" + else False + ) + + for feat in detector_info.get("Features", []): + if ( + feat.get("Name", "") == "RDS_LOGIN_EVENTS" + and feat.get("Status", "DISABLED") == "ENABLED" + ): + detector.rds_protection = True + elif ( + feat.get("Name", "") == "LAMBDA_NETWORK_LOGS" + and feat.get("Status", "DISABLED") == "ENABLED" + ): + detector.lambda_protection = True - except Exception as error: - logger.error( - f"{error.__class__.__name__}:{error.__traceback__.tb_lineno} -- {error}" - ) except Exception as error: logger.error( f"{error.__class__.__name__}:{error.__traceback__.tb_lineno} -- {error}" @@ -196,7 +214,7 @@ class Detector(BaseModel): id: str arn: str region: str - enabled_in_account: bool = True + enabled_in_account: bool status: bool = None findings: list = [] member_accounts: list = [] @@ -204,3 +222,6 @@ class Detector(BaseModel): tags: Optional[list] = [] s3_protection: bool = False rds_protection: bool = False + eks_audit_log_protection: bool = False + lambda_protection: bool = False + ec2_malware_protection: bool = False diff --git a/prowler/providers/aws/services/iam/iam_check_saml_providers_sts/iam_check_saml_providers_sts.py b/prowler/providers/aws/services/iam/iam_check_saml_providers_sts/iam_check_saml_providers_sts.py index d096ed3e07..ea5c74fe78 100644 --- a/prowler/providers/aws/services/iam/iam_check_saml_providers_sts/iam_check_saml_providers_sts.py +++ b/prowler/providers/aws/services/iam/iam_check_saml_providers_sts/iam_check_saml_providers_sts.py @@ -5,17 +5,23 @@ from prowler.providers.aws.services.iam.iam_client import iam_client class iam_check_saml_providers_sts(Check): def execute(self) -> Check_Report_AWS: findings = [] - if iam_client.saml_providers: - for provider in iam_client.saml_providers: - report = Check_Report_AWS(self.metadata()) - provider_name = provider["Arn"].split("/")[1] - report.resource_id = provider_name - report.resource_arn = provider["Arn"] - report.region = iam_client.region - report.status = "PASS" - report.status_extended = ( - f"SAML Provider {provider_name} has been found." - ) - findings.append(report) + if not iam_client.saml_providers and iam_client.saml_providers is not None: + report = Check_Report_AWS(self.metadata()) + report.resource_id = iam_client.audited_account + report.resource_arn = iam_client.audited_account_arn + report.region = iam_client.region + report.status = "FAIL" + report.status_extended = "No SAML Providers found." + findings.append(report) + + for provider_arn, provider in iam_client.saml_providers.items(): + report = Check_Report_AWS(self.metadata()) + report.resource_id = provider.name + report.resource_arn = provider_arn + report.resource_tags = provider.tags + report.region = iam_client.region + report.status = "PASS" + report.status_extended = f"SAML Provider {provider.name} has been found." + findings.append(report) return findings diff --git a/prowler/providers/aws/services/iam/iam_no_expired_server_certificates_stored/iam_no_expired_server_certificates_stored.py b/prowler/providers/aws/services/iam/iam_no_expired_server_certificates_stored/iam_no_expired_server_certificates_stored.py index e99210e10a..00a952f82a 100644 --- a/prowler/providers/aws/services/iam/iam_no_expired_server_certificates_stored/iam_no_expired_server_certificates_stored.py +++ b/prowler/providers/aws/services/iam/iam_no_expired_server_certificates_stored/iam_no_expired_server_certificates_stored.py @@ -13,6 +13,7 @@ class iam_no_expired_server_certificates_stored(Check): report.region = iam_client.region report.resource_id = certificate.id report.resource_arn = certificate.arn + report.resource_tags = certificate.tags expiration_days = (datetime.now(timezone.utc) - certificate.expiration).days if expiration_days >= 0: report.status = "FAIL" diff --git a/prowler/providers/aws/services/iam/iam_policy_attached_only_to_group_or_roles/iam_policy_attached_only_to_group_or_roles.py b/prowler/providers/aws/services/iam/iam_policy_attached_only_to_group_or_roles/iam_policy_attached_only_to_group_or_roles.py index 9637471396..512faca0b0 100644 --- a/prowler/providers/aws/services/iam/iam_policy_attached_only_to_group_or_roles/iam_policy_attached_only_to_group_or_roles.py +++ b/prowler/providers/aws/services/iam/iam_policy_attached_only_to_group_or_roles/iam_policy_attached_only_to_group_or_roles.py @@ -16,6 +16,7 @@ class iam_policy_attached_only_to_group_or_roles(Check): report.status_extended = f"User {user.name} has the policy {policy['PolicyName']} attached." report.resource_id = f"{user.name}/{policy['PolicyName']}" report.resource_arn = user.arn + report.resource_tags = user.tags findings.append(report) if user.inline_policies: for policy in user.inline_policies: @@ -25,6 +26,7 @@ class iam_policy_attached_only_to_group_or_roles(Check): report.status_extended = f"User {user.name} has the inline policy {policy} attached." report.resource_id = f"{user.name}/{policy}" report.resource_arn = user.arn + report.resource_tags = user.tags findings.append(report) else: @@ -32,6 +34,7 @@ class iam_policy_attached_only_to_group_or_roles(Check): report.region = iam_client.region report.resource_id = user.name report.resource_arn = user.arn + report.resource_tags = user.tags report.status = "PASS" report.status_extended = ( f"User {user.name} has no inline or attached policies." 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 cc7b3bfbe7..3de61166c5 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 @@ -12,9 +12,15 @@ maximum_expiration_days = 90 class iam_rotate_access_key_90_days(Check): def execute(self) -> Check_Report_AWS: findings = [] - response = iam_client.credential_report - for user in response: + for user in iam_client.credential_report: + # Search user in iam_client.users to get tags + user_tags = [] + for iam_user in iam_client.users: + if iam_user.arn == user["arn"]: + user_tags = iam_user.tags + break + if ( user["access_key_1_last_rotated"] == "N/A" and user["access_key_2_last_rotated"] == "N/A" @@ -23,6 +29,7 @@ class iam_rotate_access_key_90_days(Check): report.region = iam_client.region report.resource_id = user["user"] report.resource_arn = user["arn"] + report.resource_tags = user_tags report.status = "PASS" report.status_extended = ( f"User {user['user']} does not have access keys." @@ -44,6 +51,7 @@ class iam_rotate_access_key_90_days(Check): report.region = iam_client.region report.resource_id = user["user"] report.resource_arn = user["arn"] + report.resource_tags = user_tags report.status = "FAIL" report.status_extended = f"User {user['user']} has not rotated access key 1 in over 90 days ({access_key_1_last_rotated.days} days)." findings.append(report) @@ -60,6 +68,7 @@ class iam_rotate_access_key_90_days(Check): report.region = iam_client.region report.resource_id = user["user"] report.resource_arn = user["arn"] + report.resource_tags = user_tags report.status = "FAIL" report.status_extended = f"User {user['user']} has not rotated access key 2 in over 90 days ({access_key_2_last_rotated.days} days)." findings.append(report) @@ -69,6 +78,7 @@ class iam_rotate_access_key_90_days(Check): report.region = iam_client.region report.resource_id = user["user"] report.resource_arn = user["arn"] + report.resource_tags = user_tags report.status = "PASS" report.status_extended = f"User {user['user']} does not have access keys older than 90 days." findings.append(report) diff --git a/prowler/providers/aws/services/iam/iam_service.py b/prowler/providers/aws/services/iam/iam_service.py index 565867e565..8bfcbd7ced 100644 --- a/prowler/providers/aws/services/iam/iam_service.py +++ b/prowler/providers/aws/services/iam/iam_service.py @@ -47,7 +47,6 @@ def is_service_role(role): return False -################## IAM class IAM(AWSService): def __init__(self, provider): # Call AWSService's __init__ @@ -89,13 +88,21 @@ class IAM(AWSService): self._list_inline_role_policies() self.saml_providers = self._list_saml_providers() self.server_certificates = self._list_server_certificates() - self._list_tags_for_resource() self.access_keys_metadata = {} self._get_access_keys_metadata() self.last_accessed_services = {} self._get_last_accessed_services() self.user_temporary_credentials_usage = {} self._get_user_temporary_credentials_usage() + # List missing tags + self.__threading_call__(self._list_tags, self.users) + self.__threading_call__(self._list_tags, self.roles) + self.__threading_call__( + self._list_tags, + [policy for policy in self.policies if policy.type == "Custom"], + ) + self.__threading_call__(self._list_tags, self.server_certificates) + self.__threading_call__(self._list_tags, self.saml_providers.values()) def _get_client(self): return self.client @@ -733,17 +740,31 @@ class IAM(AWSService): def _list_saml_providers(self): logger.info("IAM - List SAML Providers...") + saml_providers = {} try: - saml_providers = self.client.list_saml_providers()["SAMLProviderList"] + saml_providers_list = self.client.list_saml_providers()["SAMLProviderList"] + + for provider in saml_providers_list: + if not self.audit_resources or ( + is_resource_filtered(provider["Arn"], self.audit_resources) + ): + saml_providers[provider["Arn"]] = SAMLProvider( + name=provider["Arn"].split("/")[-1], arn=provider["Arn"] + ) + except ClientError as error: + logger.error( + f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + if error.response["Error"]["Code"] == "AccessDenied": + saml_providers = None except Exception as error: logger.error( f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) - saml_providers = None - finally: - return saml_providers - def _list_server_certificates(self): + return saml_providers + + def _list_server_certificates(self) -> list: logger.info("IAM - List Server Certificates...") try: server_certificates = [] @@ -768,71 +789,30 @@ class IAM(AWSService): finally: return server_certificates - def _list_tags_for_resource(self): + def _list_tags(self, resource: any): logger.info("IAM - List Tags...") try: - if self.roles: - for role in self.roles: - try: - response = self.client.list_role_tags(RoleName=role.name)[ - "Tags" - ] - role.tags = response - except ClientError as error: - if error.response["Error"]["Code"] == "NoSuchEntity": - role.tags = [] - else: - logger.error( - f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" - ) - except Exception as error: - logger.error( - f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" - ) - - except Exception as error: - logger.error( - f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" - ) - - try: - for user in self.users: - try: - response = self.client.list_user_tags(UserName=user.name)["Tags"] - user.tags = response - except ClientError as error: - if error.response["Error"]["Code"] == "NoSuchEntity": - user.tags = [] - else: - logger.error( - f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" - ) - - except Exception as error: - logger.error( - f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" - ) - - try: - for policy in self.policies: - try: - if policy.type != "Inline": - response = self.client.list_policy_tags(PolicyArn=policy.arn)[ - "Tags" - ] - policy.tags = response - except ClientError as error: - if error.response["Error"]["Code"] == "NoSuchEntity": - policy.tags = [] - else: - logger.error( - f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" - ) - except Exception as error: - logger.error( - f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" - ) - + if isinstance(resource, Role): + resource.tags = self.client.list_role_tags(RoleName=resource.name).get( + "Tags", [] + ) + elif isinstance(resource, User): + resource.tags = self.client.list_user_tags(UserName=resource.name).get( + "Tags", [] + ) + elif isinstance(resource, Policy): + if resource.type == "Custom": + resource.tags = self.client.list_policy_tags( + PolicyArn=resource.arn + ).get("Tags", []) + elif isinstance(resource, Certificate): + resource.tags = self.client.list_server_certificate_tags( + ServerCertificateName=resource.name + ).get("Tags", []) + elif isinstance(resource, SAMLProvider): + resource.tags = self.client.list_saml_provider_tags( + SAMLProviderArn=resource.arn + ).get("Tags", []) except Exception as error: logger.error( f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" @@ -952,7 +932,7 @@ class User(BaseModel): console_access: Optional[bool] attached_policies: list[dict] = [] inline_policies: list[str] = [] - tags: Optional[list] = [] + tags: Optional[list] class Role(BaseModel): @@ -962,7 +942,7 @@ class Role(BaseModel): is_service_role: bool attached_policies: list[dict] = [] inline_policies: list[str] = [] - tags: Optional[list] = [] + tags: Optional[list] class Group(BaseModel): @@ -991,6 +971,7 @@ class Certificate(BaseModel): id: str arn: str expiration: datetime + tags: Optional[list] class Policy(BaseModel): @@ -1002,3 +983,9 @@ class Policy(BaseModel): attached: bool document: Optional[dict] tags: Optional[list] = [] + + +class SAMLProvider(BaseModel): + name: str + arn: str + tags: Optional[list] diff --git a/prowler/providers/aws/services/iam/iam_user_accesskey_unused/iam_user_accesskey_unused.py b/prowler/providers/aws/services/iam/iam_user_accesskey_unused/iam_user_accesskey_unused.py index 5463585bc0..6267192d97 100644 --- a/prowler/providers/aws/services/iam/iam_user_accesskey_unused/iam_user_accesskey_unused.py +++ b/prowler/providers/aws/services/iam/iam_user_accesskey_unused/iam_user_accesskey_unused.py @@ -14,6 +14,13 @@ class iam_user_accesskey_unused(Check): ) findings = [] for user in iam_client.credential_report: + # Search user in iam_client.users to get tags + user_tags = [] + for iam_user in iam_client.users: + if iam_user.arn == user["arn"]: + user_tags = iam_user.tags + break + if ( user["access_key_1_active"] != "true" and user["access_key_2_active"] != "true" @@ -22,6 +29,7 @@ class iam_user_accesskey_unused(Check): report.region = iam_client.region report.resource_id = user["user"] report.resource_arn = user["arn"] + report.resource_tags = user_tags report.status = "PASS" report.status_extended = ( f"User {user['user']} does not have access keys." @@ -41,6 +49,7 @@ class iam_user_accesskey_unused(Check): report.region = iam_client.region report.resource_id = user["user"] + "/AccessKey1" report.resource_arn = user["arn"] + report.resource_tags = user_tags report.status = "FAIL" report.status_extended = f"User {user['user']} has not used access key 1 in the last {maximum_expiration_days} days ({access_key_1_last_used_date.days} days)." findings.append(report) @@ -56,6 +65,7 @@ class iam_user_accesskey_unused(Check): report.region = iam_client.region report.resource_id = user["user"] + "/AccessKey2" report.resource_arn = user["arn"] + report.resource_tags = user_tags report.status = "FAIL" report.status_extended = f"User {user['user']} has not used access key 2 in the last {maximum_expiration_days} days ({access_key_2_last_used_date.days} days)." findings.append(report) @@ -65,6 +75,7 @@ class iam_user_accesskey_unused(Check): report.region = iam_client.region report.resource_id = user["user"] report.resource_arn = user["arn"] + report.resource_tags = user_tags report.status = "PASS" report.status_extended = f"User {user['user']} does not have unused access keys for {maximum_expiration_days} days." findings.append(report) diff --git a/prowler/providers/aws/services/iam/iam_user_mfa_enabled_console_access/iam_user_mfa_enabled_console_access.py b/prowler/providers/aws/services/iam/iam_user_mfa_enabled_console_access/iam_user_mfa_enabled_console_access.py index a3c03a8ee4..a3695ec86a 100644 --- a/prowler/providers/aws/services/iam/iam_user_mfa_enabled_console_access/iam_user_mfa_enabled_console_access.py +++ b/prowler/providers/aws/services/iam/iam_user_mfa_enabled_console_access/iam_user_mfa_enabled_console_access.py @@ -13,6 +13,11 @@ class iam_user_mfa_enabled_console_access(Check): report.resource_id = user["user"] report.resource_arn = user["arn"] report.region = iam_client.region + # Search user in iam_client.users to get tags + for iam_user in iam_client.users: + if iam_user.arn == user["arn"]: + report.resource_tags = iam_user.tags + break # check if the user has password enabled if user["password_enabled"] == "true": if user["mfa_active"] == "false": diff --git a/prowler/providers/aws/services/iam/iam_user_no_setup_initial_access_key/iam_user_no_setup_initial_access_key.py b/prowler/providers/aws/services/iam/iam_user_no_setup_initial_access_key/iam_user_no_setup_initial_access_key.py index de506c1645..2c3aaa30ea 100644 --- a/prowler/providers/aws/services/iam/iam_user_no_setup_initial_access_key/iam_user_no_setup_initial_access_key.py +++ b/prowler/providers/aws/services/iam/iam_user_no_setup_initial_access_key/iam_user_no_setup_initial_access_key.py @@ -54,4 +54,9 @@ class iam_user_no_setup_initial_access_key(Check): report.resource_arn = user["arn"] report.status = status report.status_extended = status_extended + # Search user in iam_client.users to get tags + for iam_user in iam_client.users: + if iam_user.arn == user["arn"]: + report.resource_tags = iam_user.tags + break findings.append(report) diff --git a/prowler/providers/aws/services/iam/iam_user_two_active_access_key/iam_user_two_active_access_key.py b/prowler/providers/aws/services/iam/iam_user_two_active_access_key/iam_user_two_active_access_key.py index 93b661907b..94b40985b9 100644 --- a/prowler/providers/aws/services/iam/iam_user_two_active_access_key/iam_user_two_active_access_key.py +++ b/prowler/providers/aws/services/iam/iam_user_two_active_access_key/iam_user_two_active_access_key.py @@ -13,6 +13,11 @@ class iam_user_two_active_access_key(Check): report.resource_id = user["user"] report.resource_arn = user["arn"] report.region = iam_client.region + # Search user in iam_client.users to get tags + for iam_user in iam_client.users: + if iam_user.arn == user["arn"]: + report.resource_tags = iam_user.tags + break if ( user["access_key_1_active"] == "true" and user["access_key_2_active"] == "true" diff --git a/prowler/providers/aws/services/iam/iam_user_with_temporary_credentials/iam_user_with_temporary_credentials.py b/prowler/providers/aws/services/iam/iam_user_with_temporary_credentials/iam_user_with_temporary_credentials.py index d3657d5313..b352188cc0 100644 --- a/prowler/providers/aws/services/iam/iam_user_with_temporary_credentials/iam_user_with_temporary_credentials.py +++ b/prowler/providers/aws/services/iam/iam_user_with_temporary_credentials/iam_user_with_temporary_credentials.py @@ -17,6 +17,11 @@ class iam_user_with_temporary_credentials(Check): report.resource_id = user_name report.resource_arn = user_arn report.region = iam_client.region + # Search user in iam_client.users to get tags + for iam_user in iam_client.users: + if iam_user.arn == user_arn: + report.resource_tags = iam_user.tags + break report.status = "PASS" report.status_extended = f"User {user_name} doesn't have long lived credentials with access to other services than IAM or STS." diff --git a/prowler/providers/aws/services/kinesis/kinesis_client.py b/prowler/providers/aws/services/kinesis/kinesis_client.py new file mode 100644 index 0000000000..294f8d5e90 --- /dev/null +++ b/prowler/providers/aws/services/kinesis/kinesis_client.py @@ -0,0 +1,4 @@ +from prowler.providers.aws.services.kinesis.kinesis_service import Kinesis +from prowler.providers.common.provider import Provider + +kinesis_client = Kinesis(Provider.get_global_provider()) diff --git a/prowler/providers/aws/services/kinesis/kinesis_service.py b/prowler/providers/aws/services/kinesis/kinesis_service.py new file mode 100644 index 0000000000..530710b965 --- /dev/null +++ b/prowler/providers/aws/services/kinesis/kinesis_service.py @@ -0,0 +1,95 @@ +from enum import Enum +from typing import Optional + +from pydantic import BaseModel + +from prowler.lib.logger import logger +from prowler.lib.scan_filters.scan_filters import is_resource_filtered +from prowler.providers.aws.lib.service.service import AWSService + + +class Kinesis(AWSService): + def __init__(self, provider): + # Call AWSService's __init__ + super().__init__(__class__.__name__, provider) + self.streams = {} + self.__threading_call__(self._list_streams) + self.__threading_call__(self._describe_stream, self.streams.values()) + self.__threading_call__(self._list_tags_for_stream, self.streams.values()) + + def _list_streams(self, regional_client): + logger.info("Kinesis - Listing Kinesis Streams...") + try: + list_streams_paginator = regional_client.get_paginator("list_streams") + for page in list_streams_paginator.paginate(): + for stream in page["StreamSummaries"]: + arn = stream["StreamARN"] + if not self.audit_resources or ( + is_resource_filtered(arn, self.audit_resources) + ): + self.streams[arn] = Stream( + arn=arn, + name=stream["StreamName"], + region=regional_client.region, + status=StreamStatus(stream.get("StreamStatus", "ACTIVE")), + ) + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def _describe_stream(self, stream): + logger.info(f"Kinesis - Describing Stream {stream.name}...") + try: + stream_description = ( + self.regional_clients[stream.region] + .describe_stream(StreamName=stream.name) + .get("StreamDescription", {}) + ) + stream.encrypted_at_rest = EncryptionType( + stream_description.get("EncryptionType", "NONE") + ) + except Exception as error: + logger.error( + f"{stream.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def _list_tags_for_stream(self, stream): + logger.info(f"Kinesis - Listing tags for Stream {stream.name}...") + try: + stream.tags = ( + self.regional_clients[stream.region] + .list_tags_for_stream(StreamName=stream.name) + .get("Tags", []) + ) + except Exception as error: + logger.error( + f"{stream.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + +class EncryptionType(Enum): + """Enum for Kinesis Stream Encryption Type""" + + NONE = "NONE" + KMS = "KMS" + + +class StreamStatus(Enum): + """Enum for Kinesis Stream Status""" + + ACTIVE = "ACTIVE" + CREATING = "CREATING" + DELETING = "DELETING" + UPDATING = "UPDATING" + + +class Stream(BaseModel): + """Model for Kinesis Stream""" + + arn: str + region: str + name: str + status: StreamStatus + tags: Optional[list] + encrypted_at_rest: EncryptionType = EncryptionType.NONE diff --git a/prowler/providers/aws/services/kinesis/kinesis_stream_encrypted_at_rest/__init__.py b/prowler/providers/aws/services/kinesis/kinesis_stream_encrypted_at_rest/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/kinesis/kinesis_stream_encrypted_at_rest/kinesis_stream_encrypted_at_rest.metadata.json b/prowler/providers/aws/services/kinesis/kinesis_stream_encrypted_at_rest/kinesis_stream_encrypted_at_rest.metadata.json new file mode 100644 index 0000000000..4741297398 --- /dev/null +++ b/prowler/providers/aws/services/kinesis/kinesis_stream_encrypted_at_rest/kinesis_stream_encrypted_at_rest.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "kinesis_stream_encrypted_at_rest", + "CheckTitle": "Kinesis streams should be encrypted at rest.", + "CheckType": [ + "Software and Configuration Checks/Industry and Regulatory Standards/NIST 800-53 Controls" + ], + "ServiceName": "kinesis", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:kinesis::account-id:stream/stream-name", + "Severity": "medium", + "ResourceType": "AwsKinesisStream", + "Description": "Ensure Kinesis streams use server-side encryption with AWS KMS keys for data protection.", + "Risk": "If Kinesis streams are not encrypted at rest, sensitive data stored in the stream could be exposed to unauthorized access or breaches. This could lead to potential data theft or misuse of unencrypted data.", + "RelatedUrl": "https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingKMSEncryption.html", + "Remediation": { + "Code": { + "CLI": "aws kinesis start-stream-encryption --stream-name --encryption-type KMS --key-id ", + "NativeIaC": "https://docs.prowler.com/checks/aws/general-policies/bc_aws_general_22/#cloudformation", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/kinesis-controls.html#kinesis-1", + "Terraform": "https://docs.prowler.com/checks/aws/general-policies/bc_aws_general_22/#terraform" + }, + "Recommendation": { + "Text": "Enable server-side encryption for Kinesis streams using AWS KMS keys to ensure that all data is encrypted before it is stored, protecting data at rest and reducing the risk of unauthorized access.", + "Url": "https://docs.aws.amazon.com/streams/latest/dev/getting-started-with-sse.html" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/kinesis/kinesis_stream_encrypted_at_rest/kinesis_stream_encrypted_at_rest.py b/prowler/providers/aws/services/kinesis/kinesis_stream_encrypted_at_rest/kinesis_stream_encrypted_at_rest.py new file mode 100644 index 0000000000..af46a212c4 --- /dev/null +++ b/prowler/providers/aws/services/kinesis/kinesis_stream_encrypted_at_rest/kinesis_stream_encrypted_at_rest.py @@ -0,0 +1,28 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.kinesis.kinesis_client import kinesis_client +from prowler.providers.aws.services.kinesis.kinesis_service import EncryptionType + + +class kinesis_stream_encrypted_at_rest(Check): + def execute(self): + findings = [] + for stream in kinesis_client.streams.values(): + report = Check_Report_AWS(self.metadata()) + report.region = stream.region + report.resource_id = stream.name + report.resource_arn = stream.arn + report.resource_tags = stream.tags + report.status = "FAIL" + report.status_extended = ( + f"Kinesis Stream {stream.name} is not encrypted at rest." + ) + + if stream.encrypted_at_rest == EncryptionType.KMS: + report.status = "PASS" + report.status_extended = ( + f"Kinesis Stream {stream.name} is encrypted at rest." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/networkfirewall/networkfirewall_logging_enabled/__init__.py b/prowler/providers/aws/services/networkfirewall/networkfirewall_logging_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/networkfirewall/networkfirewall_logging_enabled/networkfirewall_logging_enabled.metadata.json b/prowler/providers/aws/services/networkfirewall/networkfirewall_logging_enabled/networkfirewall_logging_enabled.metadata.json new file mode 100644 index 0000000000..e5a687ed90 --- /dev/null +++ b/prowler/providers/aws/services/networkfirewall/networkfirewall_logging_enabled/networkfirewall_logging_enabled.metadata.json @@ -0,0 +1,34 @@ +{ + "Provider": "aws", + "CheckID": "networkfirewall_logging_enabled", + "CheckTitle": "Ensure Network Firewall Logging is Enabled", + "CheckType": [ + "Software and Configuration Checks/Industry and Regulatory Standards/NIST 800-53" + ], + "ServiceName": "network-firewall", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:network-firewall::account-id:firewall/firewall-name", + "Severity": "medium", + "ResourceType": "AwsNetworkFirewallFirewall", + "Description": "This control checks whether logging is enabled for an AWS Network Firewall firewall. The control fails if logging isn't enabled for at least one log type or if the logging destination doesn't exist.", + "Risk": "Failing to enable logging on an AWS Network Firewall can lead to a lack of visibility into network traffic, making it difficult to monitor and respond to security incidents effectively, which could jeopardize the security and integrity of your infrastructure.", + "RelatedUrl": "https://docs.aws.amazon.com/network-firewall/latest/developerguide/firewall-logging.html", + "Remediation": { + "Code": { + "CLI": "aws network-firewall update-logging-configuration --firewall-arn --logging-configuration ", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/networkfirewall-controls.html#networkfirewall-2", + "Terraform": "" + }, + "Recommendation": { + "Text": "Enable logging for your AWS Network Firewall by updating its logging configuration to ensure comprehensive tracking of network traffic and facilitate better incident response and auditing capabilities.", + "Url": "https://docs.aws.amazon.com/network-firewall/latest/developerguide/firewall-update-logging-configuration.html" + } + }, + "Categories": [ + "logging" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/networkfirewall/networkfirewall_logging_enabled/networkfirewall_logging_enabled.py b/prowler/providers/aws/services/networkfirewall/networkfirewall_logging_enabled/networkfirewall_logging_enabled.py new file mode 100644 index 0000000000..24f59a4e05 --- /dev/null +++ b/prowler/providers/aws/services/networkfirewall/networkfirewall_logging_enabled/networkfirewall_logging_enabled.py @@ -0,0 +1,31 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.networkfirewall.networkfirewall_client import ( + networkfirewall_client, +) + + +class networkfirewall_logging_enabled(Check): + def execute(self): + findings = [] + for arn, firewall in networkfirewall_client.network_firewalls.items(): + report = Check_Report_AWS(self.metadata()) + report.region = firewall.region + report.resource_id = firewall.name + report.resource_arn = arn + report.resource_tags = firewall.tags + report.status = "FAIL" + report.status_extended = ( + f"Network Firewall {firewall.name} does not have logging enabled." + ) + + for configuration in firewall.logging_configuration: + if configuration.log_type or configuration.log_destination: + report.status = "PASS" + report.status_extended = ( + f"Network Firewall {firewall.name} has logging enabled." + ) + break + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/networkfirewall/networkfirewall_multi_az/__init__.py b/prowler/providers/aws/services/networkfirewall/networkfirewall_multi_az/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/networkfirewall/networkfirewall_multi_az/networkfirewall_multi_az.metadata.json b/prowler/providers/aws/services/networkfirewall/networkfirewall_multi_az/networkfirewall_multi_az.metadata.json new file mode 100644 index 0000000000..b9d7964461 --- /dev/null +++ b/prowler/providers/aws/services/networkfirewall/networkfirewall_multi_az/networkfirewall_multi_az.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "networkfirewall_multi_az", + "CheckTitle": "Ensure all Network Firewall Firewalls are deployed across multiple AZ.", + "CheckType": [ + "Software and Configuration Checks/Industry and Regulatory Standards/NIST 800-53 Controls" + ], + "ServiceName": "network-firewall", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:network-firewall::account-id:firewall/firewall-name", + "Severity": "medium", + "ResourceType": "AwsNetworkFirewallFirewall", + "Description": "Ensure all Network Firewall Firewalls are deployed in at least 2 Availability Zones.", + "Risk": "Deploying a Network Firewall in a single Availability Zone increases the risk of service disruption if that AZ experiences an outage, potentially leaving your network vulnerable to attacks or downtime.", + "RelatedUrl": "https://docs.aws.amazon.com/network-firewall/latest/developerguide/arch-two-zone-igw.html", + "Remediation": { + "Code": { + "CLI": "aws network-firewall update-firewall-delete-protection --firewall-arn --firewall-name --subnet-mappings SubnetId=,SubnetId=", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/networkfirewall-controls.html#networkfirewall-1", + "Terraform": "" + }, + "Recommendation": { + "Text": "Deploy your AWS Network Firewall across multiple Availability Zones to enhance availability, ensure traffic load balancing, and minimize the impact of any AZ-specific failures.", + "Url": "https://aws.amazon.com/es/blogs/networking-and-content-delivery/deployment-models-for-aws-network-firewall/" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/networkfirewall/networkfirewall_multi_az/networkfirewall_multi_az.py b/prowler/providers/aws/services/networkfirewall/networkfirewall_multi_az/networkfirewall_multi_az.py new file mode 100644 index 0000000000..da31c55b35 --- /dev/null +++ b/prowler/providers/aws/services/networkfirewall/networkfirewall_multi_az/networkfirewall_multi_az.py @@ -0,0 +1,29 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.networkfirewall.networkfirewall_client import ( + networkfirewall_client, +) + + +class networkfirewall_multi_az(Check): + def execute(self): + findings = [] + for firewall in networkfirewall_client.network_firewalls.values(): + report = Check_Report_AWS(self.metadata()) + report.region = firewall.region + report.resource_id = firewall.name + report.resource_arn = firewall.arn + report.resource_tags = firewall.tags + report.status = "FAIL" + report.status_extended = ( + f"Network Firewall {firewall.name} is not deployed across multiple AZ." + ) + + if len(firewall.subnet_mappings) > 1: + report.status = "PASS" + report.status_extended = ( + f"Network Firewall {firewall.name} is deployed across multiple AZ." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/networkfirewall/networkfirewall_policy_default_action_fragmented_packets/__init__.py b/prowler/providers/aws/services/networkfirewall/networkfirewall_policy_default_action_fragmented_packets/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/networkfirewall/networkfirewall_policy_default_action_fragmented_packets/networkfirewall_policy_default_action_fragmented_packets.metadata.json b/prowler/providers/aws/services/networkfirewall/networkfirewall_policy_default_action_fragmented_packets/networkfirewall_policy_default_action_fragmented_packets.metadata.json new file mode 100644 index 0000000000..983201b0c5 --- /dev/null +++ b/prowler/providers/aws/services/networkfirewall/networkfirewall_policy_default_action_fragmented_packets/networkfirewall_policy_default_action_fragmented_packets.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "networkfirewall_policy_default_action_fragmented_packets", + "CheckTitle": "Default action for fragmented packets is set to drop or forward.", + "CheckType": [ + "Software and Configuration Checks/Industry and Regulatory Standards/NIST 800-53 Controls" + ], + "ServiceName": "network-firewall", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:network-firewall::account-id:firewall/firewall-name", + "Severity": "medium", + "ResourceType": "AwsNetworkFirewallFirewall", + "Description": "NetorwFirewall firewall policy default action for fragmented packets is set to drop or forward.", + "Risk": "If the default action for fragmented packets is set to 'Pass', unintended or potentially malicious traffic may bypass filtering, exposing the network to security threats.", + "RelatedUrl": "https://docs.aws.amazon.com/network-firewall/latest/developerguide/stateless-default-actions.html", + "Remediation": { + "Code": { + "CLI": "aws network-firewall update-firewall-policy --firewall-policy-arn --stateless-default-actions aws:drop", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/networkfirewall-controls.html#networkfirewall-5", + "Terraform": "" + }, + "Recommendation": { + "Text": "Update the firewall policy to ensure fragmented packets are either dropped or forwarded to stateful rule groups for additional filtering, reducing the risk of unwanted traffic entering the network.", + "Url": "https://docs.aws.amazon.com/network-firewall/latest/developerguide/firewall-policy-updating.html" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/networkfirewall/networkfirewall_policy_default_action_fragmented_packets/networkfirewall_policy_default_action_fragmented_packets.py b/prowler/providers/aws/services/networkfirewall/networkfirewall_policy_default_action_fragmented_packets/networkfirewall_policy_default_action_fragmented_packets.py new file mode 100644 index 0000000000..5b7884a969 --- /dev/null +++ b/prowler/providers/aws/services/networkfirewall/networkfirewall_policy_default_action_fragmented_packets/networkfirewall_policy_default_action_fragmented_packets.py @@ -0,0 +1,28 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.networkfirewall.networkfirewall_client import ( + networkfirewall_client, +) + + +class networkfirewall_policy_default_action_fragmented_packets(Check): + def execute(self): + findings = [] + for arn, firewall in networkfirewall_client.network_firewalls.items(): + report = Check_Report_AWS(self.metadata()) + report.region = firewall.region + report.resource_id = firewall.name + report.resource_arn = arn + report.resource_tags = firewall.tags + report.status = "FAIL" + report.status_extended = f"Network Firewall {firewall.name} policy does not drop or forward fragmented packets by default." + + if ( + "aws:drop" in firewall.default_stateless_frag_actions + or "aws:forward_to_sfe" in firewall.default_stateless_frag_actions + ): + report.status = "PASS" + report.status_extended = f"Network Firewall {firewall.name} policy does drop or forward fragmented packets by default." + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/networkfirewall/networkfirewall_policy_default_action_full_packets/__init__.py b/prowler/providers/aws/services/networkfirewall/networkfirewall_policy_default_action_full_packets/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/networkfirewall/networkfirewall_policy_default_action_full_packets/networkfirewall_policy_default_action_full_packets.metadata.json b/prowler/providers/aws/services/networkfirewall/networkfirewall_policy_default_action_full_packets/networkfirewall_policy_default_action_full_packets.metadata.json new file mode 100644 index 0000000000..118f5c19c2 --- /dev/null +++ b/prowler/providers/aws/services/networkfirewall/networkfirewall_policy_default_action_full_packets/networkfirewall_policy_default_action_full_packets.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "networkfirewall_policy_default_action_full_packets", + "CheckTitle": "NetworkFirewall firewall policy default action for full packets is set to drop or forward.", + "CheckType": [ + "Software and Configuration Checks/Industry and Regulatory Standards/NIST 800-53 Controls" + ], + "ServiceName": "network-firewall", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:network-firewall::account-id:firewall/firewall-name", + "Severity": "medium", + "ResourceType": "AwsNetworkFirewallFirewall", + "Description": "Default stateless action for full packets must be set to drop or forward in AWS Network Firewall policy.", + "Risk": "If the default stateless action for full packets is set to 'Pass', unintended traffic may bypass network filtering, leading to potential security vulnerabilities by allowing unauthorized or harmful packets.", + "RelatedUrl": "https://docs.aws.amazon.com/network-firewall/latest/developerguide/stateless-default-actions.html", + "Remediation": { + "Code": { + "CLI": "aws network-firewall update-firewall-policy --firewall-policy-arn --stateless-default-actions aws:drop", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/networkfirewall-controls.html#networkfirewall-4", + "Terraform": "" + }, + "Recommendation": { + "Text": "Modify the firewall policy to ensure that full packets are either dropped or forwarded to stateful rule groups for further inspection, mitigating the risk of unauthorized traffic entering the network.", + "Url": "https://docs.aws.amazon.com/network-firewall/latest/developerguide/firewall-policy-updating.html" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/networkfirewall/networkfirewall_policy_default_action_full_packets/networkfirewall_policy_default_action_full_packets.py b/prowler/providers/aws/services/networkfirewall/networkfirewall_policy_default_action_full_packets/networkfirewall_policy_default_action_full_packets.py new file mode 100644 index 0000000000..3611ee8991 --- /dev/null +++ b/prowler/providers/aws/services/networkfirewall/networkfirewall_policy_default_action_full_packets/networkfirewall_policy_default_action_full_packets.py @@ -0,0 +1,28 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.networkfirewall.networkfirewall_client import ( + networkfirewall_client, +) + + +class networkfirewall_policy_default_action_full_packets(Check): + def execute(self): + findings = [] + for arn, firewall in networkfirewall_client.network_firewalls.items(): + report = Check_Report_AWS(self.metadata()) + report.region = firewall.region + report.resource_id = firewall.name + report.resource_arn = arn + report.resource_tags = firewall.tags + report.status = "FAIL" + report.status_extended = f"Network Firewall {firewall.name} policy does not drop or forward full packets by default." + + if ( + "aws:drop" in firewall.default_stateless_actions + or "aws:forward_to_sfe" in firewall.default_stateless_actions + ): + report.status = "PASS" + report.status_extended = f"Network Firewall {firewall.name} policy does drop or forward full packets by default." + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/networkfirewall/networkfirewall_service.py b/prowler/providers/aws/services/networkfirewall/networkfirewall_service.py index d140c367a2..7ed0bcfbc4 100644 --- a/prowler/providers/aws/services/networkfirewall/networkfirewall_service.py +++ b/prowler/providers/aws/services/networkfirewall/networkfirewall_service.py @@ -1,3 +1,6 @@ +from enum import Enum +from typing import Optional + from pydantic import BaseModel from prowler.lib.logger import logger @@ -17,6 +20,9 @@ class NetworkFirewall(AWSService): self.__threading_call__( self._describe_firewall_policy, self.network_firewalls.values() ) + self.__threading_call__( + self._describe_logging_configuration, self.network_firewalls.values() + ) def _list_firewalls(self, regional_client): logger.info("Network Firewall - Listing Network Firewalls...") @@ -31,9 +37,8 @@ class NetworkFirewall(AWSService): network_firewall["FirewallArn"], self.audit_resources ) ): - self.network_firewalls[ - network_firewall.get("FirewallArn", "") - ] = Firewall( + arn = network_firewall.get("FirewallArn", "") + self.network_firewalls[arn] = Firewall( arn=network_firewall.get("FirewallArn"), region=regional_client.region, name=network_firewall.get("FirewallName"), @@ -58,6 +63,16 @@ class NetworkFirewall(AWSService): network_firewall.deletion_protection = describe_firewall.get( "DeleteProtection", False ) + for subnet in describe_firewall.get("SubnetMappings", []): + if subnet.get("SubnetId"): + network_firewall.subnet_mappings.append( + Subnet( + subnet_id=subnet.get("SubnetId"), + ip_addr_type=subnet.get( + "IPAddressType", IPAddressType.IPV4 + ), + ) + ) except Exception as error: logger.error( f"{error.__class__.__name__}:{error.__traceback__.tb_lineno} -- {error}" @@ -79,13 +94,93 @@ class NetworkFirewall(AWSService): group.get("ResourceArn", "") for group in firewall_policy.get("StatefulRuleGroupReferences", []) ] + network_firewall.default_stateless_actions = firewall_policy.get( + "StatelessDefaultActions", [] + ) + network_firewall.default_stateless_frag_actions = firewall_policy.get( + "StatelessFragmentDefaultActions", [] + ) + except Exception as error: + logger.error( + f"{error.__class__.__name__}:{error.__traceback__.tb_lineno} -- {error}" + ) + + def _describe_logging_configuration(self, network_firewall): + logger.info( + "Network Firewall - Describe Network Firewalls Logging Configuration..." + ) + try: + describe_logging_configuration = ( + self.regional_clients[network_firewall.region] + .describe_logging_configuration(FirewallArn=network_firewall.arn) + .get("LoggingConfiguration", {}) + ) + destination_configs = describe_logging_configuration.get( + "LogDestinationConfigs", [] + ) + network_firewall.logging_configuration = [] + if destination_configs: + for log_destination_config in destination_configs: + log_type = LogType(log_destination_config.get("LogType", "FLOW")) + log_destination_type = LogDestinationType( + log_destination_config.get("LogDestinationType", "S3") + ) + log_destination = log_destination_config.get("LogDestination", {}) + network_firewall.logging_configuration.append( + LoggingConfiguration( + log_type=log_type, + log_destination_type=log_destination_type, + log_destination=log_destination, + ) + ) except Exception as error: logger.error( f"{error.__class__.__name__}:{error.__traceback__.tb_lineno} -- {error}" ) +class LogType(Enum): + """Log Type for Network Firewall""" + + alert = "ALERT" + flow = "FLOW" + tls = "TLS" + + +class LogDestinationType(Enum): + """Log Destination Type for Network Firewall""" + + s3 = "S3" + cloudwatch_logs = "CloudWatchLogs" + kinesis_data_firehose = "KinesisDataFirehose" + + +class LoggingConfiguration(BaseModel): + """Logging Configuration for Network Firewall""" + + log_type: LogType + log_destination_type: LogDestinationType + log_destination: dict = {} + + +class IPAddressType(Enum): + """Enum for IP Address Type""" + + IPV4 = "IPV4" + IPV6 = "IPV6" + DUALSTACK = "DUALSTACK" + + +class Subnet(BaseModel): + """Subnet model for SubnetMappings""" + + subnet_id: str + ip_addr_type: IPAddressType + + class Firewall(BaseModel): + """Firewall Model for Network Firewall""" + arn: str name: str region: str @@ -94,5 +189,9 @@ class Firewall(BaseModel): tags: list = [] encryption_type: str = None deletion_protection: bool = False + default_stateless_actions: list = [] + default_stateless_frag_actions: list = [] + subnet_mappings: list[Subnet] = [] + logging_configuration: Optional[list[LoggingConfiguration]] stateless_rule_groups: list[str] = [] stateful_rule_groups: list[str] = [] diff --git a/prowler/providers/aws/services/redshift/redshift_cluster_encrypted_at_rest/__init__.py b/prowler/providers/aws/services/redshift/redshift_cluster_encrypted_at_rest/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/redshift/redshift_cluster_encrypted_at_rest/redshift_cluster_encrypted_at_rest.metadata.json b/prowler/providers/aws/services/redshift/redshift_cluster_encrypted_at_rest/redshift_cluster_encrypted_at_rest.metadata.json new file mode 100644 index 0000000000..ca03c9b08b --- /dev/null +++ b/prowler/providers/aws/services/redshift/redshift_cluster_encrypted_at_rest/redshift_cluster_encrypted_at_rest.metadata.json @@ -0,0 +1,34 @@ +{ + "Provider": "aws", + "CheckID": "redshift_cluster_encrypted_at_rest", + "CheckTitle": "Check if Redshift clusters are encrypted at rest.", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "redshift", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:redshift:region:account-id:cluster/cluster-name", + "Severity": "medium", + "ResourceType": "AwsRedshiftCluster", + "Description": "This control checks whether Amazon Redshift clusters are encrypted at rest. The control fails if a Redshift cluster isn't encrypted at rest.", + "Risk": "Without encryption at rest, sensitive data stored in Redshift clusters is vulnerable to unauthorized access, which could lead to data breaches and regulatory non-compliance.", + "RelatedUrl": "https://docs.aws.amazon.com/redshift/latest/mgmt/working-with-db-encryption.html", + "Remediation": { + "Code": { + "CLI": "aws redshift modify-cluster --cluster-identifier --encrypted --kms-key-id ", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/redshift-controls.html#redshift-10", + "Terraform": "" + }, + "Recommendation": { + "Text": "Enable encryption at rest for your Redshift clusters using KMS to protect sensitive data from unauthorized access.", + "Url": "https://docs.aws.amazon.com/redshift/latest/mgmt/changing-cluster-encryption.html" + } + }, + "Categories": [ + "encryption" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/redshift/redshift_cluster_encrypted_at_rest/redshift_cluster_encrypted_at_rest.py b/prowler/providers/aws/services/redshift/redshift_cluster_encrypted_at_rest/redshift_cluster_encrypted_at_rest.py new file mode 100644 index 0000000000..cd34794444 --- /dev/null +++ b/prowler/providers/aws/services/redshift/redshift_cluster_encrypted_at_rest/redshift_cluster_encrypted_at_rest.py @@ -0,0 +1,26 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.redshift.redshift_client import redshift_client + + +class redshift_cluster_encrypted_at_rest(Check): + def execute(self): + findings = [] + for cluster in redshift_client.clusters: + report = Check_Report_AWS(self.metadata()) + report.region = cluster.region + report.resource_id = cluster.id + report.resource_arn = cluster.arn + report.resource_tags = cluster.tags + report.status = "FAIL" + report.status_extended = ( + f"Redshift Cluster {cluster.id} is not encrypted at rest." + ) + if cluster.encrypted: + report.status = "PASS" + report.status_extended = ( + f"Redshift Cluster {cluster.id} is encrypted at rest." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/redshift/redshift_cluster_in_transit_encryption_enabled/__init__.py b/prowler/providers/aws/services/redshift/redshift_cluster_in_transit_encryption_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/redshift/redshift_cluster_in_transit_encryption_enabled/redshift_cluster_in_transit_encryption_enabled.metadata.json b/prowler/providers/aws/services/redshift/redshift_cluster_in_transit_encryption_enabled/redshift_cluster_in_transit_encryption_enabled.metadata.json new file mode 100644 index 0000000000..7a959883b8 --- /dev/null +++ b/prowler/providers/aws/services/redshift/redshift_cluster_in_transit_encryption_enabled/redshift_cluster_in_transit_encryption_enabled.metadata.json @@ -0,0 +1,34 @@ +{ + "Provider": "aws", + "CheckID": "redshift_cluster_in_transit_encryption_enabled", + "CheckTitle": "Check if connections to Amazon Redshift clusters are encrypted in transit.", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "redshift", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:redshift:region:account-id:cluster/cluster-name", + "Severity": "medium", + "ResourceType": "AwsRedshiftCluster", + "Description": "This control checks whether connections to Amazon Redshift clusters are required to use encryption in transit. The control fails if the Redshift cluster parameter 'require_SSL' isn't set to True.", + "Risk": "Without encryption in transit, connections to the Redshift cluster are vulnerable to eavesdropping or person-in-the-middle attacks, exposing sensitive data to unauthorized access.", + "RelatedUrl": "https://docs.aws.amazon.com/redshift/latest/mgmt/security-encryption-in-transit.html", + "Remediation": { + "Code": { + "CLI": "aws redshift modify-cluster-parameter-group --parameter-group-name --parameters ParameterName=require_ssl,ParameterValue=true,ApplyType=static", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/redshift-controls.html#redshift-2", + "Terraform": "" + }, + "Recommendation": { + "Text": "Ensure that connections to Amazon Redshift clusters use encryption in transit by setting the 'require_ssl' parameter to True.", + "Url": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/Redshift/redshift-parameter-groups-require-ssl.html" + } + }, + "Categories": [ + "encryption" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/redshift/redshift_cluster_in_transit_encryption_enabled/redshift_cluster_in_transit_encryption_enabled.py b/prowler/providers/aws/services/redshift/redshift_cluster_in_transit_encryption_enabled/redshift_cluster_in_transit_encryption_enabled.py new file mode 100644 index 0000000000..0e546953a3 --- /dev/null +++ b/prowler/providers/aws/services/redshift/redshift_cluster_in_transit_encryption_enabled/redshift_cluster_in_transit_encryption_enabled.py @@ -0,0 +1,26 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.redshift.redshift_client import redshift_client + + +class redshift_cluster_in_transit_encryption_enabled(Check): + def execute(self): + findings = [] + for cluster in redshift_client.clusters: + report = Check_Report_AWS(self.metadata()) + report.region = cluster.region + report.resource_id = cluster.id + report.resource_arn = cluster.arn + report.resource_tags = cluster.tags + report.status = "FAIL" + report.status_extended = ( + f"Redshift Cluster {cluster.id} is not encrypted in transit." + ) + if cluster.require_ssl: + report.status = "PASS" + report.status_extended = ( + f"Redshift Cluster {cluster.id} is encrypted in transit." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/redshift/redshift_cluster_non_default_database_name/__init__.py b/prowler/providers/aws/services/redshift/redshift_cluster_non_default_database_name/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/redshift/redshift_cluster_non_default_database_name/redshift_cluster_non_default_database_name.metadata.json b/prowler/providers/aws/services/redshift/redshift_cluster_non_default_database_name/redshift_cluster_non_default_database_name.metadata.json new file mode 100644 index 0000000000..96952a5be4 --- /dev/null +++ b/prowler/providers/aws/services/redshift/redshift_cluster_non_default_database_name/redshift_cluster_non_default_database_name.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "redshift_cluster_non_default_database_name", + "CheckTitle": "Check if Redshift clusters are using the default database name.", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "redshift", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:redshift:region:account-id:cluster/cluster-name", + "Severity": "medium", + "ResourceType": "AwsRedshiftCluster", + "Description": "This control checks whether an Amazon Redshift cluster has changed the database name from its default value. The control fails if the database name is set to 'dev'.", + "Risk": "Using the default database name 'dev' increases the risk of unintended access, as it is publicly known and could be used in IAM policy conditions to inadvertently allow access.", + "RelatedUrl": "https://docs.aws.amazon.com/redshift/latest/gsg/getting-started.html", + "Remediation": { + "Code": { + "CLI": "aws redshift create-cluster --cluster-identifier --db-name ", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/redshift-controls.html#redshift-9", + "Terraform": "" + }, + "Recommendation": { + "Text": "Create a new Redshift cluster with a unique database name to replace the default 'dev' database name.", + "Url": "https://docs.aws.amazon.com/redshift/latest/gsg/getting-started.html" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/redshift/redshift_cluster_non_default_database_name/redshift_cluster_non_default_database_name.py b/prowler/providers/aws/services/redshift/redshift_cluster_non_default_database_name/redshift_cluster_non_default_database_name.py new file mode 100644 index 0000000000..f236e90990 --- /dev/null +++ b/prowler/providers/aws/services/redshift/redshift_cluster_non_default_database_name/redshift_cluster_non_default_database_name.py @@ -0,0 +1,22 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.redshift.redshift_client import redshift_client + + +class redshift_cluster_non_default_database_name(Check): + def execute(self): + findings = [] + for cluster in redshift_client.clusters: + report = Check_Report_AWS(self.metadata()) + report.region = cluster.region + report.resource_id = cluster.id + report.resource_arn = cluster.arn + report.resource_tags = cluster.tags + report.status = "PASS" + report.status_extended = f"Redshift Cluster {cluster.id} does not have the default database name." + if cluster.database_name == "dev": + report.status = "FAIL" + report.status_extended = f"Redshift Cluster {cluster.id} has the default database name: {cluster.database_name}." + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/redshift/redshift_cluster_non_default_username/__init__.py b/prowler/providers/aws/services/redshift/redshift_cluster_non_default_username/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/redshift/redshift_cluster_non_default_username/redshift_cluster_non_default_username.metadata.json b/prowler/providers/aws/services/redshift/redshift_cluster_non_default_username/redshift_cluster_non_default_username.metadata.json new file mode 100644 index 0000000000..54884cef50 --- /dev/null +++ b/prowler/providers/aws/services/redshift/redshift_cluster_non_default_username/redshift_cluster_non_default_username.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "redshift_cluster_non_default_username", + "CheckTitle": "Check if Amazon Redshift clusters are using the default Admin username.", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "redshift", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:redshift:region:account-id:cluster/cluster-name", + "Severity": "medium", + "ResourceType": "AwsRedshiftCluster", + "Description": "This control checks whether an Amazon Redshift cluster has changed the admin username from its default value. The control fails if the admin username is set to 'awsuser'.", + "Risk": "Using the default admin username increases the risk of unauthorized access, as default credentials are publicly known and often targeted by attackers.", + "RelatedUrl": "https://docs.aws.amazon.com/redshift/latest/gsg/rs-gsg-prereq.html", + "Remediation": { + "Code": { + "CLI": "aws redshift create-cluster --cluster-identifier --master-username --master-user-password ", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/redshift-controls.html#redshift-8", + "Terraform": "" + }, + "Recommendation": { + "Text": "Change the default admin username by creating a new Redshift cluster with a unique admin username.", + "Url": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/Redshift/master-username.html" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/redshift/redshift_cluster_non_default_username/redshift_cluster_non_default_username.py b/prowler/providers/aws/services/redshift/redshift_cluster_non_default_username/redshift_cluster_non_default_username.py new file mode 100644 index 0000000000..83940d3909 --- /dev/null +++ b/prowler/providers/aws/services/redshift/redshift_cluster_non_default_username/redshift_cluster_non_default_username.py @@ -0,0 +1,24 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.redshift.redshift_client import redshift_client + + +class redshift_cluster_non_default_username(Check): + def execute(self): + findings = [] + for cluster in redshift_client.clusters: + report = Check_Report_AWS(self.metadata()) + report.region = cluster.region + report.resource_id = cluster.id + report.resource_arn = cluster.arn + report.resource_tags = cluster.tags + report.status = "PASS" + report.status_extended = f"Redshift Cluster {cluster.id} does not have the default Admin username." + if cluster.master_username == "awsuser": + report.status = "FAIL" + report.status_extended = ( + f"Redshift Cluster {cluster.id} has the default Admin username." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/redshift/redshift_service.py b/prowler/providers/aws/services/redshift/redshift_service.py index 27e09361b3..36817fbf69 100644 --- a/prowler/providers/aws/services/redshift/redshift_service.py +++ b/prowler/providers/aws/services/redshift/redshift_service.py @@ -7,15 +7,15 @@ from prowler.lib.scan_filters.scan_filters import is_resource_filtered from prowler.providers.aws.lib.service.service import AWSService -################################ Redshift class Redshift(AWSService): def __init__(self, provider): # Call AWSService's __init__ super().__init__(__class__.__name__, provider) self.clusters = [] self.__threading_call__(self._describe_clusters) - self._describe_logging_status(self.regional_clients) - self._describe_cluster_snapshots(self.regional_clients) + self.__threading_call__(self._describe_logging_status, self.clusters) + self.__threading_call__(self._describe_cluster_snapshots, self.clusters) + self.__threading_call__(self._describe_cluster_parameters, self.clusters) def _describe_clusters(self, regional_client): logger.info("Redshift - describing clusters...") @@ -30,60 +30,74 @@ class Redshift(AWSService): cluster_to_append = Cluster( arn=arn, id=cluster["ClusterIdentifier"], + endpoint_address=cluster.get("Endpoint", {}).get( + "Address", "" + ), + public_access=cluster.get("PubliclyAccessible", False), + allow_version_upgrade=cluster.get( + "AllowVersionUpgrade", False + ), + encrypted=cluster.get("Encrypted", False), region=regional_client.region, tags=cluster.get("Tags"), + master_username=cluster.get("MasterUsername", ""), + database_name=cluster.get("DBName", ""), + parameter_group_name=cluster.get( + "ClusterParameterGroups", [{}] + )[0].get("ParameterGroupName", ""), ) - if ( - "PubliclyAccessible" in cluster - and cluster["PubliclyAccessible"] - ): - cluster_to_append.public_access = True - if "Endpoint" in cluster and "Address" in cluster["Endpoint"]: - cluster_to_append.endpoint_address = cluster["Endpoint"][ - "Address" - ] - if ( - "AllowVersionUpgrade" in cluster - and cluster["AllowVersionUpgrade"] - ): - cluster_to_append.allow_version_upgrade = True self.clusters.append(cluster_to_append) except Exception as error: logger.error( f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) - def _describe_logging_status(self, regional_clients): + def _describe_logging_status(self, cluster): logger.info("Redshift - describing logging status...") try: - for cluster in self.clusters: - regional_client = regional_clients[cluster.region] - cluster_attributes = regional_client.describe_logging_status( - ClusterIdentifier=cluster.id - ) - if ( - "LoggingEnabled" in cluster_attributes - and cluster_attributes["LoggingEnabled"] - ): - cluster.logging_enabled = True - if "BucketName" in cluster_attributes: - cluster.bucket = cluster_attributes["BucketName"] + regional_client = self.regional_clients[cluster.region] + cluster_attributes = regional_client.describe_logging_status( + ClusterIdentifier=cluster.id + ) + if ( + "LoggingEnabled" in cluster_attributes + and cluster_attributes["LoggingEnabled"] + ): + cluster.logging_enabled = True + if "BucketName" in cluster_attributes: + cluster.bucket = cluster_attributes["BucketName"] except Exception as error: logger.error( f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) - def _describe_cluster_snapshots(self, regional_clients): + def _describe_cluster_snapshots(self, cluster): logger.info("Redshift - describing logging status...") try: - for cluster in self.clusters: - regional_client = regional_clients[cluster.region] - cluster_snapshots = regional_client.describe_cluster_snapshots( - ClusterIdentifier=cluster.id - ) - if "Snapshots" in cluster_snapshots and cluster_snapshots["Snapshots"]: - cluster.cluster_snapshots = True + regional_client = self.regional_clients[cluster.region] + cluster_snapshots = regional_client.describe_cluster_snapshots( + ClusterIdentifier=cluster.id + ) + if "Snapshots" in cluster_snapshots and cluster_snapshots["Snapshots"]: + cluster.cluster_snapshots = True + + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def _describe_cluster_parameters(self, cluster): + logger.info("Redshift - describing cluster parameter groups...") + try: + regional_client = self.regional_clients[cluster.region] + cluster_parameter_groups = regional_client.describe_cluster_parameters( + ClusterParameterGroupName=cluster.parameter_group_name + ) + for parameter_group in cluster_parameter_groups["Parameters"]: + if parameter_group["ParameterName"].lower() == "require_ssl": + if parameter_group["ParameterValue"].lower() == "true": + cluster.require_ssl = True except Exception as error: logger.error( @@ -95,10 +109,15 @@ class Cluster(BaseModel): id: str arn: str region: str - public_access: bool = None + public_access: bool = False + encrypted: bool = False + master_username: str = None + database_name: str = None endpoint_address: str = None - allow_version_upgrade: bool = None - logging_enabled: bool = None + allow_version_upgrade: bool = False + logging_enabled: bool = False bucket: str = None - cluster_snapshots: bool = None + cluster_snapshots: bool = False tags: Optional[list] = [] + parameter_group_name: str = None + require_ssl: bool = False diff --git a/prowler/providers/aws/services/sns/sns_service.py b/prowler/providers/aws/services/sns/sns_service.py index eb92161eb7..81474b4de6 100644 --- a/prowler/providers/aws/services/sns/sns_service.py +++ b/prowler/providers/aws/services/sns/sns_service.py @@ -1,6 +1,7 @@ from json import loads from typing import Optional +from botocore.exceptions import ClientError from pydantic import BaseModel from prowler.lib.logger import logger @@ -8,7 +9,6 @@ from prowler.lib.scan_filters.scan_filters import is_resource_filtered from prowler.providers.aws.lib.service.service import AWSService -################################ SNS class SNS(AWSService): def __init__(self, provider): # Call AWSService's __init__ @@ -16,7 +16,7 @@ class SNS(AWSService): self.topics = [] self.__threading_call__(self._list_topics) self._get_topic_attributes(self.regional_clients) - self._list_tags_for_resource() + self.__threading_call__(self._list_tags_for_resource, self.topics) self._list_subscriptions_by_topic() def _list_topics(self, regional_client): @@ -61,18 +61,24 @@ class SNS(AWSService): f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) - def _list_tags_for_resource(self): - logger.info("SNS - List Tags...") + def _list_tags_for_resource(self, resource): + logger.info("SNS - Listing Tags...") try: - for topic in self.topics: - regional_client = self.regional_clients[topic.region] - response = regional_client.list_tags_for_resource( - ResourceArn=topic.arn - )["Tags"] - topic.tags = response + resource.tags = self.regional_clients[ + resource.region + ].list_tags_for_resource(ResourceArn=resource.arn)["Tags"] + except ClientError as error: + if error.response["Error"]["Code"] == "ResourceNotFoundException": + logger.warning( + f"{resource.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: Resource {resource.arn} not found while listing tags" + ) + else: + logger.error( + f"{resource.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}" + f"{resource.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) def _list_subscriptions_by_topic(self): diff --git a/prowler/providers/aws/services/wafv2/wafv2_service.py b/prowler/providers/aws/services/wafv2/wafv2_service.py index 1f1ad3793e..e1b11d489b 100644 --- a/prowler/providers/aws/services/wafv2/wafv2_service.py +++ b/prowler/providers/aws/services/wafv2/wafv2_service.py @@ -12,11 +12,13 @@ class WAFv2(AWSService): def __init__(self, provider): # Call AWSService's __init__ super().__init__(__class__.__name__, provider) - self.web_acls = [] + self.web_acls = {} self.__threading_call__(self._list_web_acls) - self.__threading_call__(self._list_resources_for_web_acl) - self.__threading_call__(self._get_logging_configuration) - self.__threading_call__(self._list_tags, self.web_acls) + self.__threading_call__( + self._list_resources_for_web_acl, self.web_acls.values() + ) + self.__threading_call__(self._get_logging_configuration, self.web_acls.values()) + self.__threading_call__(self._list_tags, self.web_acls.values()) def _list_web_acls(self, regional_client): logger.info("WAFv2 - Listing Regional Web ACLs...") @@ -25,66 +27,69 @@ class WAFv2(AWSService): if not self.audit_resources or ( is_resource_filtered(wafv2["ARN"], self.audit_resources) ): - self.web_acls.append( - WebAclv2( - arn=wafv2["ARN"], - name=wafv2["Name"], - id=wafv2["Id"], - albs=[], - user_pools=[], - region=regional_client.region, - ) + arn = wafv2["ARN"] + self.web_acls[arn] = WebAclv2( + arn=arn, + name=wafv2["Name"], + id=wafv2["Id"], + albs=[], + user_pools=[], + region=regional_client.region, ) except Exception as error: logger.error( f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) - def _get_logging_configuration(self, regional_client): + def _get_logging_configuration(self, acl): logger.info("WAFv2 - Get Logging Configuration...") - for acl in self.web_acls: - if acl.region == regional_client.region: - try: - logging_enabled = regional_client.get_logging_configuration( - ResourceArn=acl.arn - ) - acl.logging_enabled = bool( - logging_enabled["LoggingConfiguration"]["LogDestinationConfigs"] - ) + try: + logging_enabled = self.regional_clients[ + acl.region + ].get_logging_configuration(ResourceArn=acl.arn) + acl.logging_enabled = bool( + logging_enabled["LoggingConfiguration"]["LogDestinationConfigs"] + ) - except ClientError as error: - if error.response["Error"]["Code"] == "WAFNonexistentItemException": - logger.warning( - f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" - ) - else: - logger.error( - f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" - ) - except Exception as error: - logger.error( - f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" - ) + except ClientError as error: + if error.response["Error"]["Code"] == "WAFNonexistentItemException": + logger.warning( + f"{acl.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + else: + logger.error( + f"{acl.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + except Exception as error: + logger.error( + f"{acl.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) - def _list_resources_for_web_acl(self, regional_client): + def _list_resources_for_web_acl(self, acl): logger.info("WAFv2 - Describing resources...") - for acl in self.web_acls: - if acl.region == regional_client.region: - try: - for resource in regional_client.list_resources_for_web_acl( - WebACLArn=acl.arn, ResourceType="APPLICATION_LOAD_BALANCER" - )["ResourceArns"]: - acl.albs.append(resource) + try: + for resource in self.regional_clients[ + acl.region + ].list_resources_for_web_acl( + WebACLArn=acl.arn, ResourceType="APPLICATION_LOAD_BALANCER" + )[ + "ResourceArns" + ]: + acl.albs.append(resource) - for resource in regional_client.list_resources_for_web_acl( - WebACLArn=acl.arn, ResourceType="COGNITO_USER_POOL" - )["ResourceArns"]: - acl.user_pools.append(resource) + for resource in self.regional_clients[ + acl.region + ].list_resources_for_web_acl( + WebACLArn=acl.arn, ResourceType="COGNITO_USER_POOL" + )[ + "ResourceArns" + ]: + acl.user_pools.append(resource) - 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"{acl.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) def _list_tags(self, resource: any): logger.info("WAFv2 - Listing tags...") diff --git a/prowler/providers/aws/services/wafv2/wafv2_webacl_logging_enabled/wafv2_webacl_logging_enabled.py b/prowler/providers/aws/services/wafv2/wafv2_webacl_logging_enabled/wafv2_webacl_logging_enabled.py index 5307e68752..aaec85df18 100644 --- a/prowler/providers/aws/services/wafv2/wafv2_webacl_logging_enabled/wafv2_webacl_logging_enabled.py +++ b/prowler/providers/aws/services/wafv2/wafv2_webacl_logging_enabled/wafv2_webacl_logging_enabled.py @@ -5,7 +5,7 @@ from prowler.providers.aws.services.wafv2.wafv2_client import wafv2_client class wafv2_webacl_logging_enabled(Check): def execute(self): findings = [] - for web_acl in wafv2_client.web_acls: + for web_acl in wafv2_client.web_acls.values(): report = Check_Report_AWS(self.metadata()) report.region = web_acl.region report.resource_id = web_acl.id diff --git a/pyproject.toml b/pyproject.toml index 74a0a204d1..22c0ba71bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ packages = [ {include = "dashboard"} ] readme = "README.md" -version = "4.4.0" +version = "4.5.0" [tool.poetry.dependencies] alive-progress = "3.1.5" @@ -47,13 +47,13 @@ azure-mgmt-storage = "21.2.1" azure-mgmt-subscription = "3.1.1" azure-mgmt-web = "7.3.1" azure-storage-blob = "12.23.1" -boto3 = "1.35.28" +boto3 = "1.35.29" botocore = "1.35.29" colorama = "0.4.6" cryptography = "43.0.1" dash = "2.18.1" dash-bootstrap-components = "1.6.0" -detect-secrets = "1.5.0" +detect-secrets = {git = "https://github.com/Yelp/detect-secrets.git", rev = "master"} google-api-python-client = "2.147.0" google-auth-httplib2 = ">=0.1,<0.3" jsonschema = "4.23.0" @@ -62,8 +62,8 @@ microsoft-kiota-abstractions = "1.3.3" msgraph-sdk = "1.8.0" numpy = "2.0.2" pandas = "2.2.3" -py-ocsf-models = "0.1.1" -pydantic = "1.10.15" +py-ocsf-models = "0.2.0" +pydantic = "1.10.18" python = ">=3.9,<3.13" python-dateutil = "^2.9.0.post0" pytz = "2024.2" @@ -81,7 +81,7 @@ docker = "7.1.0" flake8 = "7.1.1" freezegun = "1.5.1" mock = "5.1.0" -moto = {extras = ["all"], version = "5.0.15"} +moto = {extras = ["all"], version = "5.0.16"} openapi-schema-validator = "0.6.2" openapi-spec-validator = "0.7.1" pylint = "3.3.1" diff --git a/tests/config/config_test.py b/tests/config/config_test.py index 1e1d235ac2..d02e34b79c 100644 --- a/tests/config/config_test.py +++ b/tests/config/config_test.py @@ -93,6 +93,8 @@ config_aws = { 8080, 8088, ], + "fargate_linux_latest_version": "1.4.0", + "fargate_windows_latest_version": "1.0.0", "trusted_account_ids": [], "log_group_retention_days": 365, "max_idle_disconnect_timeout_in_seconds": 600, diff --git a/tests/config/fixtures/config.yaml b/tests/config/fixtures/config.yaml index 46e499453a..0d0cca204d 100644 --- a/tests/config/fixtures/config.yaml +++ b/tests/config/fixtures/config.yaml @@ -57,6 +57,11 @@ aws: 8088, ] + # AWS ECS Configuration + # aws.ecs_service_fargate_latest_platform_version + fargate_linux_latest_version: "1.4.0" + fargate_windows_latest_version: "1.0.0" + # AWS VPC Configuration (vpc_endpoint_connections_trust_boundaries, vpc_endpoint_services_allowed_principals_trust_boundaries) # AWS SSM Configuration (aws.ssm_documents_set_as_public) # Single account environment: No action required. The AWS account number will be automatically added by the checks. @@ -328,8 +333,8 @@ aws: # Minimum number of Availability Zones that an ELBv2 must be in elbv2_min_azs: 2 - # Known secrets to ignore on detection - # this will include a list of regex patterns to ignore on detection + # AWS Secrets Configuration + # Patterns to ignore in the secrets checks secrets_ignore_patterns: [] # Azure Configuration diff --git a/tests/lib/outputs/ocsf/ocsf_test.py b/tests/lib/outputs/ocsf/ocsf_test.py index 4fa889dcac..52cb636fc5 100644 --- a/tests/lib/outputs/ocsf/ocsf_test.py +++ b/tests/lib/outputs/ocsf/ocsf_test.py @@ -2,6 +2,7 @@ import json from datetime import datetime from io import StringIO +import requests from freezegun import freeze_time from mock import patch from py_ocsf_models.events.base_event import SeverityID, StatusID @@ -45,12 +46,18 @@ class TestOCSF: assert isinstance(output_data, DetectionFinding) assert output_data.activity_id == ActivityID.Create.value assert output_data.activity_name == ActivityID.Create.name - assert output_data.finding_info.created_time == findings[0].timestamp + assert output_data.message == findings[0].status_extended + assert output_data.finding_info.created_time == int( + findings[0].timestamp.timestamp() + ) + assert output_data.finding_info.created_time_dt == findings[0].timestamp assert output_data.finding_info.desc == findings[0].description assert output_data.finding_info.title == findings[0].check_title assert output_data.finding_info.uid == findings[0].finding_uid assert output_data.finding_info.product_uid == "prowler" - assert output_data.event_time == findings[0].timestamp + assert output_data.finding_info.types == ["test-type"] + assert output_data.time == int(findings[0].timestamp.timestamp()) + assert output_data.time_dt == findings[0].timestamp assert ( output_data.remediation.desc == findings[0].remediation_recommendation_text ) @@ -71,14 +78,19 @@ class TestOCSF: assert output_data.resources[0].data == { "details": findings[0].resource_details } + assert output_data.metadata.profiles == ["cloud", "datetime"] + assert output_data.metadata.tenant_uid == "test-organization-id" assert output_data.metadata.event_code == findings[0].check_id assert output_data.metadata.product.name == "Prowler" assert output_data.metadata.product.vendor_name == "Prowler" + assert output_data.metadata.product.uid == "prowler" assert output_data.metadata.product.version == prowler_version assert output_data.type_uid == DetectionFindingTypeID.Create - assert output_data.type_name == DetectionFindingTypeID.Create.name + assert ( + output_data.type_name + == f"Detection Finding: {DetectionFindingTypeID.Create.name}" + ) assert output_data.unmapped == { - "check_type": findings[0].check_type, "related_url": findings[0].related_url, "categories": findings[0].categories, "depends_on": findings[0].depends_on, @@ -87,6 +99,36 @@ class TestOCSF: "compliance": findings[0].compliance, } + def test_validate_ocsf(self): + mock_file = StringIO() + findings = [ + generate_finding_output( + status="FAIL", + severity="low", + muted=False, + region=AWS_REGION_EU_WEST_1, + timestamp=datetime.now(), + resource_details="resource_details", + resource_name="resource_name", + resource_uid="resource-id", + status_extended="status extended", + ) + ] + + output = OCSF(findings) + output._file_descriptor = mock_file + + with patch.object(mock_file, "close", return_value=None): + output.batch_write_data_to_file() + + mock_file.seek(0) + content = mock_file.read() + json_data = json.loads(content) + url = "https://schema.ocsf.io/api/v2/validate" + headers = {"content-type": "application/json"} + response = requests.post(url, headers=headers, json=json_data[0]) + assert response.json()["error_count"] == 0 + @freeze_time(datetime.now()) def test_batch_write_data_to_file(self): mock_file = StringIO() @@ -106,14 +148,18 @@ class TestOCSF: expected_json_output = [ { + "message": "status extended", "metadata": { "event_code": "test-check-id", "product": { "name": "Prowler", + "uid": "prowler", "vendor_name": "Prowler", "version": prowler_version, }, - "version": "1.2.0", + "version": "1.3.0", + "profiles": ["cloud", "datetime"], + "tenant_uid": "test-organization-id", }, "severity_id": 2, "severity": "Low", @@ -122,7 +168,6 @@ class TestOCSF: "status_detail": "status extended", "status_id": 1, "unmapped": { - "check_type": "test-type", "related_url": "test-url", "categories": "test-category", "depends_on": "test-dependency", @@ -133,11 +178,13 @@ class TestOCSF: "activity_name": "Create", "activity_id": 1, "finding_info": { - "created_time": datetime.now().isoformat(), + "created_time": int(datetime.now().timestamp()), + "created_time_dt": datetime.now().isoformat(), "desc": "check description", "product_uid": "prowler", "title": "test-check-id", "uid": "test-unique-finding", + "types": ["test-type"], }, "resources": [ { @@ -153,12 +200,12 @@ class TestOCSF: ], "category_name": "Findings", "category_uid": 2, - "class_name": "DetectionFinding", + "class_name": "Detection Finding", "class_uid": 2004, "cloud": { "account": { "name": "123456789012", - "type": "AWS_Account", + "type": "AWS Account", "type_id": 10, "uid": "123456789012", "labels": ["test-tag:test-value"], @@ -170,11 +217,12 @@ class TestOCSF: "provider": "aws", "region": "eu-west-1", }, - "event_time": datetime.now().isoformat(), + "time": int(datetime.now().timestamp()), + "time_dt": datetime.now().isoformat(), "remediation": {"desc": "", "references": []}, "risk_details": "test-risk", "type_uid": 200401, - "type_name": "Create", + "type_name": "Detection Finding: Create", } ] @@ -186,7 +234,6 @@ class TestOCSF: mock_file.seek(0) content = mock_file.read() - assert json.loads(content) == expected_json_output def test_batch_write_data_to_file_without_findings(self): @@ -211,14 +258,18 @@ class TestOCSF: finding_information = finding_ocsf.finding_info assert isinstance(finding_information, FindingInformation) - assert finding_information.created_time == finding_output.timestamp + assert finding_information.created_time == int( + finding_output.timestamp.timestamp() + ) + assert finding_information.created_time_dt == finding_output.timestamp assert finding_information.desc == finding_output.description assert finding_information.title == finding_output.check_title assert finding_information.uid == finding_output.finding_uid assert finding_information.product_uid == "prowler" # Event time - assert finding_ocsf.event_time == finding_output.timestamp + assert finding_ocsf.time == int(finding_output.timestamp.timestamp()) + assert finding_ocsf.time_dt == finding_output.timestamp # Remediation remediation = finding_ocsf.remediation @@ -241,7 +292,6 @@ class TestOCSF: # Unmapped Data assert finding_ocsf.unmapped == { - "check_type": finding_output.check_type, "related_url": finding_output.related_url, "categories": finding_output.categories, "depends_on": finding_output.depends_on, @@ -281,7 +331,10 @@ class TestOCSF: # Type assert finding_ocsf.type_uid == DetectionFindingTypeID.Create - assert finding_ocsf.type_name == DetectionFindingTypeID.Create.name + assert ( + finding_ocsf.type_name + == f"Detection Finding: {DetectionFindingTypeID.Create.name}" + ) # Cloud cloud = finding_ocsf.cloud @@ -293,7 +346,7 @@ class TestOCSF: assert isinstance(cloud_account, Account) assert cloud_account.name == finding_output.account_name assert cloud_account.type_id == TypeID.AWS_Account - assert cloud_account.type == TypeID.AWS_Account.name + assert cloud_account.type == TypeID.AWS_Account.name.replace("_", " ") assert cloud_account.uid == finding_output.account_uid assert cloud_account.labels == ["test-tag:test-value"] @@ -314,8 +367,10 @@ class TestOCSF: finding_ocsf = OCSF([finding_output]) finding_ocsf = finding_ocsf.data[0] - assert finding_ocsf.container.name == finding_output.resource_name - assert finding_ocsf.container.uid == finding_output.resource_uid + assert finding_ocsf.metadata.profiles == ["container", "datetime"] + assert finding_ocsf.resources[0].namespace == finding_output.region.replace( + "namespace: ", "" + ) def test_finding_output_cloud_fail_low_not_muted(self): finding_output = generate_finding_output( @@ -340,8 +395,8 @@ class TestOCSF: finding_ocsf = finding_ocsf.data[0] # Status - assert finding_ocsf.status_id == StatusID.Other.value - assert finding_ocsf.status == StatusID.Other.name + assert finding_ocsf.status_id == StatusID.New.value + assert finding_ocsf.status == StatusID.New.name assert finding_ocsf.status_code == finding_output.status assert finding_ocsf.status_detail == finding_output.status_extended @@ -365,38 +420,12 @@ class TestOCSF: provider = "None" assert OCSF.get_account_type_id_by_provider(provider) == TypeID.Other - # Returns StatusID.New when status is "FAIL" and muted is False - def test_new_when_status_fail_and_not_muted(self): - status = "FAIL" + # Returns StatusID.New when muted is False + def test_new_when_not_muted(self): muted = False - assert OCSF.get_finding_status_id(status, muted) == StatusID.New + assert OCSF.get_finding_status_id(muted) == StatusID.New - # Returns StatusID.Suppressed when status is "FAIL" and muted is True - def test_suppressed_when_status_fail_and_muted(self): - status = "FAIL" + # Returns StatusID.Suppressed when muted is True + def test_suppressed_when_muted(self): muted = True - assert OCSF.get_finding_status_id(status, muted) == StatusID.Suppressed - - # Returns StatusID.Other when status is PASS and muted is False - def test_other_when_status_whatever_and_not_muted(self): - status = "PASS" - muted = False - assert OCSF.get_finding_status_id(status, muted) == StatusID.Other - - # Returns StatusID.Suppresed when status is PASS and muted is True - def test_other_when_status_whatever_and_muted(self): - status = "PASS" - muted = True - assert OCSF.get_finding_status_id(status, muted) == StatusID.Suppressed - - # Returns StatusID.Suppressed when muted is True and status is not "FAIL" - def test_suppressed_when_status_pass_and_muted(self): - status = "PASS" - muted = True - assert OCSF.get_finding_status_id(status, muted) == StatusID.Suppressed - - # Returns StatusID.Other when muted is False and status is not "FAIL" - def test_other_when_status_pass_and_not_muted(self): - status = "PASS" - muted = False - assert OCSF.get_finding_status_id(status, muted) == StatusID.Other + assert OCSF.get_finding_status_id(muted) == StatusID.Suppressed diff --git a/tests/lib/scan/scan_test.py b/tests/lib/scan/scan_test.py index 2ccf92f57e..c737039917 100644 --- a/tests/lib/scan/scan_test.py +++ b/tests/lib/scan/scan_test.py @@ -1,3 +1,5 @@ +from importlib.machinery import FileFinder +from pkgutil import ModuleInfo from unittest import mock import pytest @@ -69,6 +71,23 @@ def mock_generate_output(): yield mock_gen_output +@pytest.fixture +def mock_list_modules(): + with mock.patch( + "prowler.lib.check.utils.list_modules", autospec=True + ) as mock_list_mod: + mock_list_mod.return_value = [ + ModuleInfo( + module_finder=FileFinder( + "/prowler/providers/aws/services/accessanalyzer/accessanalyzer_enabled" + ), + name="prowler.providers.aws.services.accessanalyzer.accessanalyzer_enabled.accessanalyzer_enabled", + ispkg=False, + ) + ] + yield mock_list_mod + + class TestScan: def test_init(mock_provider): checks_to_execute = { @@ -201,6 +220,23 @@ class TestScan: ) assert scan.service_checks_completed == {} assert scan.progress == 0 + assert scan.duration == 0 + assert scan.get_completed_services() == set() + assert scan.get_completed_checks() == set() + + def test_init_with_no_checks(mock_provider, mock_list_modules): + checks_to_execute = set() + mock_provider.type = "aws" + + scan = Scan(mock_provider, checks_to_execute) + + assert scan.provider == mock_provider + assert scan.checks_to_execute == ["accessanalyzer_enabled"] + assert scan.service_checks_to_execute == get_service_checks_to_execute( + ["accessanalyzer_enabled"] + ) + assert scan.service_checks_completed == {} + assert scan.progress == 0 assert scan.get_completed_services() == set() assert scan.get_completed_checks() == set() @@ -235,6 +271,8 @@ class TestScan: assert results[0][1] == mock_execute.side_effect() assert results[0][0] == 100.0 assert scan.progress == 100.0 + # Since the scan is mocked, the duration will always be 0 for now + assert scan.duration == 0 assert scan._number_of_checks_completed == 1 assert scan.service_checks_completed == { "accessanalyzer": {"accessanalyzer_enabled"}, diff --git a/tests/providers/aws/aws_provider_test.py b/tests/providers/aws/aws_provider_test.py index e4e5fe491a..bb3883ba9a 100644 --- a/tests/providers/aws/aws_provider_test.py +++ b/tests/providers/aws/aws_provider_test.py @@ -997,11 +997,11 @@ aws: ], ) instance_id = instances["Instances"][0]["InstanceId"] - instance_arn = f"arn:aws:ec2:{AWS_REGION_EU_CENTRAL_1}::instance/{instance_id}" + instance_arn = f"arn:aws:ec2:{AWS_REGION_EU_CENTRAL_1}:{AWS_ACCOUNT_NUMBER}:ec2:instance/{instance_id}" image_id = ec2_client.create_image(Name="testami", InstanceId=instance_id)[ "ImageId" ] - image_arn = f"arn:aws:ec2:{AWS_REGION_EU_CENTRAL_1}::image/{image_id}" + image_arn = f"arn:aws:ec2:{AWS_REGION_EU_CENTRAL_1}:{AWS_ACCOUNT_NUMBER}:ec2:image/{image_id}" ec2_client.create_tags( Resources=[image_id], Tags=[{"Key": "ami", "Value": "test"}] ) diff --git a/tests/providers/aws/services/autoscaling/autoscaling_service_test.py b/tests/providers/aws/services/autoscaling/autoscaling_service_test.py index cd9e99018e..a891dd033e 100644 --- a/tests/providers/aws/services/autoscaling/autoscaling_service_test.py +++ b/tests/providers/aws/services/autoscaling/autoscaling_service_test.py @@ -4,7 +4,10 @@ from boto3 import client from moto import mock_aws from prowler.config.config import encoding_format_utf_8 -from prowler.providers.aws.services.autoscaling.autoscaling_service import AutoScaling +from prowler.providers.aws.services.autoscaling.autoscaling_service import ( + ApplicationAutoScaling, + AutoScaling, +) from tests.providers.aws.utils import ( AWS_ACCOUNT_NUMBER, AWS_REGION_US_EAST_1, @@ -127,3 +130,42 @@ class Test_AutoScaling_Service: "Value": "value_test", } ] + + # Test Application AutoScaling Describe Scalable Targets + @mock_aws + def test_application_auto_scaling_scalable_targets(self): + dynamodb_client = client("dynamodb", region_name=AWS_REGION_US_EAST_1) + table = dynamodb_client.create_table( + TableName="test1", + AttributeDefinitions=[ + {"AttributeName": "client", "AttributeType": "S"}, + {"AttributeName": "app", "AttributeType": "S"}, + ], + KeySchema=[ + {"AttributeName": "client", "KeyType": "HASH"}, + {"AttributeName": "app", "KeyType": "RANGE"}, + ], + BillingMode="PROVISIONED", + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + )["TableDescription"] + + autoscaling_client = client( + "application-autoscaling", region_name=AWS_REGION_US_EAST_1 + ) + autoscaling_client.register_scalable_target( + ServiceNamespace="dynamodb", + ResourceId=f"table/{table['TableName']}", + ScalableDimension="dynamodb:table:ReadCapacityUnits", + MinCapacity=1, + MaxCapacity=10, + ) + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + autoscaling = ApplicationAutoScaling(aws_provider) + assert len(autoscaling.scalable_targets) == 1 + assert autoscaling.scalable_targets[0].service_namespace == "dynamodb" + assert autoscaling.scalable_targets[0].resource_id == "table/test1" + assert ( + autoscaling.scalable_targets[0].scalable_dimension + == "dynamodb:table:ReadCapacityUnits" + ) diff --git a/tests/providers/aws/services/awslambda/awslambda_function_no_secrets_in_code/awslambda_function_no_secrets_in_code_test.py b/tests/providers/aws/services/awslambda/awslambda_function_no_secrets_in_code/awslambda_function_no_secrets_in_code_test.py index 4fb6da3977..8256f13ad3 100644 --- a/tests/providers/aws/services/awslambda/awslambda_function_no_secrets_in_code/awslambda_function_no_secrets_in_code_test.py +++ b/tests/providers/aws/services/awslambda/awslambda_function_no_secrets_in_code/awslambda_function_no_secrets_in_code_test.py @@ -28,6 +28,12 @@ def lambda_handler(event, context): print("custom log event") return event """ +LAMBDA_FUNCTION_CODE_WITH_METADATA_API = """ +def lambda_handler(event, context): + metadata_api = "169.254.169.254" + print("custom log event") + return event +""" def create_lambda_function() -> Function: @@ -59,6 +65,12 @@ def mock_get_function_codewithout_secrets(): ) +def mock_get_function_codewith_metadata_api(): + yield create_lambda_function(), get_lambda_code_with_secrets( + LAMBDA_FUNCTION_CODE_WITH_METADATA_API + ) + + class Test_awslambda_function_no_secrets_in_code: def test_no_functions(self): lambda_client = mock.MagicMock @@ -144,3 +156,36 @@ class Test_awslambda_function_no_secrets_in_code: == f"No secrets found in Lambda function {LAMBDA_FUNCTION_NAME} code." ) assert result[0].resource_tags == [] + + def test_function_code_with_metadata_api(self): + lambda_client = mock.MagicMock + lambda_client.functions = {LAMBDA_FUNCTION_ARN: create_lambda_function()} + + lambda_client._get_function_code = mock_get_function_codewith_metadata_api + lambda_client.audit_config = {"secrets_ignore_patterns": []} + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider(), + ), mock.patch( + "prowler.providers.aws.services.awslambda.awslambda_function_no_secrets_in_code.awslambda_function_no_secrets_in_code.awslambda_client", + new=lambda_client, + ): + # Test Check + from prowler.providers.aws.services.awslambda.awslambda_function_no_secrets_in_code.awslambda_function_no_secrets_in_code import ( + awslambda_function_no_secrets_in_code, + ) + + check = awslambda_function_no_secrets_in_code() + result = check.execute() + + assert len(result) == 1 + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == LAMBDA_FUNCTION_NAME + assert result[0].resource_arn == LAMBDA_FUNCTION_ARN + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"No secrets found in Lambda function {LAMBDA_FUNCTION_NAME} code." + ) + assert result[0].resource_tags == [] 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 67399dff35..b38d448eac 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 @@ -27,6 +27,19 @@ def mock__get_lookup_events__(trail=None, event_name=None, minutes=None, *_) -> ] +def mock__get_lookup_events_aws_service__( + trail=None, event_name=None, minutes=None, *_ +) -> list: + return [ + { + "CloudTrailEvent": '{"eventName": "DescribeAccessEntry", "userIdentity": {"type": "AWSService", "principalId": "EXAMPLE6E4XEGITWATV6R", "accountId": "123456789012", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}' + }, + { + "CloudTrailEvent": '{"eventName": "DescribeAccountAttributes", "userIdentity": {"type": "AWSService", "principalId": "EXAMPLE6E4XEGITWATV6R", "accountId": "123456789012", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}' + }, + ] + + class Test_cloudtrail_threat_detection_enumeration: @mock_aws def test_no_trails(self): @@ -211,3 +224,52 @@ class Test_cloudtrail_threat_detection_enumeration: result[0].resource_arn == f"arn:aws:cloudtrail:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:trail" ) + + @mock_aws + def test_potential_enumeration_from_aws_service(self): + ENUMERATION_ACTIONS = ["DescribeAccessEntry", "DescribeAccountAttributes"] + THRESHOLD = 2.0 + THREAT_DETECTION_MINUTES = 1440 + cloudtrail_client = mock.MagicMock() + cloudtrail_client.trails = {"us-east-1": mock.MagicMock()} + cloudtrail_client.trails["us-east-1"].is_multiregion = False + cloudtrail_client.trails["us-east-1"].name = "trail_test_us" + cloudtrail_client.trails["us-east-1"].s3_bucket_name = "bucket_test_us" + cloudtrail_client.trails["us-east-1"].region = "us-east-1" + cloudtrail_client.audited_account = AWS_ACCOUNT_NUMBER + cloudtrail_client.region = AWS_REGION_US_EAST_1 + cloudtrail_client.audit_config = { + "threat_detection_enumeration_actions": ENUMERATION_ACTIONS, + "threat_detection_enumeration_threshold": THRESHOLD, + "threat_detection_enumeration_minutes": THREAT_DETECTION_MINUTES, + } + + 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, + ): + # Test Check + from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_enumeration.cloudtrail_threat_detection_enumeration import ( + cloudtrail_threat_detection_enumeration, + ) + + check = cloudtrail_threat_detection_enumeration() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended == "No potential enumeration attack detected." + ) + assert result[0].resource_id == AWS_ACCOUNT_NUMBER + 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" + ) 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 fcf4a90965..ba278db3cf 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 @@ -27,6 +27,19 @@ def mock__get_lookup_events__(trail=None, event_name=None, minutes=None, *_) -> ] +def mock__get_lookup_events_aws_service__( + trail=None, event_name=None, minutes=None, *_ +) -> list: + return [ + { + "CloudTrailEvent": '{"eventName": "CreateLoginProfile", "userIdentity": {"type": "AWSService", "principalId": "EXAMPLE6E4XEGITWATV6R", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}' + }, + { + "CloudTrailEvent": '{"eventName": "UpdateLoginProfile", "userIdentity": {"type": "AWSService", "principalId": "EXAMPLE6E4XEGITWATV6R", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}' + }, + ] + + class Test_cloudtrail_threat_detection_privilege_escalation: @mock_aws def test_no_trails(self): @@ -211,3 +224,53 @@ class Test_cloudtrail_threat_detection_privilege_escalation: result[0].resource_arn == f"arn:aws:cloudtrail:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:trail" ) + + @mock_aws + def test_potential_enumeration_from_aws_service(self): + cloudtrail_client = mock.MagicMock() + cloudtrail_client.trails = {"us-east-1": mock.MagicMock()} + cloudtrail_client.trails["us-east-1"].is_multiregion = False + cloudtrail_client.trails["us-east-1"].name = "trail_test_us" + cloudtrail_client.trails["us-east-1"].s3_bucket_name = "bucket_test_us" + cloudtrail_client.trails["us-east-1"].region = "us-east-1" + cloudtrail_client.audited_account = AWS_ACCOUNT_NUMBER + cloudtrail_client.region = AWS_REGION_US_EAST_1 + cloudtrail_client.audit_config = { + "threat_detection_privilege_escalation_actions": [ + "CreateLoginProfile", + "UpdateLoginProfile", + ], + "threat_detection_privilege_escalation_threshold": 2.0, + "threat_detection_privilege_escalation_minutes": 1440, + } + + 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, + ): + # Test Check + from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_privilege_escalation.cloudtrail_threat_detection_privilege_escalation import ( + cloudtrail_threat_detection_privilege_escalation, + ) + + check = cloudtrail_threat_detection_privilege_escalation() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "No potential privilege escalation attack detected." + ) + assert result[0].resource_id == AWS_ACCOUNT_NUMBER + 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" + ) diff --git a/tests/providers/aws/services/cloudwatch/cloudwatch_changes_to_network_acls_alarm_configured/cloudwatch_changes_to_network_acls_alarm_configured_test.py b/tests/providers/aws/services/cloudwatch/cloudwatch_changes_to_network_acls_alarm_configured/cloudwatch_changes_to_network_acls_alarm_configured_test.py index 900c033bd1..7f5f7a2ddf 100644 --- a/tests/providers/aws/services/cloudwatch/cloudwatch_changes_to_network_acls_alarm_configured/cloudwatch_changes_to_network_acls_alarm_configured_test.py +++ b/tests/providers/aws/services/cloudwatch/cloudwatch_changes_to_network_acls_alarm_configured/cloudwatch_changes_to_network_acls_alarm_configured_test.py @@ -227,6 +227,12 @@ class Test_cloudwatch_changes_to_network_acls_alarm_configured: ], ) + # Tag the log group + logs_client.tag_log_group( + logGroupName="/log-group/test", + tags={"tag_key_1": "tag_value_1", "tag_key_2": "tag_value_2"}, + ) + from prowler.providers.aws.services.cloudtrail.cloudtrail_service import ( Cloudtrail, ) @@ -279,9 +285,15 @@ class Test_cloudwatch_changes_to_network_acls_alarm_configured: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [ + { + "tag_key_1": "tag_value_1", + "tag_key_2": "tag_value_2", + } + ] @mock_aws def test_cloudwatch_trail_with_log_group_with_metric_and_alarm(self): @@ -320,6 +332,11 @@ class Test_cloudwatch_changes_to_network_acls_alarm_configured: ActionsEnabled=True, ) + logs_client.tag_log_group( + logGroupName="/log-group/test", + tags={"tag_key_1": "tag_value_1", "tag_key_2": "tag_value_2"}, + ) + from prowler.providers.aws.services.cloudtrail.cloudtrail_service import ( Cloudtrail, ) @@ -372,9 +389,15 @@ class Test_cloudwatch_changes_to_network_acls_alarm_configured: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [ + { + "tag_key_1": "tag_value_1", + "tag_key_2": "tag_value_2", + } + ] @mock_aws def test_cloudwatch_trail_with_log_group_with_metric_and_alarm_with_quotes(self): @@ -413,6 +436,11 @@ class Test_cloudwatch_changes_to_network_acls_alarm_configured: ActionsEnabled=True, ) + logs_client.tag_log_group( + logGroupName="/log-group/test", + tags={"tag_key_1": "tag_value_1", "tag_key_2": "tag_value_2"}, + ) + from prowler.providers.aws.services.cloudtrail.cloudtrail_service import ( Cloudtrail, ) @@ -465,9 +493,15 @@ class Test_cloudwatch_changes_to_network_acls_alarm_configured: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [ + { + "tag_key_1": "tag_value_1", + "tag_key_2": "tag_value_2", + } + ] @mock_aws def test_cloudwatch_trail_with_log_group_with_metric_and_alarm_with_newlines(self): @@ -506,6 +540,11 @@ class Test_cloudwatch_changes_to_network_acls_alarm_configured: ActionsEnabled=True, ) + logs_client.tag_log_group( + logGroupName="/log-group/test", + tags={"tag_key_1": "tag_value_1", "tag_key_2": "tag_value_2"}, + ) + from prowler.providers.aws.services.cloudtrail.cloudtrail_service import ( Cloudtrail, ) @@ -558,9 +597,15 @@ class Test_cloudwatch_changes_to_network_acls_alarm_configured: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [ + { + "tag_key_1": "tag_value_1", + "tag_key_2": "tag_value_2", + } + ] @mock_aws def test_access_denied(self): diff --git a/tests/providers/aws/services/cloudwatch/cloudwatch_changes_to_network_gateways_alarm_configured/cloudwatch_changes_to_network_gateways_alarm_configured_test.py b/tests/providers/aws/services/cloudwatch/cloudwatch_changes_to_network_gateways_alarm_configured/cloudwatch_changes_to_network_gateways_alarm_configured_test.py index 64de52d209..55865357e8 100644 --- a/tests/providers/aws/services/cloudwatch/cloudwatch_changes_to_network_gateways_alarm_configured/cloudwatch_changes_to_network_gateways_alarm_configured_test.py +++ b/tests/providers/aws/services/cloudwatch/cloudwatch_changes_to_network_gateways_alarm_configured/cloudwatch_changes_to_network_gateways_alarm_configured_test.py @@ -227,6 +227,10 @@ class Test_cloudwatch_changes_to_network_gateways_alarm_configured: ], ) + logs_client.tag_log_group( + logGroupName="/log-group/test", tags={"tag_key": "tag_value"} + ) + from prowler.providers.aws.services.cloudtrail.cloudtrail_service import ( Cloudtrail, ) @@ -279,9 +283,10 @@ class Test_cloudwatch_changes_to_network_gateways_alarm_configured: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [{"tag_key": "tag_value"}] @mock_aws def test_cloudwatch_trail_with_log_group_with_metric_and_alarm(self): @@ -320,6 +325,10 @@ class Test_cloudwatch_changes_to_network_gateways_alarm_configured: ActionsEnabled=True, ) + logs_client.tag_log_group( + logGroupName="/log-group/test", tags={"tag_key": "tag_value"} + ) + from prowler.providers.aws.services.cloudtrail.cloudtrail_service import ( Cloudtrail, ) @@ -372,9 +381,10 @@ class Test_cloudwatch_changes_to_network_gateways_alarm_configured: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [{"tag_key": "tag_value"}] @mock_aws def test_cloudwatch_trail_with_log_group_with_metric_and_alarm_with_quotes(self): @@ -413,6 +423,10 @@ class Test_cloudwatch_changes_to_network_gateways_alarm_configured: ActionsEnabled=True, ) + logs_client.tag_log_group( + logGroupName="/log-group/test", tags={"tag_key": "tag_value"} + ) + from prowler.providers.aws.services.cloudtrail.cloudtrail_service import ( Cloudtrail, ) @@ -465,9 +479,10 @@ class Test_cloudwatch_changes_to_network_gateways_alarm_configured: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [{"tag_key": "tag_value"}] @mock_aws def test_cloudwatch_trail_with_log_group_with_metric_and_alarm_with_newlines(self): @@ -506,6 +521,10 @@ class Test_cloudwatch_changes_to_network_gateways_alarm_configured: ActionsEnabled=True, ) + logs_client.tag_log_group( + logGroupName="/log-group/test", tags={"tag_key": "tag_value"} + ) + from prowler.providers.aws.services.cloudtrail.cloudtrail_service import ( Cloudtrail, ) @@ -558,6 +577,7 @@ class Test_cloudwatch_changes_to_network_gateways_alarm_configured: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [{"tag_key": "tag_value"}] diff --git a/tests/providers/aws/services/cloudwatch/cloudwatch_changes_to_network_route_tables_alarm_configured/cloudwatch_changes_to_network_route_tables_alarm_configured_test.py b/tests/providers/aws/services/cloudwatch/cloudwatch_changes_to_network_route_tables_alarm_configured/cloudwatch_changes_to_network_route_tables_alarm_configured_test.py index a69b03f45e..30033378bd 100644 --- a/tests/providers/aws/services/cloudwatch/cloudwatch_changes_to_network_route_tables_alarm_configured/cloudwatch_changes_to_network_route_tables_alarm_configured_test.py +++ b/tests/providers/aws/services/cloudwatch/cloudwatch_changes_to_network_route_tables_alarm_configured/cloudwatch_changes_to_network_route_tables_alarm_configured_test.py @@ -279,7 +279,7 @@ class Test_cloudwatch_changes_to_network_route_tables_alarm_configured: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -372,7 +372,7 @@ class Test_cloudwatch_changes_to_network_route_tables_alarm_configured: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -465,7 +465,7 @@ class Test_cloudwatch_changes_to_network_route_tables_alarm_configured: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -558,6 +558,6 @@ class Test_cloudwatch_changes_to_network_route_tables_alarm_configured: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 diff --git a/tests/providers/aws/services/cloudwatch/cloudwatch_changes_to_vpcs_alarm_configured/cloudwatch_changes_to_vpcs_alarm_configured_test.py b/tests/providers/aws/services/cloudwatch/cloudwatch_changes_to_vpcs_alarm_configured/cloudwatch_changes_to_vpcs_alarm_configured_test.py index 70a7dc8d1f..c4ef5da546 100644 --- a/tests/providers/aws/services/cloudwatch/cloudwatch_changes_to_vpcs_alarm_configured/cloudwatch_changes_to_vpcs_alarm_configured_test.py +++ b/tests/providers/aws/services/cloudwatch/cloudwatch_changes_to_vpcs_alarm_configured/cloudwatch_changes_to_vpcs_alarm_configured_test.py @@ -279,7 +279,7 @@ class Test_cloudwatch_changes_to_vpcs_alarm_configured: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -372,7 +372,7 @@ class Test_cloudwatch_changes_to_vpcs_alarm_configured: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -465,7 +465,7 @@ class Test_cloudwatch_changes_to_vpcs_alarm_configured: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -558,6 +558,6 @@ class Test_cloudwatch_changes_to_vpcs_alarm_configured: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 diff --git a/tests/providers/aws/services/cloudwatch/cloudwatch_cross_account_sharing_disabled/cloudwatch_cross_account_sharing_disabled_test.py b/tests/providers/aws/services/cloudwatch/cloudwatch_cross_account_sharing_disabled/cloudwatch_cross_account_sharing_disabled_test.py index c79f87caaa..7a70c1f515 100644 --- a/tests/providers/aws/services/cloudwatch/cloudwatch_cross_account_sharing_disabled/cloudwatch_cross_account_sharing_disabled_test.py +++ b/tests/providers/aws/services/cloudwatch/cloudwatch_cross_account_sharing_disabled/cloudwatch_cross_account_sharing_disabled_test.py @@ -52,6 +52,14 @@ class Test_cloudwatch_cross_account_sharing_disabled: == "CloudWatch doesn't allow cross-account sharing." ) assert result[0].resource_id == AWS_ACCOUNT_NUMBER + assert ( + result[0].resource_arn + == f"arn:aws:iam:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:role" + or result[0].resource_arn + == f"arn:aws:iam:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:role" + ) + assert result[0].region == AWS_REGION_EU_WEST_1 + assert result[0].resource_tags == [] @mock_aws def test_cloudwatch_log_group_with_cross_account_role(self): @@ -59,7 +67,9 @@ class Test_cloudwatch_cross_account_sharing_disabled: iam_client = client("iam", region_name=AWS_REGION_US_EAST_1) # Request Logs group iam_client.create_role( - RoleName="CloudWatch-CrossAccountSharingRole", AssumeRolePolicyDocument="{}" + RoleName="CloudWatch-CrossAccountSharingRole", + AssumeRolePolicyDocument="{}", + Tags=[{"Key": "Name", "Value": "CloudWatch-CrossAccountSharingRole"}], ) from prowler.providers.aws.services.iam.iam_service import IAM @@ -99,6 +109,17 @@ class Test_cloudwatch_cross_account_sharing_disabled: == "CloudWatch has allowed cross-account sharing." ) assert result[0].resource_id == "CloudWatch-CrossAccountSharingRole" + assert ( + result[0].resource_arn + == f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:role/CloudWatch-CrossAccountSharingRole" + ) + assert ( + result[0].region == AWS_REGION_US_EAST_1 + or result[0].region == AWS_REGION_EU_WEST_1 + ) + assert result[0].resource_tags == [ + {"Key": "Name", "Value": "CloudWatch-CrossAccountSharingRole"} + ] @mock_aws def test_access_denied(self): diff --git a/tests/providers/aws/services/cloudwatch/cloudwatch_log_group_no_secrets_in_logs/cloudwatch_log_group_no_secrets_in_logs_test.py b/tests/providers/aws/services/cloudwatch/cloudwatch_log_group_no_secrets_in_logs/cloudwatch_log_group_no_secrets_in_logs_test.py index 886828a964..0bca97ec5d 100644 --- a/tests/providers/aws/services/cloudwatch/cloudwatch_log_group_no_secrets_in_logs/cloudwatch_log_group_no_secrets_in_logs_test.py +++ b/tests/providers/aws/services/cloudwatch/cloudwatch_log_group_no_secrets_in_logs/cloudwatch_log_group_no_secrets_in_logs_test.py @@ -52,7 +52,7 @@ class Test_cloudwatch_log_group_no_secrets_in_logs: # Generate Logs Client logs_client = client("logs", region_name=AWS_REGION_US_EAST_1) # Request Logs group - logs_client.create_log_group(logGroupName="test") + logs_client.create_log_group(logGroupName="test", tags={"test": "test"}) logs_client.create_log_stream(logGroupName="test", logStreamName="test stream") logs_client.put_log_events( logGroupName="test", @@ -99,13 +99,19 @@ class Test_cloudwatch_log_group_no_secrets_in_logs: assert result[0].status == "PASS" assert result[0].status_extended == "No secrets found in test log group." assert result[0].resource_id == "test" + assert ( + result[0].resource_arn + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:123456789012:log-group:test" + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [{"test": "test"}] @mock_aws def test_cloudwatch_log_group_with_secrets(self): # Generate Logs Client logs_client = client("logs", region_name=AWS_REGION_US_EAST_1) # Request Logs group - logs_client.create_log_group(logGroupName="test") + logs_client.create_log_group(logGroupName="test", tags={"test": "test"}) logs_client.create_log_stream(logGroupName="test", logStreamName="test stream") logs_client.put_log_events( logGroupName="test", @@ -154,6 +160,12 @@ class Test_cloudwatch_log_group_no_secrets_in_logs: "Potential secrets found in log group", result[0].status_extended ) assert result[0].resource_id == "test" + assert ( + result[0].resource_arn + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:123456789012:log-group:test" + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [{"test": "test"}] @mock_aws def test_access_denied(self): diff --git a/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled/cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled_test.py b/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled/cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled_test.py index 345cd1a687..c3bcb951d5 100644 --- a/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled/cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled_test.py +++ b/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled/cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled_test.py @@ -287,7 +287,7 @@ class Test_cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_c assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -382,7 +382,7 @@ class Test_cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_c assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -477,7 +477,7 @@ class Test_cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_c assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -572,7 +572,7 @@ class Test_cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_c assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 diff --git a/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled/cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled_test.py b/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled/cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled_test.py index 0b8a3485a6..c5cb84cfa5 100644 --- a/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled/cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled_test.py +++ b/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled/cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled_test.py @@ -287,7 +287,7 @@ class Test_cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_c assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -382,7 +382,7 @@ class Test_cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_c assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -477,7 +477,7 @@ class Test_cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_c assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -572,6 +572,6 @@ class Test_cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_c assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 diff --git a/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_authentication_failures/cloudwatch_log_metric_filter_authentication_failures_test.py b/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_authentication_failures/cloudwatch_log_metric_filter_authentication_failures_test.py index 6c70042b79..63a762faf5 100644 --- a/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_authentication_failures/cloudwatch_log_metric_filter_authentication_failures_test.py +++ b/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_authentication_failures/cloudwatch_log_metric_filter_authentication_failures_test.py @@ -279,7 +279,7 @@ class Test_cloudwatch_log_metric_filter_authentication_failures: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -372,7 +372,7 @@ class Test_cloudwatch_log_metric_filter_authentication_failures: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -465,7 +465,7 @@ class Test_cloudwatch_log_metric_filter_authentication_failures: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -558,6 +558,6 @@ class Test_cloudwatch_log_metric_filter_authentication_failures: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 diff --git a/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_aws_organizations_changes/cloudwatch_log_metric_filter_aws_organizations_changes_test.py b/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_aws_organizations_changes/cloudwatch_log_metric_filter_aws_organizations_changes_test.py index b5bcb564e9..1cdd9867db 100644 --- a/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_aws_organizations_changes/cloudwatch_log_metric_filter_aws_organizations_changes_test.py +++ b/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_aws_organizations_changes/cloudwatch_log_metric_filter_aws_organizations_changes_test.py @@ -279,7 +279,7 @@ class Test_cloudwatch_log_metric_filter_aws_organizations_changes: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -372,7 +372,7 @@ class Test_cloudwatch_log_metric_filter_aws_organizations_changes: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -465,7 +465,7 @@ class Test_cloudwatch_log_metric_filter_aws_organizations_changes: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -558,6 +558,6 @@ class Test_cloudwatch_log_metric_filter_aws_organizations_changes: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 diff --git a/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk/cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk_test.py b/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk/cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk_test.py index 0f6bda8b83..ddcd8eb021 100644 --- a/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk/cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk_test.py +++ b/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk/cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk_test.py @@ -287,7 +287,7 @@ class Test_cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -382,7 +382,7 @@ class Test_cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -477,7 +477,7 @@ class Test_cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -572,6 +572,6 @@ class Test_cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 diff --git a/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_for_s3_bucket_policy_changes/cloudwatch_log_metric_filter_for_s3_bucket_policy_changes_test.py b/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_for_s3_bucket_policy_changes/cloudwatch_log_metric_filter_for_s3_bucket_policy_changes_test.py index 441fda56ae..b826f78764 100644 --- a/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_for_s3_bucket_policy_changes/cloudwatch_log_metric_filter_for_s3_bucket_policy_changes_test.py +++ b/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_for_s3_bucket_policy_changes/cloudwatch_log_metric_filter_for_s3_bucket_policy_changes_test.py @@ -279,7 +279,7 @@ class Test_cloudwatch_log_metric_filter_for_s3_bucket_policy_changes: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -372,7 +372,7 @@ class Test_cloudwatch_log_metric_filter_for_s3_bucket_policy_changes: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -465,7 +465,7 @@ class Test_cloudwatch_log_metric_filter_for_s3_bucket_policy_changes: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -558,6 +558,6 @@ class Test_cloudwatch_log_metric_filter_for_s3_bucket_policy_changes: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 diff --git a/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_policy_changes/cloudwatch_log_metric_filter_policy_changes_test.py b/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_policy_changes/cloudwatch_log_metric_filter_policy_changes_test.py index 7c74615989..b016c43e68 100644 --- a/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_policy_changes/cloudwatch_log_metric_filter_policy_changes_test.py +++ b/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_policy_changes/cloudwatch_log_metric_filter_policy_changes_test.py @@ -279,7 +279,7 @@ class Test_cloudwatch_log_metric_filter_unauthorized_api_calls: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -372,7 +372,7 @@ class Test_cloudwatch_log_metric_filter_unauthorized_api_calls: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -465,7 +465,7 @@ class Test_cloudwatch_log_metric_filter_unauthorized_api_calls: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -558,6 +558,6 @@ class Test_cloudwatch_log_metric_filter_unauthorized_api_calls: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 diff --git a/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_root_usage/cloudwatch_log_metric_filter_root_usage_test.py b/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_root_usage/cloudwatch_log_metric_filter_root_usage_test.py index 8df5057fae..0d2c48a2b2 100644 --- a/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_root_usage/cloudwatch_log_metric_filter_root_usage_test.py +++ b/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_root_usage/cloudwatch_log_metric_filter_root_usage_test.py @@ -279,7 +279,7 @@ class Test_cloudwatch_log_metric_filter_root_usage: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -372,7 +372,7 @@ class Test_cloudwatch_log_metric_filter_root_usage: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -465,7 +465,7 @@ class Test_cloudwatch_log_metric_filter_root_usage: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -558,6 +558,6 @@ class Test_cloudwatch_log_metric_filter_root_usage: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 diff --git a/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_security_group_changes/cloudwatch_log_metric_filter_security_group_changes_test.py b/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_security_group_changes/cloudwatch_log_metric_filter_security_group_changes_test.py index 9729c0d3ab..c85b5b6ad0 100644 --- a/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_security_group_changes/cloudwatch_log_metric_filter_security_group_changes_test.py +++ b/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_security_group_changes/cloudwatch_log_metric_filter_security_group_changes_test.py @@ -282,7 +282,7 @@ class Test_cloudwatch_log_metric_filter_unauthorized_api_calls: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -375,7 +375,7 @@ class Test_cloudwatch_log_metric_filter_unauthorized_api_calls: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -468,7 +468,7 @@ class Test_cloudwatch_log_metric_filter_unauthorized_api_calls: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -561,6 +561,6 @@ class Test_cloudwatch_log_metric_filter_unauthorized_api_calls: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 diff --git a/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_sign_in_without_mfa/cloudwatch_log_metric_filter_sign_in_without_mfa_test.py b/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_sign_in_without_mfa/cloudwatch_log_metric_filter_sign_in_without_mfa_test.py index 78e5b150bf..b31c224e0f 100644 --- a/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_sign_in_without_mfa/cloudwatch_log_metric_filter_sign_in_without_mfa_test.py +++ b/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_sign_in_without_mfa/cloudwatch_log_metric_filter_sign_in_without_mfa_test.py @@ -279,7 +279,7 @@ class Test_cloudwatch_log_metric_filter_sign_in_without_mfa: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -372,7 +372,7 @@ class Test_cloudwatch_log_metric_filter_sign_in_without_mfa: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -465,7 +465,7 @@ class Test_cloudwatch_log_metric_filter_sign_in_without_mfa: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -558,6 +558,6 @@ class Test_cloudwatch_log_metric_filter_sign_in_without_mfa: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 diff --git a/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_unauthorized_api_calls/cloudwatch_log_metric_filter_unauthorized_api_calls_test.py b/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_unauthorized_api_calls/cloudwatch_log_metric_filter_unauthorized_api_calls_test.py index 67fdc39fcb..d8ef907ec1 100644 --- a/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_unauthorized_api_calls/cloudwatch_log_metric_filter_unauthorized_api_calls_test.py +++ b/tests/providers/aws/services/cloudwatch/cloudwatch_log_metric_filter_unauthorized_api_calls/cloudwatch_log_metric_filter_unauthorized_api_calls_test.py @@ -279,7 +279,7 @@ class Test_cloudwatch_log_metric_filter_unauthorized_api_calls: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -372,7 +372,7 @@ class Test_cloudwatch_log_metric_filter_unauthorized_api_calls: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -465,7 +465,7 @@ class Test_cloudwatch_log_metric_filter_unauthorized_api_calls: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 @@ -558,6 +558,6 @@ class Test_cloudwatch_log_metric_filter_unauthorized_api_calls: assert result[0].resource_id == "/log-group/test" assert ( result[0].resource_arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:metric-filter/test-filter" + == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" ) assert result[0].region == AWS_REGION_US_EAST_1 diff --git a/tests/providers/aws/services/cloudwatch/cloudwatch_service_test.py b/tests/providers/aws/services/cloudwatch/cloudwatch_service_test.py index d79d9fd997..e175dfd65e 100644 --- a/tests/providers/aws/services/cloudwatch/cloudwatch_service_test.py +++ b/tests/providers/aws/services/cloudwatch/cloudwatch_service_test.py @@ -156,7 +156,7 @@ class Test_CloudWatch_Service: ) logs = Logs(aws_provider) assert len(logs.metric_filters) == 1 - assert logs.metric_filters[0].log_group == "/log-group/test" + assert logs.metric_filters[0].log_group is None assert logs.metric_filters[0].name == "test-filter" assert logs.metric_filters[0].metric == "my-metric" assert logs.metric_filters[0].pattern == "test-pattern" diff --git a/tests/providers/aws/services/cognito/cognito_user_pool_waf_acl_attached/cognito_user_pool_waf_acl_attached_test.py b/tests/providers/aws/services/cognito/cognito_user_pool_waf_acl_attached/cognito_user_pool_waf_acl_attached_test.py index 0ae9cef84e..5389fc05a9 100644 --- a/tests/providers/aws/services/cognito/cognito_user_pool_waf_acl_attached/cognito_user_pool_waf_acl_attached_test.py +++ b/tests/providers/aws/services/cognito/cognito_user_pool_waf_acl_attached/cognito_user_pool_waf_acl_attached_test.py @@ -12,7 +12,7 @@ class Test_cognito_user_pool_waf_acl_attached: cognito_client.user_pools = {} cognito_client.audited_account = AWS_ACCOUNT_NUMBER wafv2_client = mock.MagicMock - wafv2_client.web_acls = [] + wafv2_client.web_acls = {} with mock.patch( "prowler.providers.aws.services.cognito.cognito_service.CognitoIDP", new=cognito_client, @@ -53,7 +53,7 @@ class Test_cognito_user_pool_waf_acl_attached: } cognito_client.audited_account = AWS_ACCOUNT_NUMBER wafv2_client = mock.MagicMock - wafv2_client.web_acls = [] + wafv2_client.web_acls = {} with mock.patch( "prowler.providers.aws.services.cognito.cognito_service.CognitoIDP", @@ -105,8 +105,8 @@ class Test_cognito_user_pool_waf_acl_attached: web_acl_arn = "arn:aws:wafv2:us-east-1:123456789012:regional/webacl/abcd1234" web_acl_name = "abcd1234" web_acl_id = "abcd1234" - wafv2_client.web_acls = [ - WebAclv2( + wafv2_client.web_acls = { + web_acl_arn: WebAclv2( arn=web_acl_arn, name=web_acl_name, id=web_acl_id, @@ -114,7 +114,7 @@ class Test_cognito_user_pool_waf_acl_attached: user_pools=[user_pool_arn], region="us-east-1", ) - ] + } with mock.patch( "prowler.providers.aws.services.cognito.cognito_service.CognitoIDP", diff --git a/tests/providers/aws/services/dynamodb/dynamodb_service_test.py b/tests/providers/aws/services/dynamodb/dynamodb_service_test.py index 48ce9c7648..9920338049 100644 --- a/tests/providers/aws/services/dynamodb/dynamodb_service_test.py +++ b/tests/providers/aws/services/dynamodb/dynamodb_service_test.py @@ -71,7 +71,8 @@ class Test_DynamoDB_Service: {"AttributeName": "client", "KeyType": "HASH"}, {"AttributeName": "app", "KeyType": "RANGE"}, ], - BillingMode="PAY_PER_REQUEST", + BillingMode="PROVISIONED", + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, ) # DynamoDB client for this test class aws_provider = set_mocked_aws_provider() @@ -82,6 +83,9 @@ class Test_DynamoDB_Service: assert "test2" in table_names for table in dynamo.tables.values(): assert table.region == AWS_REGION_US_EAST_1 + table_billing = [table.billing_mode for table in dynamo.tables.values()] + assert "PAY_PER_REQUEST" in table_billing + assert "PROVISIONED" in table_billing # Test DynamoDB Describe Table @mock_aws @@ -116,6 +120,7 @@ class Test_DynamoDB_Service: assert tables.tags == [ {"Key": "test", "Value": "test"}, ] + assert tables.billing_mode == "PAY_PER_REQUEST" assert tables.deletion_protection # Test DynamoDB Describe Continuous Backups diff --git a/tests/providers/aws/services/dynamodb/dynamodb_table_autoscaling_enabled/dynamodb_table_autoscaling_enabled_test.py b/tests/providers/aws/services/dynamodb/dynamodb_table_autoscaling_enabled/dynamodb_table_autoscaling_enabled_test.py new file mode 100644 index 0000000000..9573762a59 --- /dev/null +++ b/tests/providers/aws/services/dynamodb/dynamodb_table_autoscaling_enabled/dynamodb_table_autoscaling_enabled_test.py @@ -0,0 +1,340 @@ +from unittest import mock + +from boto3 import client +from moto import mock_aws + +from tests.providers.aws.utils import ( + AWS_REGION_EU_WEST_1, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + + +class Test_dynamodb_table_autoscaling_enabled: + @mock_aws + def test_dynamodb_no_tables(self): + from prowler.providers.aws.services.dynamodb.dynamodb_service import DynamoDB + + aws_provider = set_mocked_aws_provider( + [AWS_REGION_EU_WEST_1, 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.dynamodb.dynamodb_table_autoscaling_enabled.dynamodb_table_autoscaling_enabled.dynamodb_client", + new=DynamoDB(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.dynamodb.dynamodb_table_autoscaling_enabled.dynamodb_table_autoscaling_enabled import ( + dynamodb_table_autoscaling_enabled, + ) + + check = dynamodb_table_autoscaling_enabled() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_dynamodb_table_on_demand(self): + dynamodb_client = client("dynamodb", region_name=AWS_REGION_US_EAST_1) + table = dynamodb_client.create_table( + TableName="test1", + AttributeDefinitions=[ + {"AttributeName": "client", "AttributeType": "S"}, + {"AttributeName": "app", "AttributeType": "S"}, + ], + KeySchema=[ + {"AttributeName": "client", "KeyType": "HASH"}, + {"AttributeName": "app", "KeyType": "RANGE"}, + ], + BillingMode="PAY_PER_REQUEST", + )["TableDescription"] + + from prowler.providers.aws.services.dynamodb.dynamodb_service import DynamoDB + + aws_provider = set_mocked_aws_provider( + [AWS_REGION_EU_WEST_1, 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.dynamodb.dynamodb_table_autoscaling_enabled.dynamodb_table_autoscaling_enabled.dynamodb_client", + new=DynamoDB(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.dynamodb.dynamodb_table_autoscaling_enabled.dynamodb_table_autoscaling_enabled import ( + dynamodb_table_autoscaling_enabled, + ) + + check = dynamodb_table_autoscaling_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].status_extended == ( + "DynamoDB table test1 automatically scales capacity on demand." + ) + assert result[0].resource_id == table["TableName"] + assert result[0].resource_arn == table["TableArn"] + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] + + @mock_aws + def test_dynamodb_table_provisioned_with_autoscaling(self): + dynamodb_client = client("dynamodb", region_name=AWS_REGION_US_EAST_1) + table = dynamodb_client.create_table( + TableName="test1", + AttributeDefinitions=[ + {"AttributeName": "client", "AttributeType": "S"}, + {"AttributeName": "app", "AttributeType": "S"}, + ], + KeySchema=[ + {"AttributeName": "client", "KeyType": "HASH"}, + {"AttributeName": "app", "KeyType": "RANGE"}, + ], + BillingMode="PROVISIONED", + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + )["TableDescription"] + + autoscaling_client = client( + "application-autoscaling", region_name=AWS_REGION_US_EAST_1 + ) + autoscaling_client.register_scalable_target( + ServiceNamespace="dynamodb", + ResourceId=f"table/{table['TableName']}", + ScalableDimension="dynamodb:table:ReadCapacityUnits", + MinCapacity=1, + MaxCapacity=10, + ) + autoscaling_client.register_scalable_target( + ServiceNamespace="dynamodb", + ResourceId=f"table/{table['TableName']}", + ScalableDimension="dynamodb:table:WriteCapacityUnits", + MinCapacity=1, + MaxCapacity=10, + ) + + from prowler.providers.aws.services.autoscaling.autoscaling_service import ( + ApplicationAutoScaling, + ) + from prowler.providers.aws.services.dynamodb.dynamodb_service import DynamoDB + + aws_provider = set_mocked_aws_provider( + [AWS_REGION_EU_WEST_1, 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.dynamodb.dynamodb_table_autoscaling_enabled.dynamodb_table_autoscaling_enabled.dynamodb_client", + new=DynamoDB(aws_provider), + ), mock.patch( + "prowler.providers.aws.services.dynamodb.dynamodb_table_autoscaling_enabled.dynamodb_table_autoscaling_enabled.applicationautoscaling_client", + new=ApplicationAutoScaling(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.dynamodb.dynamodb_table_autoscaling_enabled.dynamodb_table_autoscaling_enabled import ( + dynamodb_table_autoscaling_enabled, + ) + + check = dynamodb_table_autoscaling_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].status_extended == ( + "DynamoDB table test1 is in provisioned mode with auto scaling enabled for both read and write capacity units." + ) + assert result[0].resource_id == table["TableName"] + assert result[0].resource_arn == table["TableArn"] + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] + + @mock_aws + def test_dynamodb_table_provisioned_only_with_read_autoscaling(self): + dynamodb_client = client("dynamodb", region_name=AWS_REGION_US_EAST_1) + table = dynamodb_client.create_table( + TableName="test1", + AttributeDefinitions=[ + {"AttributeName": "client", "AttributeType": "S"}, + {"AttributeName": "app", "AttributeType": "S"}, + ], + KeySchema=[ + {"AttributeName": "client", "KeyType": "HASH"}, + {"AttributeName": "app", "KeyType": "RANGE"}, + ], + BillingMode="PROVISIONED", + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + )["TableDescription"] + + autoscaling_client = client( + "application-autoscaling", region_name=AWS_REGION_US_EAST_1 + ) + autoscaling_client.register_scalable_target( + ServiceNamespace="dynamodb", + ResourceId=f"table/{table['TableName']}", + ScalableDimension="dynamodb:table:ReadCapacityUnits", + MinCapacity=1, + MaxCapacity=10, + ) + + from prowler.providers.aws.services.autoscaling.autoscaling_service import ( + ApplicationAutoScaling, + ) + from prowler.providers.aws.services.dynamodb.dynamodb_service import DynamoDB + + aws_provider = set_mocked_aws_provider( + [AWS_REGION_EU_WEST_1, 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.dynamodb.dynamodb_table_autoscaling_enabled.dynamodb_table_autoscaling_enabled.dynamodb_client", + new=DynamoDB(aws_provider), + ), mock.patch( + "prowler.providers.aws.services.dynamodb.dynamodb_table_autoscaling_enabled.dynamodb_table_autoscaling_enabled.applicationautoscaling_client", + new=ApplicationAutoScaling(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.dynamodb.dynamodb_table_autoscaling_enabled.dynamodb_table_autoscaling_enabled import ( + dynamodb_table_autoscaling_enabled, + ) + + check = dynamodb_table_autoscaling_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].status_extended == ( + "DynamoDB table test1 is in provisioned mode without auto scaling enabled for write." + ) + assert result[0].resource_id == table["TableName"] + assert result[0].resource_arn == table["TableArn"] + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] + + @mock_aws + def test_dynamodb_table_provisioned_only_with_write_autoscaling(self): + dynamodb_client = client("dynamodb", region_name=AWS_REGION_US_EAST_1) + table = dynamodb_client.create_table( + TableName="test1", + AttributeDefinitions=[ + {"AttributeName": "client", "AttributeType": "S"}, + {"AttributeName": "app", "AttributeType": "S"}, + ], + KeySchema=[ + {"AttributeName": "client", "KeyType": "HASH"}, + {"AttributeName": "app", "KeyType": "RANGE"}, + ], + BillingMode="PROVISIONED", + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + )["TableDescription"] + + autoscaling_client = client( + "application-autoscaling", region_name=AWS_REGION_US_EAST_1 + ) + autoscaling_client.register_scalable_target( + ServiceNamespace="dynamodb", + ResourceId=f"table/{table['TableName']}", + ScalableDimension="dynamodb:table:WriteCapacityUnits", + MinCapacity=1, + MaxCapacity=10, + ) + + from prowler.providers.aws.services.autoscaling.autoscaling_service import ( + ApplicationAutoScaling, + ) + from prowler.providers.aws.services.dynamodb.dynamodb_service import DynamoDB + + aws_provider = set_mocked_aws_provider( + [AWS_REGION_EU_WEST_1, 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.dynamodb.dynamodb_table_autoscaling_enabled.dynamodb_table_autoscaling_enabled.dynamodb_client", + new=DynamoDB(aws_provider), + ), mock.patch( + "prowler.providers.aws.services.dynamodb.dynamodb_table_autoscaling_enabled.dynamodb_table_autoscaling_enabled.applicationautoscaling_client", + new=ApplicationAutoScaling(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.dynamodb.dynamodb_table_autoscaling_enabled.dynamodb_table_autoscaling_enabled import ( + dynamodb_table_autoscaling_enabled, + ) + + check = dynamodb_table_autoscaling_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].status_extended == ( + "DynamoDB table test1 is in provisioned mode without auto scaling enabled for read." + ) + assert result[0].resource_id == table["TableName"] + assert result[0].resource_arn == table["TableArn"] + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] + + @mock_aws + def test_dynamodb_table_provisioned_without_autoscaling(self): + dynamodb_client = client("dynamodb", region_name=AWS_REGION_US_EAST_1) + table = dynamodb_client.create_table( + TableName="test1", + AttributeDefinitions=[ + {"AttributeName": "client", "AttributeType": "S"}, + {"AttributeName": "app", "AttributeType": "S"}, + ], + KeySchema=[ + {"AttributeName": "client", "KeyType": "HASH"}, + {"AttributeName": "app", "KeyType": "RANGE"}, + ], + BillingMode="PROVISIONED", + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + )["TableDescription"] + + from prowler.providers.aws.services.autoscaling.autoscaling_service import ( + ApplicationAutoScaling, + ) + from prowler.providers.aws.services.dynamodb.dynamodb_service import DynamoDB + + aws_provider = set_mocked_aws_provider( + [AWS_REGION_EU_WEST_1, 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.dynamodb.dynamodb_table_autoscaling_enabled.dynamodb_table_autoscaling_enabled.dynamodb_client", + new=DynamoDB(aws_provider), + ), mock.patch( + "prowler.providers.aws.services.dynamodb.dynamodb_table_autoscaling_enabled.dynamodb_table_autoscaling_enabled.applicationautoscaling_client", + new=ApplicationAutoScaling(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.dynamodb.dynamodb_table_autoscaling_enabled.dynamodb_table_autoscaling_enabled import ( + dynamodb_table_autoscaling_enabled, + ) + + check = dynamodb_table_autoscaling_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].status_extended == ( + "DynamoDB table test1 is in provisioned mode without auto scaling enabled for read, write." + ) + assert result[0].resource_id == table["TableName"] + assert result[0].resource_arn == table["TableArn"] + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] diff --git a/tests/providers/aws/services/ecs/ecs_cluster_container_insights_enabled/ecs_cluster_container_insights_enabled_test.py b/tests/providers/aws/services/ecs/ecs_cluster_container_insights_enabled/ecs_cluster_container_insights_enabled_test.py new file mode 100644 index 0000000000..780ccdca8c --- /dev/null +++ b/tests/providers/aws/services/ecs/ecs_cluster_container_insights_enabled/ecs_cluster_container_insights_enabled_test.py @@ -0,0 +1,137 @@ +from unittest import mock + +from boto3 import client +from moto import mock_aws + +from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider + +CLUSTER_NAME = "test-cluster" + + +class Test_ecs_clusters_container_insights_enabled: + @mock_aws + def test_no_clusters(self): + from prowler.providers.aws.services.ecs.ecs_service import ECS + + 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.ecs.ecs_cluster_container_insights_enabled.ecs_cluster_container_insights_enabled.ecs_client", + new=ECS(aws_provider), + ): + from prowler.providers.aws.services.ecs.ecs_cluster_container_insights_enabled.ecs_cluster_container_insights_enabled import ( + ecs_cluster_container_insights_enabled, + ) + + check = ecs_cluster_container_insights_enabled() + result = check.execute() + assert len(result) == 0 + + @mock_aws + def test_cluster_no_settings(self): + ecs_client = client("ecs", region_name=AWS_REGION_US_EAST_1) + cluster_arn = ecs_client.create_cluster( + clusterName=CLUSTER_NAME, + )[ + "cluster" + ]["clusterArn"] + + from prowler.providers.aws.services.ecs.ecs_service import ECS + + 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.ecs.ecs_cluster_container_insights_enabled.ecs_cluster_container_insights_enabled.ecs_client", + new=ECS(aws_provider), + ): + from prowler.providers.aws.services.ecs.ecs_cluster_container_insights_enabled.ecs_cluster_container_insights_enabled import ( + ecs_cluster_container_insights_enabled, + ) + + check = ecs_cluster_container_insights_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].resource_arn == cluster_arn + assert ( + result[0].status_extended + == f"ECS cluster {CLUSTER_NAME} does not have container insights enabled." + ) + + @mock_aws + def test_cluster_enabled_container_insights(self): + ecs_client = client("ecs", region_name=AWS_REGION_US_EAST_1) + cluster_settings = [ + {"name": "containerInsights", "value": "enabled"}, + ] + cluster_arn = ecs_client.create_cluster( + clusterName=CLUSTER_NAME, + settings=cluster_settings, + )["cluster"]["clusterArn"] + + from prowler.providers.aws.services.ecs.ecs_service import ECS + + 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.ecs.ecs_cluster_container_insights_enabled.ecs_cluster_container_insights_enabled.ecs_client", + new=ECS(aws_provider), + ): + from prowler.providers.aws.services.ecs.ecs_cluster_container_insights_enabled.ecs_cluster_container_insights_enabled import ( + ecs_cluster_container_insights_enabled, + ) + + check = ecs_cluster_container_insights_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].resource_arn == cluster_arn + assert ( + result[0].status_extended + == f"ECS cluster {CLUSTER_NAME} has container insights enabled." + ) + + @mock_aws + def test_cluster_disabled_container_insights(self): + ecs_client = client("ecs", region_name=AWS_REGION_US_EAST_1) + cluster_settings = [ + {"name": "containerInsights", "value": "disabled"}, + ] + cluster_arn = ecs_client.create_cluster( + clusterName=CLUSTER_NAME, + settings=cluster_settings, + )["cluster"]["clusterArn"] + + from prowler.providers.aws.services.ecs.ecs_service import ECS + + 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.ecs.ecs_cluster_container_insights_enabled.ecs_cluster_container_insights_enabled.ecs_client", + new=ECS(aws_provider), + ): + from prowler.providers.aws.services.ecs.ecs_cluster_container_insights_enabled.ecs_cluster_container_insights_enabled import ( + ecs_cluster_container_insights_enabled, + ) + + check = ecs_cluster_container_insights_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].resource_arn == cluster_arn + assert ( + result[0].status_extended + == f"ECS cluster {CLUSTER_NAME} does not have container insights enabled." + ) diff --git a/tests/providers/aws/services/ecs/ecs_service_fargate_latest_platform_version/ecs_service_fargate_latest_platform_version_test.py b/tests/providers/aws/services/ecs/ecs_service_fargate_latest_platform_version/ecs_service_fargate_latest_platform_version_test.py new file mode 100644 index 0000000000..89e032136f --- /dev/null +++ b/tests/providers/aws/services/ecs/ecs_service_fargate_latest_platform_version/ecs_service_fargate_latest_platform_version_test.py @@ -0,0 +1,368 @@ +from unittest.mock import patch + +import botocore +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, +) + +orig = botocore.client.BaseClient._make_api_call + + +def mock_make_api_call(self, operation_name, kwarg): + if operation_name == "DescribeServices": + if kwarg["services"] == [ + f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:service/test-cluster/test-latest-linux-service" + ]: + return { + "services": [ + { + "serviceName": "test-latest-linux-service", + "clusterArn": f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:cluster/test-cluster", + "taskDefinition": "test-task", + "loadBalancers": [], + "serviceArn": f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:service/test-cluster/test-latest-linux-service", + "desiredCount": 1, + "launchType": "FARGATE", + "platformVersion": "1.4.0", + "platformFamily": "Linux", + "networkConfiguration": { + "awsvpcConfiguration": { + "subnets": ["subnet-12345678"], + "securityGroups": ["sg-12345678"], + "assignPublicIp": "DISABLED", + }, + }, + "tags": [], + }, + ], + } + elif kwarg["services"] == [ + f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:service/test-cluster/test-latest-windows-service" + ]: + return { + "services": [ + { + "serviceName": "test-latest-windows-service", + "clusterArn": f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:cluster/test-cluster", + "taskDefinition": "test-task", + "loadBalancers": [], + "serviceArn": f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:service/test-cluster/test-latest-windows-service", + "desiredCount": 1, + "launchType": "FARGATE", + "platformVersion": "1.0.0", + "platformFamily": "Windows", + "networkConfiguration": { + "awsvpcConfiguration": { + "subnets": ["subnet-12345678"], + "securityGroups": ["sg-12345678"], + "assignPublicIp": "DISABLED", + }, + }, + "tags": [], + }, + ], + } + elif kwarg["services"] == [ + f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:service/test-cluster/test-no-latest-linux-service" + ]: + return { + "services": [ + { + "serviceName": "test-no-latest-linux-service", + "clusterArn": f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:cluster/test-cluster", + "taskDefinition": "test-task", + "loadBalancers": [], + "serviceArn": f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:service/test-cluster/test-no-latest-linux-service", + "desiredCount": 1, + "launchType": "FARGATE", + "platformVersion": "1.2.0", + "platformFamily": "Linux", + "networkConfiguration": { + "awsvpcConfiguration": { + "subnets": ["subnet-12345678"], + "securityGroups": ["sg-12345678"], + "assignPublicIp": "DISABLED", + }, + }, + "tags": [], + }, + ], + } + elif kwarg["services"] == [ + f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:service/test-cluster/test-no-latest-windows-service" + ]: + return { + "services": [ + { + "serviceName": "test-no-latest-windows-service", + "clusterArn": f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:cluster/test-cluster", + "taskDefinition": "test-task", + "loadBalancers": [], + "serviceArn": f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:service/test-cluster/test-no-latest-windows-service", + "desiredCount": 1, + "launchType": "FARGATE", + "platformVersion": "0.9.0", + "platformFamily": "Windows", + "networkConfiguration": { + "awsvpcConfiguration": { + "subnets": ["subnet-12345678"], + "securityGroups": ["sg-12345678"], + "assignPublicIp": "DISABLED", + }, + }, + "tags": [], + }, + ], + } + return orig(self, operation_name, kwarg) + + +class Test_ecs_service_fargate_latest_platform_version: + def test_no_services(self): + from prowler.providers.aws.services.ecs.ecs_service import ECS + + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_service_fargate_latest_platform_version.ecs_service_fargate_latest_platform_version.ecs_client", + new=ECS(mocked_aws_provider), + ): + from prowler.providers.aws.services.ecs.ecs_service_fargate_latest_platform_version.ecs_service_fargate_latest_platform_version import ( + ecs_service_fargate_latest_platform_version, + ) + + check = ecs_service_fargate_latest_platform_version() + result = check.execute() + assert len(result) == 0 + + @mock_aws + def test_service_ec2_type(self): + ecs_client = client("ecs", region_name=AWS_REGION_US_EAST_1) + + ecs_client.create_cluster(clusterName="test-cluster") + + ecs_client.create_service( + cluster="test-cluster", + serviceName="test-service", + launchType="EC2", + platformVersion="1.4.0", + desiredCount=1, + clientToken="test-token", + ) + + from prowler.providers.aws.services.ecs.ecs_service import ECS + + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_service_fargate_latest_platform_version.ecs_service_fargate_latest_platform_version.ecs_client", + new=ECS(mocked_aws_provider), + ): + from prowler.providers.aws.services.ecs.ecs_service_fargate_latest_platform_version.ecs_service_fargate_latest_platform_version import ( + ecs_service_fargate_latest_platform_version, + ) + + check = ecs_service_fargate_latest_platform_version() + result = check.execute() + assert len(result) == 0 + + @patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) + @mock_aws + def test_service_linux_latest_version(self): + ecs_client = client("ecs", region_name=AWS_REGION_US_EAST_1) + + ecs_client.create_cluster(clusterName="test-cluster") + + ecs_client.create_service( + cluster="test-cluster", + serviceName="test-latest-linux-service", + launchType="FARGATE", + platformVersion="1.4.0", + desiredCount=1, + clientToken="test-token", + ) + + from prowler.providers.aws.services.ecs.ecs_service import ECS + + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + mocked_ecs_client = ECS(mocked_aws_provider) + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_service_fargate_latest_platform_version.ecs_service_fargate_latest_platform_version.ecs_client", + new=mocked_ecs_client, + ): + from prowler.providers.aws.services.ecs.ecs_service_fargate_latest_platform_version.ecs_service_fargate_latest_platform_version import ( + ecs_service_fargate_latest_platform_version, + ) + + check = ecs_service_fargate_latest_platform_version() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].status_extended == ( + "ECS Service test-latest-linux-service is using latest FARGATE Linux version 1.4.0." + ) + assert result[0].resource_id == "test-latest-linux-service" + assert ( + result[0].resource_arn + == f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:service/test-cluster/test-latest-linux-service" + ) + assert result[0].resource_tags == [] + assert result[0].region == AWS_REGION_US_EAST_1 + + @patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) + @mock_aws + def test_service_windows_latest_version(self): + ecs_client = client("ecs", region_name=AWS_REGION_US_EAST_1) + + ecs_client.create_cluster(clusterName="test-cluster") + + ecs_client.create_service( + cluster="test-cluster", + serviceName="test-latest-windows-service", + launchType="FARGATE", + platformVersion="1.0.0", + desiredCount=1, + clientToken="test-token", + ) + + ecs_client.audit_config = { + "fargate_windows_latest_version": "1.0.0", + } + + from prowler.providers.aws.services.ecs.ecs_service import ECS + + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_service_fargate_latest_platform_version.ecs_service_fargate_latest_platform_version.ecs_client", + new=ECS(mocked_aws_provider), + ): + from prowler.providers.aws.services.ecs.ecs_service_fargate_latest_platform_version.ecs_service_fargate_latest_platform_version import ( + ecs_service_fargate_latest_platform_version, + ) + + check = ecs_service_fargate_latest_platform_version() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].status_extended == ( + "ECS Service test-latest-windows-service is using latest FARGATE Windows version 1.0.0." + ) + assert result[0].resource_id == "test-latest-windows-service" + assert ( + result[0].resource_arn + == f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:service/test-cluster/test-latest-windows-service" + ) + assert result[0].resource_tags == [] + assert result[0].region == AWS_REGION_US_EAST_1 + + @patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) + @mock_aws + def test_service_linux_no_latest_version(self): + ecs_client = client("ecs", region_name=AWS_REGION_US_EAST_1) + + ecs_client.create_cluster(clusterName="test-cluster") + + ecs_client.create_service( + cluster="test-cluster", + serviceName="test-no-latest-linux-service", + launchType="FARGATE", + platformVersion="1.2.0", + desiredCount=1, + clientToken="test-token", + ) + + from prowler.providers.aws.services.ecs.ecs_service import ECS + + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_service_fargate_latest_platform_version.ecs_service_fargate_latest_platform_version.ecs_client", + new=ECS(mocked_aws_provider), + ): + from prowler.providers.aws.services.ecs.ecs_service_fargate_latest_platform_version.ecs_service_fargate_latest_platform_version import ( + ecs_service_fargate_latest_platform_version, + ) + + check = ecs_service_fargate_latest_platform_version() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].status_extended == ( + "ECS Service test-no-latest-linux-service is not using latest FARGATE Linux version 1.4.0, currently using 1.2.0." + ) + assert result[0].resource_id == "test-no-latest-linux-service" + assert ( + result[0].resource_arn + == f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:service/test-cluster/test-no-latest-linux-service" + ) + assert result[0].resource_tags == [] + assert result[0].region == AWS_REGION_US_EAST_1 + + @patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) + @mock_aws + def test_service_windows_no_latest_version(self): + ecs_client = client("ecs", region_name=AWS_REGION_US_EAST_1) + + ecs_client.create_cluster(clusterName="test-cluster") + + ecs_client.create_service( + cluster="test-cluster", + serviceName="test-no-latest-windows-service", + launchType="FARGATE", + platformVersion="0.9.0", + desiredCount=1, + clientToken="test-token", + ) + + from prowler.providers.aws.services.ecs.ecs_service import ECS + + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_service_fargate_latest_platform_version.ecs_service_fargate_latest_platform_version.ecs_client", + new=ECS(mocked_aws_provider), + ): + from prowler.providers.aws.services.ecs.ecs_service_fargate_latest_platform_version.ecs_service_fargate_latest_platform_version import ( + ecs_service_fargate_latest_platform_version, + ) + + check = ecs_service_fargate_latest_platform_version() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].status_extended == ( + "ECS Service test-no-latest-windows-service is not using latest FARGATE Windows version 1.0.0, currently using 0.9.0." + ) + assert result[0].resource_id == "test-no-latest-windows-service" + assert ( + result[0].resource_arn + == f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:service/test-cluster/test-no-latest-windows-service" + ) + assert result[0].resource_tags == [] + assert result[0].region == AWS_REGION_US_EAST_1 diff --git a/tests/providers/aws/services/ecs/ecs_service_no_assign_public_ip/ecs_service_no_assign_public_ip_test.py b/tests/providers/aws/services/ecs/ecs_service_no_assign_public_ip/ecs_service_no_assign_public_ip_test.py index f2fc01aa4a..38753ffeae 100644 --- a/tests/providers/aws/services/ecs/ecs_service_no_assign_public_ip/ecs_service_no_assign_public_ip_test.py +++ b/tests/providers/aws/services/ecs/ecs_service_no_assign_public_ip/ecs_service_no_assign_public_ip_test.py @@ -1,22 +1,87 @@ -from unittest import mock +from unittest.mock import patch -from prowler.providers.aws.services.ecs.ecs_service import Service -from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER, AWS_REGION_US_EAST_1 +import botocore +from boto3 import client +from moto import mock_aws -SERVICE_ARN = ( - f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:service/sample-service" +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, ) -SERVICE_NAME = "sample-service" + +orig = botocore.client.BaseClient._make_api_call + + +def mock_make_api_call(self, operation_name, kwarg): + if operation_name == "DescribeServices": + if kwarg["services"] == [ + f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:service/sample-cluster/service-with-no-public-ip" + ]: + return { + "services": [ + { + "serviceName": "test-latest-linux-service", + "clusterArn": f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:cluster/sample-cluster", + "taskDefinition": "test-task", + "loadBalancers": [], + "serviceArn": f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:service/sample-cluster/service-with-no-public-ip", + "desiredCount": 1, + "launchType": "FARGATE", + "platformVersion": "1.4.0", + "platformFamily": "Linux", + "networkConfiguration": { + "awsvpcConfiguration": { + "subnets": ["subnet-12345678"], + "securityGroups": ["sg-12345678"], + "assignPublicIp": "DISABLED", + }, + }, + "tags": [], + }, + ], + } + elif kwarg["services"] == [ + f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:service/sample-cluster/service-with-public-ip" + ]: + return { + "services": [ + { + "serviceName": "test-latest-linux-service", + "clusterArn": f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:cluster/sample-cluster", + "taskDefinition": "test-task", + "loadBalancers": [], + "serviceArn": f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:service/sample-cluster/service-with-public-ip", + "desiredCount": 1, + "launchType": "FARGATE", + "platformVersion": "1.4.0", + "platformFamily": "Linux", + "networkConfiguration": { + "awsvpcConfiguration": { + "subnets": ["subnet-12345678"], + "securityGroups": ["sg-12345678"], + "assignPublicIp": "ENABLED", + }, + }, + "tags": [], + }, + ], + } + return orig(self, operation_name, kwarg) class Test_ecs_service_no_assign_public_ip: def test_no_services(self): - ecs_client = mock.MagicMock - ecs_client.services = {} + from prowler.providers.aws.services.ecs.ecs_service import ECS - with mock.patch( - "prowler.providers.aws.services.ecs.ecs_service.ECS", - ecs_client, + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_service_no_assign_public_ip.ecs_service_no_assign_public_ip.ecs_client", + new=ECS(mocked_aws_provider), ): from prowler.providers.aws.services.ecs.ecs_service_no_assign_public_ip.ecs_service_no_assign_public_ip import ( ecs_service_no_assign_public_ip, @@ -26,20 +91,37 @@ class Test_ecs_service_no_assign_public_ip: result = check.execute() assert len(result) == 0 + @patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) + @mock_aws def test_service_with_no_public_ip(self): - ecs_client = mock.MagicMock - ecs_client.services = {} - ecs_client.services[SERVICE_ARN] = Service( - name=SERVICE_NAME, - arn=SERVICE_ARN, - region=AWS_REGION_US_EAST_1, - assign_public_ip=False, - tags=[], - ) + ecs_client = client("ecs", region_name=AWS_REGION_US_EAST_1) - with mock.patch( - "prowler.providers.aws.services.ecs.ecs_service.ECS", - ecs_client, + ecs_client.create_cluster(clusterName="sample-cluster") + + service_arn = ecs_client.create_service( + cluster="sample-cluster", + serviceName="service-with-no-public-ip", + desiredCount=1, + launchType="FARGATE", + networkConfiguration={ + "awsvpcConfiguration": { + "subnets": ["subnet-123456"], + "securityGroups": ["sg-123456"], + "assignPublicIp": "DISABLED", + } + }, + )["service"]["serviceArn"] + + from prowler.providers.aws.services.ecs.ecs_service import ECS + + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_service_no_assign_public_ip.ecs_service_no_assign_public_ip.ecs_client", + new=ECS(mocked_aws_provider), ): from prowler.providers.aws.services.ecs.ecs_service_no_assign_public_ip.ecs_service_no_assign_public_ip import ( ecs_service_no_assign_public_ip, @@ -51,25 +133,44 @@ class Test_ecs_service_no_assign_public_ip: assert result[0].status == "PASS" assert ( result[0].status_extended - == f"ECS Service {SERVICE_NAME} does not have automatic public IP assignment." + == "ECS Service service-with-no-public-ip does not have automatic public IP assignment." ) - assert result[0].resource_id == SERVICE_NAME - assert result[0].resource_arn == SERVICE_ARN + assert result[0].resource_id == "service-with-no-public-ip" + assert result[0].resource_arn == service_arn + assert result[0].resource_tags == [] + assert result[0].region == AWS_REGION_US_EAST_1 + @patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) + @mock_aws def test_task_definition_no_host_network_mode(self): - ecs_client = mock.MagicMock - ecs_client.services = {} - ecs_client.services[SERVICE_ARN] = Service( - name=SERVICE_NAME, - arn=SERVICE_ARN, - region=AWS_REGION_US_EAST_1, - assign_public_ip=True, - tags=[], - ) + ecs_client = client("ecs", region_name=AWS_REGION_US_EAST_1) - with mock.patch( - "prowler.providers.aws.services.ecs.ecs_service.ECS", - ecs_client, + ecs_client.create_cluster(clusterName="sample-cluster") + + service_arn = ecs_client.create_service( + cluster="sample-cluster", + serviceName="service-with-public-ip", + desiredCount=1, + launchType="FARGATE", + networkConfiguration={ + "awsvpcConfiguration": { + "subnets": ["subnet-123456"], + "securityGroups": ["sg-123456"], + "assignPublicIp": "ENABLED", + } + }, + )["service"]["serviceArn"] + + from prowler.providers.aws.services.ecs.ecs_service import ECS + + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_service_no_assign_public_ip.ecs_service_no_assign_public_ip.ecs_client", + new=ECS(mocked_aws_provider), ): from prowler.providers.aws.services.ecs.ecs_service_no_assign_public_ip.ecs_service_no_assign_public_ip import ( ecs_service_no_assign_public_ip, @@ -81,7 +182,9 @@ class Test_ecs_service_no_assign_public_ip: assert result[0].status == "FAIL" assert ( result[0].status_extended - == f"ECS Service {SERVICE_NAME} has automatic public IP assignment." + == "ECS Service service-with-public-ip has automatic public IP assignment." ) - assert result[0].resource_id == SERVICE_NAME - assert result[0].resource_arn == SERVICE_ARN + assert result[0].resource_id == "service-with-public-ip" + assert result[0].resource_arn == service_arn + assert result[0].resource_tags == [] + assert result[0].region == AWS_REGION_US_EAST_1 diff --git a/tests/providers/aws/services/ecs/ecs_service_test.py b/tests/providers/aws/services/ecs/ecs_service_test.py index 26cbfbfc0b..76f7abbc77 100644 --- a/tests/providers/aws/services/ecs/ecs_service_test.py +++ b/tests/providers/aws/services/ecs/ecs_service_test.py @@ -12,7 +12,7 @@ def mock_make_api_call(self, operation_name, kwarg): if operation_name == "ListTaskDefinitions": return { "taskDefinitionArns": [ - "arn:aws:ecs:eu-west-1:123456789012:task-definition/test_ecs_task:1" + "arn:aws:ecs:eu-west-1:123456789012:task-definition/test_cluster_1/test_ecs_task:1" ] } if operation_name == "DescribeTaskDefinition": @@ -44,7 +44,6 @@ def mock_make_api_call(self, operation_name, kwarg): { "serviceArn": "arn:aws:ecs:eu-west-1:123456789012:service/test_cluster_1/test_ecs_service", "serviceName": "test_ecs_service", - "launchType": "EC2", "networkConfiguration": { "awsvpcConfiguration": { "subnets": ["subnet-12345678"], @@ -52,6 +51,9 @@ def mock_make_api_call(self, operation_name, kwarg): "assignPublicIp": "ENABLED", } }, + "launchType": "FARGATE", + "platformVersion": "1.4.0", + "platformFamily": "Linux", } ] } @@ -69,6 +71,9 @@ def mock_make_api_call(self, operation_name, kwarg): "clusterName": "test_cluster_1", "status": "ACTIVE", "tags": [{"key": "Name", "value": "test_cluster_1"}], + "settings": [ + {"name": "containerInsights", "value": "enabled"}, + ], "registeredContainerInstancesCount": 5, "runningTasksCount": 10, "pendingTasksCount": 1, @@ -117,7 +122,7 @@ class Test_ECS_Service: aws_provider = set_mocked_aws_provider() ecs = ECS(aws_provider) - task_arn = "arn:aws:ecs:eu-west-1:123456789012:task-definition/test_ecs_task:1" + task_arn = "arn:aws:ecs:eu-west-1:123456789012:task-definition/test_cluster_1/test_ecs_task:1" assert len(ecs.task_definitions) == 1 assert ecs.task_definitions[task_arn].name == "test_ecs_task" @@ -131,7 +136,7 @@ class Test_ECS_Service: aws_provider = set_mocked_aws_provider() ecs = ECS(aws_provider) - task_arn = "arn:aws:ecs:eu-west-1:123456789012:task-definition/test_ecs_task:1" + task_arn = "arn:aws:ecs:eu-west-1:123456789012:task-definition/test_cluster_1/test_ecs_task:1" assert len(ecs.task_definitions) == 1 assert ecs.task_definitions[task_arn].name == "test_ecs_task" @@ -195,6 +200,9 @@ class Test_ECS_Service: assert ecs.clusters[cluster_arn1].tags == [ {"key": "Name", "value": "test_cluster_1"} ] + assert ecs.clusters[cluster_arn1].settings == [ + {"name": "containerInsights", "value": "enabled"} + ] @patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) # Test describe ECS services @@ -212,3 +220,6 @@ class Test_ECS_Service: assert ecs.services[service_arn].region == AWS_REGION_EU_WEST_1 assert ecs.services[service_arn].assign_public_ip assert ecs.services[service_arn].tags == [] + assert ecs.services[service_arn].launch_type == "FARGATE" + assert ecs.services[service_arn].platform_version == "1.4.0" + assert ecs.services[service_arn].platform_family == "Linux" diff --git a/tests/providers/aws/services/ecs/ecs_task_definitions_containers_readonly_access/ecs_task_definitions_containers_readonly_access_test.py b/tests/providers/aws/services/ecs/ecs_task_definitions_containers_readonly_access/ecs_task_definitions_containers_readonly_access_test.py index 13417518dd..a162ef87d8 100644 --- a/tests/providers/aws/services/ecs/ecs_task_definitions_containers_readonly_access/ecs_task_definitions_containers_readonly_access_test.py +++ b/tests/providers/aws/services/ecs/ecs_task_definitions_containers_readonly_access/ecs_task_definitions_containers_readonly_access_test.py @@ -1,10 +1,13 @@ -from unittest import mock +from unittest.mock import patch -from prowler.providers.aws.services.ecs.ecs_service import ( - ContainerDefinition, - TaskDefinition, +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, ) -from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER, AWS_REGION_US_EAST_1 TASK_NAME = "test-task-readonly" TASK_REVISION = "1" @@ -14,12 +17,16 @@ TASK_ARN = f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:task-defini class Test_ecs_task_definitions_containers_readonly_access: def test_no_task_definitions(self): - ecs_client = mock.MagicMock - ecs_client.task_definitions = {} + from prowler.providers.aws.services.ecs.ecs_service import ECS - with mock.patch( - "prowler.providers.aws.services.ecs.ecs_service.ECS", - ecs_client, + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_task_definitions_containers_readonly_access.ecs_task_definitions_containers_readonly_access.ecs_client", + new=ECS(mocked_aws_provider), ): from prowler.providers.aws.services.ecs.ecs_task_definitions_containers_readonly_access.ecs_task_definitions_containers_readonly_access import ( ecs_task_definitions_containers_readonly_access, @@ -29,29 +36,35 @@ class Test_ecs_task_definitions_containers_readonly_access: result = check.execute() assert len(result) == 0 + @mock_aws def test_task_definition_all_containers_readonly(self): - ecs_client = mock.MagicMock - ecs_client.task_definitions = {} - ecs_client.task_definitions[TASK_ARN] = TaskDefinition( - name=TASK_NAME, - arn=TASK_ARN, - revision=TASK_REVISION, - region=AWS_REGION_US_EAST_1, - network_mode="bridge", - container_definitions=[ - ContainerDefinition( - name=CONTAINER_NAME, - readonly_rootfilesystem=True, - privileged=False, - user="appuser", - environment=[], - ) - ], - ) + ecs_client = client("ecs", region_name=AWS_REGION_US_EAST_1) - with mock.patch( - "prowler.providers.aws.services.ecs.ecs_service.ECS", - ecs_client, + task_definition_arn = ecs_client.register_task_definition( + family=TASK_NAME, + containerDefinitions=[ + { + "name": CONTAINER_NAME, + "image": "ubuntu", + "memory": 128, + "readonlyRootFilesystem": True, + "privileged": False, + "user": "appuser", + "environment": [], + } + ], + )["taskDefinition"]["taskDefinitionArn"] + + from prowler.providers.aws.services.ecs.ecs_service import ECS + + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_task_definitions_containers_readonly_access.ecs_task_definitions_containers_readonly_access.ecs_client", + new=ECS(mocked_aws_provider), ): from prowler.providers.aws.services.ecs.ecs_task_definitions_containers_readonly_access.ecs_task_definitions_containers_readonly_access import ( ecs_task_definitions_containers_readonly_access, @@ -65,30 +78,40 @@ class Test_ecs_task_definitions_containers_readonly_access: result[0].status_extended == f"ECS task definition {TASK_NAME} with revision {TASK_REVISION} does not have containers with write access to the root filesystems." ) + assert result[0].resource_id == f"{TASK_NAME}:{TASK_REVISION}" + assert result[0].resource_arn == task_definition_arn + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] + @mock_aws def test_task_definition_some_containers_not_readonly(self): - ecs_client = mock.MagicMock - ecs_client.task_definitions = {} - ecs_client.task_definitions[TASK_ARN] = TaskDefinition( - name=TASK_NAME, - arn=TASK_ARN, - revision=TASK_REVISION, - region=AWS_REGION_US_EAST_1, - network_mode="bridge", - container_definitions=[ - ContainerDefinition( - name=CONTAINER_NAME, - readonly_rootfilesystem=False, - privileged=False, - user="appuser", - environment=[], - ) + ecs_client = client("ecs", region_name=AWS_REGION_US_EAST_1) + + task_definition_arn = ecs_client.register_task_definition( + family=TASK_NAME, + containerDefinitions=[ + { + "name": CONTAINER_NAME, + "image": "ubuntu", + "memory": 128, + "readonlyRootFilesystem": False, + "privileged": False, + "user": "appuser", + "environment": [], + } ], - ) + )["taskDefinition"]["taskDefinitionArn"] - with mock.patch( - "prowler.providers.aws.services.ecs.ecs_service.ECS", - ecs_client, + from prowler.providers.aws.services.ecs.ecs_service import ECS + + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_task_definitions_containers_readonly_access.ecs_task_definitions_containers_readonly_access.ecs_client", + new=ECS(mocked_aws_provider), ): from prowler.providers.aws.services.ecs.ecs_task_definitions_containers_readonly_access.ecs_task_definitions_containers_readonly_access import ( ecs_task_definitions_containers_readonly_access, @@ -102,38 +125,49 @@ class Test_ecs_task_definitions_containers_readonly_access: result[0].status_extended == f"ECS task definition {TASK_NAME} with revision {TASK_REVISION} has containers with write access to the root filesystem: {CONTAINER_NAME}" ) + assert result[0].resource_id == f"{TASK_NAME}:{TASK_REVISION}" + assert result[0].resource_arn == task_definition_arn + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] + @mock_aws def test_task_definition_mixed_containers(self): - ecs_client = mock.MagicMock - ecs_client.task_definitions = { - TASK_ARN: TaskDefinition( - name=TASK_NAME, - arn=TASK_ARN, - revision=TASK_REVISION, - region=AWS_REGION_US_EAST_1, - network_mode="bridge", - container_definitions=[ - ContainerDefinition( - name=CONTAINER_NAME, - readonly_rootfilesystem=False, - privileged=False, - user="appuser", - environment=[], - ), - ContainerDefinition( - name="readonly-container", - readonly_rootfilesystem=True, - privileged=False, - user="appuser", - environment=[], - ), - ], - ) - } + ecs_client = client("ecs", region_name=AWS_REGION_US_EAST_1) - with mock.patch( - "prowler.providers.aws.services.ecs.ecs_service.ECS", - ecs_client, + task_definition_arn = ecs_client.register_task_definition( + family=TASK_NAME, + containerDefinitions=[ + { + "name": CONTAINER_NAME, + "image": "ubuntu", + "memory": 128, + "readonlyRootFilesystem": False, # Not readonly + "privileged": False, + "user": "appuser", + "environment": [], + }, + { + "name": "readonly-container", + "image": "ubuntu", + "memory": 128, + "readonlyRootFilesystem": True, # Readonly + "privileged": False, + "user": "appuser", + "environment": [], + }, + ], + )["taskDefinition"]["taskDefinitionArn"] + + from prowler.providers.aws.services.ecs.ecs_service import ECS + + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_task_definitions_containers_readonly_access.ecs_task_definitions_containers_readonly_access.ecs_client", + new=ECS(mocked_aws_provider), ): from prowler.providers.aws.services.ecs.ecs_task_definitions_containers_readonly_access.ecs_task_definitions_containers_readonly_access import ( ecs_task_definitions_containers_readonly_access, @@ -147,3 +181,7 @@ class Test_ecs_task_definitions_containers_readonly_access: result[0].status_extended == f"ECS task definition {TASK_NAME} with revision {TASK_REVISION} has containers with write access to the root filesystem: {CONTAINER_NAME}" ) + assert result[0].resource_id == f"{TASK_NAME}:{TASK_REVISION}" + assert result[0].resource_arn == task_definition_arn + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] diff --git a/tests/providers/aws/services/ecs/ecs_task_definitions_host_namespace_not_shared/ecs_task_definitions_host_namespace_not_shared_test.py b/tests/providers/aws/services/ecs/ecs_task_definitions_host_namespace_not_shared/ecs_task_definitions_host_namespace_not_shared_test.py index c88a9a6c7e..92346124e0 100644 --- a/tests/providers/aws/services/ecs/ecs_task_definitions_host_namespace_not_shared/ecs_task_definitions_host_namespace_not_shared_test.py +++ b/tests/providers/aws/services/ecs/ecs_task_definitions_host_namespace_not_shared/ecs_task_definitions_host_namespace_not_shared_test.py @@ -1,33 +1,28 @@ -from unittest import mock +from unittest.mock import patch -from prowler.providers.aws.services.ecs.ecs_service import ( - ContainerDefinition, - ContainerEnvVariable, - TaskDefinition, -) -from tests.providers.aws.utils import ( - AWS_ACCOUNT_NUMBER, - AWS_REGION_US_EAST_1, - set_mocked_aws_provider, -) +from boto3 import client +from moto import mock_aws -task_name = "test-task" -task_revision = "1" -container_name = "test-container" -task_arn = f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:task-definition/{task_name}:{task_revision}" +from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider + +TASK_NAME = "test-task" +TASK_REVISION = "1" +CONTAINER_NAME = "test-container" class Test_ecs_task_definitions_host_namespace_not_shared: + @mock_aws def test_no_task_definitions(self): - ecs_client = mock.MagicMock - ecs_client.task_definitions = {} + from prowler.providers.aws.services.ecs.ecs_service import ECS - with mock.patch( + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( "prowler.providers.common.provider.Provider.get_global_provider", - return_value=set_mocked_aws_provider([AWS_REGION_US_EAST_1]), - ), mock.patch( - "prowler.providers.aws.services.ecs.ecs_service.ECS", - ecs_client, + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_task_definitions_host_namespace_not_shared.ecs_task_definitions_host_namespace_not_shared.ecs_client", + new=ECS(mocked_aws_provider), ): from prowler.providers.aws.services.ecs.ecs_task_definitions_host_namespace_not_shared.ecs_task_definitions_host_namespace_not_shared import ( ecs_task_definitions_host_namespace_not_shared, @@ -37,37 +32,36 @@ class Test_ecs_task_definitions_host_namespace_not_shared: result = check.execute() assert len(result) == 0 + @mock_aws def test_task_definition_no_host_pid_mode(self): - ecs_client = mock.MagicMock - ecs_client.task_definitions = {} - ecs_client.task_definitions[task_arn] = TaskDefinition( - name=task_name, - arn=task_arn, - revision=task_revision, - region=AWS_REGION_US_EAST_1, - network_mode="bridge", - pid_mode="task", - container_definitions=[ - ContainerDefinition( - name=container_name, - privileged=False, - user="", - environment=[ - ContainerEnvVariable( - name="env_var_name_no_secrets", - value="env_var_value_no_secrets", - ) - ], - ) - ], - ) + ecs_client = client("ecs", region_name=AWS_REGION_US_EAST_1) - with mock.patch( + task_arn = ecs_client.register_task_definition( + family=TASK_NAME, + containerDefinitions=[ + { + "name": CONTAINER_NAME, + "image": "ubuntu", + "memory": 128, + "readonlyRootFilesystem": True, + "privileged": False, + "user": "appuser", + "environment": [], + } + ], + pidMode="task", + )["taskDefinition"]["taskDefinitionArn"] + + from prowler.providers.aws.services.ecs.ecs_service import ECS + + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( "prowler.providers.common.provider.Provider.get_global_provider", - return_value=set_mocked_aws_provider([AWS_REGION_US_EAST_1]), - ), mock.patch( - "prowler.providers.aws.services.ecs.ecs_service.ECS", - ecs_client, + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_task_definitions_host_namespace_not_shared.ecs_task_definitions_host_namespace_not_shared.ecs_client", + new=ECS(mocked_aws_provider), ): from prowler.providers.aws.services.ecs.ecs_task_definitions_host_namespace_not_shared.ecs_task_definitions_host_namespace_not_shared import ( ecs_task_definitions_host_namespace_not_shared, @@ -79,35 +73,43 @@ class Test_ecs_task_definitions_host_namespace_not_shared: assert result[0].status == "PASS" assert ( result[0].status_extended - == f"ECS task definition {task_name} with revision {task_revision} does not share a host's process namespace with its containers." + == f"ECS task definition {TASK_NAME} with revision {TASK_REVISION} does not share a host's process namespace with its containers." ) + assert result[0].resource_id == f"{TASK_NAME}:{TASK_REVISION}" + assert result[0].resource_arn == task_arn + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] + @mock_aws def test_task_definition_host_pid_mode(self): - ecs_client = mock.MagicMock - ecs_client.task_definitions = {} - ecs_client.task_definitions[task_arn] = TaskDefinition( - name=task_name, - arn=task_arn, - revision=task_revision, - region=AWS_REGION_US_EAST_1, - network_mode="host", - pid_mode="host", - container_definitions=[ - ContainerDefinition( - name=container_name, - privileged=False, - user="root", - environment=[], - ) - ], - ) + ecs_client = client("ecs", region_name=AWS_REGION_US_EAST_1) - with mock.patch( + task_arn = ecs_client.register_task_definition( + family=TASK_NAME, + containerDefinitions=[ + { + "name": CONTAINER_NAME, + "image": "ubuntu", + "memory": 128, + "readonlyRootFilesystem": True, + "privileged": False, + "user": "appuser", + "environment": [], + } + ], + pidMode="host", + )["taskDefinition"]["taskDefinitionArn"] + + from prowler.providers.aws.services.ecs.ecs_service import ECS + + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( "prowler.providers.common.provider.Provider.get_global_provider", - return_value=set_mocked_aws_provider([AWS_REGION_US_EAST_1]), - ), mock.patch( - "prowler.providers.aws.services.ecs.ecs_service.ECS", - ecs_client, + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_task_definitions_host_namespace_not_shared.ecs_task_definitions_host_namespace_not_shared.ecs_client", + new=ECS(mocked_aws_provider), ): from prowler.providers.aws.services.ecs.ecs_task_definitions_host_namespace_not_shared.ecs_task_definitions_host_namespace_not_shared import ( ecs_task_definitions_host_namespace_not_shared, @@ -119,35 +121,42 @@ class Test_ecs_task_definitions_host_namespace_not_shared: assert result[0].status == "FAIL" assert ( result[0].status_extended - == f"ECS task definition {task_name} with revision {task_revision} is configured to share a host's process namespace with its containers." + == f"ECS task definition {TASK_NAME} with revision {TASK_REVISION} is configured to share a host's process namespace with its containers." ) + assert result[0].resource_id == f"{TASK_NAME}:{TASK_REVISION}" + assert result[0].resource_arn == task_arn + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] + @mock_aws def test_task_definition_no_pid_mode(self): - ecs_client = mock.MagicMock - ecs_client.task_definitions = {} - ecs_client.task_definitions[task_arn] = TaskDefinition( - name=task_name, - arn=f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:task-definition/{task_name}:{task_revision}", - revision=task_revision, - region=AWS_REGION_US_EAST_1, - network_mode="host", - pid_mode="", - container_definitions=[ - ContainerDefinition( - name=container_name, - privileged=True, - user="root", - environment=[], - ) - ], - ) + ecs_client = client("ecs", region_name=AWS_REGION_US_EAST_1) - with mock.patch( + task_arn = ecs_client.register_task_definition( + family=TASK_NAME, + containerDefinitions=[ + { + "name": CONTAINER_NAME, + "image": "ubuntu", + "memory": 128, + "readonlyRootFilesystem": True, + "privileged": False, + "user": "appuser", + "environment": [], + } + ], + )["taskDefinition"]["taskDefinitionArn"] + + from prowler.providers.aws.services.ecs.ecs_service import ECS + + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( "prowler.providers.common.provider.Provider.get_global_provider", - return_value=set_mocked_aws_provider([AWS_REGION_US_EAST_1]), - ), mock.patch( - "prowler.providers.aws.services.ecs.ecs_service.ECS", - ecs_client, + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_task_definitions_host_namespace_not_shared.ecs_task_definitions_host_namespace_not_shared.ecs_client", + new=ECS(mocked_aws_provider), ): from prowler.providers.aws.services.ecs.ecs_task_definitions_host_namespace_not_shared.ecs_task_definitions_host_namespace_not_shared import ( ecs_task_definitions_host_namespace_not_shared, @@ -159,5 +168,9 @@ class Test_ecs_task_definitions_host_namespace_not_shared: assert result[0].status == "PASS" assert ( result[0].status_extended - == f"ECS task definition {task_name} with revision {task_revision} does not share a host's process namespace with its containers." + == f"ECS task definition {TASK_NAME} with revision {TASK_REVISION} does not share a host's process namespace with its containers." ) + assert result[0].resource_id == f"{TASK_NAME}:{TASK_REVISION}" + assert result[0].resource_arn == task_arn + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] diff --git a/tests/providers/aws/services/ecs/ecs_task_definitions_host_networking_mode_users/ecs_task_definitions_host_networking_mode_users_test.py b/tests/providers/aws/services/ecs/ecs_task_definitions_host_networking_mode_users/ecs_task_definitions_host_networking_mode_users_test.py index 827d085de3..6458a4e527 100644 --- a/tests/providers/aws/services/ecs/ecs_task_definitions_host_networking_mode_users/ecs_task_definitions_host_networking_mode_users_test.py +++ b/tests/providers/aws/services/ecs/ecs_task_definitions_host_networking_mode_users/ecs_task_definitions_host_networking_mode_users_test.py @@ -1,26 +1,27 @@ -from unittest import mock +from unittest.mock import patch -from prowler.providers.aws.services.ecs.ecs_service import ( - ContainerDefinition, - ContainerEnvVariable, - TaskDefinition, -) -from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER, AWS_REGION_US_EAST_1 +from boto3 import client +from moto import mock_aws -TASK_NAME = "test-task-hostmode" +from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider + +TASK_NAME = "test-task" TASK_REVISION = "1" CONTAINER_NAME = "test-container" -TASK_ARN = f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:task-definition/{TASK_NAME}:{TASK_REVISION}" class Test_ecs_task_definitions_host_networking_mode_users: def test_no_task_definitions(self): - ecs_client = mock.MagicMock - ecs_client.task_definitions = {} + from prowler.providers.aws.services.ecs.ecs_service import ECS - with mock.patch( - "prowler.providers.aws.services.ecs.ecs_service.ECS", - ecs_client, + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_task_definitions_host_networking_mode_users.ecs_task_definitions_host_networking_mode_users.ecs_client", + new=ECS(mocked_aws_provider), ): from prowler.providers.aws.services.ecs.ecs_task_definitions_host_networking_mode_users.ecs_task_definitions_host_networking_mode_users import ( ecs_task_definitions_host_networking_mode_users, @@ -30,33 +31,36 @@ class Test_ecs_task_definitions_host_networking_mode_users: result = check.execute() assert len(result) == 0 + @mock_aws def test_task_definition_no_host_network_mode(self): - ecs_client = mock.MagicMock - ecs_client.task_definitions = {} - ecs_client.task_definitions[TASK_ARN] = TaskDefinition( - name=TASK_NAME, - arn=TASK_ARN, - revision=TASK_REVISION, - region=AWS_REGION_US_EAST_1, - network_mode="bridge", - container_definitions=[ - ContainerDefinition( - name=CONTAINER_NAME, - privileged=False, - user="", - environment=[ - ContainerEnvVariable( - name="env_var_name_no_secrets", - value="env_var_value_no_secrets", - ) - ], - ) - ], - ) + ecs_client = client("ecs", region_name=AWS_REGION_US_EAST_1) - with mock.patch( - "prowler.providers.aws.services.ecs.ecs_service.ECS", - ecs_client, + task_arn = ecs_client.register_task_definition( + family=TASK_NAME, + networkMode="bridge", + containerDefinitions=[ + { + "name": CONTAINER_NAME, + "image": "ubuntu", + "memory": 128, + "readonlyRootFilesystem": True, + "privileged": False, + "user": "appuser", + "environment": [], + } + ], + )["taskDefinition"]["taskDefinitionArn"] + + from prowler.providers.aws.services.ecs.ecs_service import ECS + + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_task_definitions_host_networking_mode_users.ecs_task_definitions_host_networking_mode_users.ecs_client", + new=ECS(mocked_aws_provider), ): from prowler.providers.aws.services.ecs.ecs_task_definitions_host_networking_mode_users.ecs_task_definitions_host_networking_mode_users import ( ecs_task_definitions_host_networking_mode_users, @@ -70,29 +74,41 @@ class Test_ecs_task_definitions_host_networking_mode_users: result[0].status_extended == f"ECS task definition {TASK_NAME} with revision {TASK_REVISION} does not have host network mode." ) + assert result[0].resource_id == f"{TASK_NAME}:{TASK_REVISION}" + assert result[0].resource_arn == task_arn + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] + @mock_aws def test_task_definition_host_mode_container_root_non_privileged(self): - ecs_client = mock.MagicMock - ecs_client.task_definitions = {} - ecs_client.task_definitions[TASK_ARN] = TaskDefinition( - name=TASK_NAME, - arn=TASK_ARN, - revision=TASK_REVISION, - region=AWS_REGION_US_EAST_1, - network_mode="host", - container_definitions=[ - ContainerDefinition( - name=CONTAINER_NAME, - privileged=False, - user="root", - environment=[], - ) - ], - ) + ecs_client = client("ecs", region_name=AWS_REGION_US_EAST_1) - with mock.patch( - "prowler.providers.aws.services.ecs.ecs_service.ECS", - ecs_client, + task_arn = ecs_client.register_task_definition( + family=TASK_NAME, + networkMode="host", + containerDefinitions=[ + { + "name": CONTAINER_NAME, + "image": "ubuntu", + "memory": 128, + "readonlyRootFilesystem": True, + "privileged": False, + "user": "root", + "environment": [], + } + ], + )["taskDefinition"]["taskDefinitionArn"] + + from prowler.providers.aws.services.ecs.ecs_service import ECS + + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_task_definitions_host_networking_mode_users.ecs_task_definitions_host_networking_mode_users.ecs_client", + new=ECS(mocked_aws_provider), ): from prowler.providers.aws.services.ecs.ecs_task_definitions_host_networking_mode_users.ecs_task_definitions_host_networking_mode_users import ( ecs_task_definitions_host_networking_mode_users, @@ -106,29 +122,41 @@ class Test_ecs_task_definitions_host_networking_mode_users: result[0].status_extended == f"ECS task definition {TASK_NAME} with revision {TASK_REVISION} has containers with host network mode and non-privileged containers running as root or with no user specified: {CONTAINER_NAME}" ) + assert result[0].resource_id == f"{TASK_NAME}:{TASK_REVISION}" + assert result[0].resource_arn == task_arn + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] + @mock_aws def test_task_definition_host_mode_container_privileged(self): - ecs_client = mock.MagicMock - ecs_client.task_definitions = {} - ecs_client.task_definitions[TASK_ARN] = TaskDefinition( - name=TASK_NAME, - arn=f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:task-definition/{TASK_NAME}:{TASK_REVISION}", - revision=TASK_REVISION, - region=AWS_REGION_US_EAST_1, - network_mode="host", - container_definitions=[ - ContainerDefinition( - name=CONTAINER_NAME, - privileged=True, - user="root", - environment=[], - ) - ], - ) + ecs_client = client("ecs", region_name=AWS_REGION_US_EAST_1) - with mock.patch( - "prowler.providers.aws.services.ecs.ecs_service.ECS", - ecs_client, + task_arn = ecs_client.register_task_definition( + family=TASK_NAME, + networkMode="host", + containerDefinitions=[ + { + "name": CONTAINER_NAME, + "image": "ubuntu", + "memory": 128, + "readonlyRootFilesystem": True, + "privileged": True, + "user": "root", + "environment": [], + } + ], + )["taskDefinition"]["taskDefinitionArn"] + + from prowler.providers.aws.services.ecs.ecs_service import ECS + + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_task_definitions_host_networking_mode_users.ecs_task_definitions_host_networking_mode_users.ecs_client", + new=ECS(mocked_aws_provider), ): from prowler.providers.aws.services.ecs.ecs_task_definitions_host_networking_mode_users.ecs_task_definitions_host_networking_mode_users import ( ecs_task_definitions_host_networking_mode_users, @@ -142,29 +170,41 @@ class Test_ecs_task_definitions_host_networking_mode_users: result[0].status_extended == f"ECS task definition {TASK_NAME} with revision {TASK_REVISION} has host network mode but no containers running as root or with no user specified." ) + assert result[0].resource_id == f"{TASK_NAME}:{TASK_REVISION}" + assert result[0].resource_arn == task_arn + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] + @mock_aws def test_task_definition_host_mode_container_not_root(self): - ecs_client = mock.MagicMock - ecs_client.task_definitions = {} - ecs_client.task_definitions[TASK_ARN] = TaskDefinition( - name=TASK_NAME, - arn=TASK_ARN, - revision=TASK_REVISION, - region=AWS_REGION_US_EAST_1, - network_mode="host", - container_definitions=[ - ContainerDefinition( - name=CONTAINER_NAME, - privileged=False, - user="appuser", - environment=[], - ) - ], - ) + ecs_client = client("ecs", region_name=AWS_REGION_US_EAST_1) - with mock.patch( - "prowler.providers.aws.services.ecs.ecs_service.ECS", - ecs_client, + task_arn = ecs_client.register_task_definition( + family=TASK_NAME, + networkMode="host", + containerDefinitions=[ + { + "name": CONTAINER_NAME, + "image": "ubuntu", + "memory": 128, + "readonlyRootFilesystem": True, + "privileged": False, + "user": "appuser", + "environment": [], + } + ], + )["taskDefinition"]["taskDefinitionArn"] + + from prowler.providers.aws.services.ecs.ecs_service import ECS + + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_task_definitions_host_networking_mode_users.ecs_task_definitions_host_networking_mode_users.ecs_client", + new=ECS(mocked_aws_provider), ): from prowler.providers.aws.services.ecs.ecs_task_definitions_host_networking_mode_users.ecs_task_definitions_host_networking_mode_users import ( ecs_task_definitions_host_networking_mode_users, @@ -178,3 +218,7 @@ class Test_ecs_task_definitions_host_networking_mode_users: result[0].status_extended == f"ECS task definition {TASK_NAME} with revision {TASK_REVISION} has host network mode but no containers running as root or with no user specified." ) + assert result[0].resource_id == f"{TASK_NAME}:{TASK_REVISION}" + assert result[0].resource_arn == task_arn + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] diff --git a/tests/providers/aws/services/ecs/ecs_task_definitions_logging_enabled/ecs_task_definitions_logging_enabled_test.py b/tests/providers/aws/services/ecs/ecs_task_definitions_logging_enabled/ecs_task_definitions_logging_enabled_test.py index 037ac54bd5..d49da4eaa9 100644 --- a/tests/providers/aws/services/ecs/ecs_task_definitions_logging_enabled/ecs_task_definitions_logging_enabled_test.py +++ b/tests/providers/aws/services/ecs/ecs_task_definitions_logging_enabled/ecs_task_definitions_logging_enabled_test.py @@ -1,60 +1,27 @@ -from unittest import mock +from unittest.mock import patch -import botocore +from boto3 import client +from moto import mock_aws -from prowler.providers.aws.services.ecs.ecs_service import ( - ContainerDefinition, - TaskDefinition, -) -from tests.providers.aws.utils import ( - AWS_ACCOUNT_NUMBER, - AWS_REGION_US_EAST_1, - set_mocked_aws_provider, -) +from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider TASK_NAME = "test-task" TASK_REVISION = "1" CONTAINER_NAME = "test-container" -TASK_ARN = f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:task-definition/{TASK_NAME}:{TASK_REVISION}" - - -make_api_call = botocore.client.BaseClient._make_api_call - - -def mock_make_api_call(self, operation_name, kwarg): - if operation_name == "ListTaskDefinitions": - return { - "taskDefinitionArns": [ - "arn:aws:ecs:eu-west-1:123456789012:task-definition/test-task:1" - ] - } - if operation_name == "DescribeTaskDefinition": - return { - "taskDefinition": { - "containerDefinitions": [ - { - "name": "test-container", - "image": "test-image", - "environment": [ - {"name": "DB_PASSWORD", "value": "pass-12343"}, - ], - } - ], - "networkMode": "host", - "tags": [], - } - } - return make_api_call(self, operation_name, kwarg) class Test_ecs_task_definitions_logging_enabled: def test_no_task_definitions(self): - ecs_client = mock.MagicMock - ecs_client.task_definitions = {} + from prowler.providers.aws.services.ecs.ecs_service import ECS - with mock.patch( - "prowler.providers.aws.services.ecs.ecs_service.ECS", - ecs_client, + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_task_definitions_logging_enabled.ecs_task_definitions_logging_enabled.ecs_client", + new=ECS(mocked_aws_provider), ): from prowler.providers.aws.services.ecs.ecs_task_definitions_logging_enabled.ecs_task_definitions_logging_enabled import ( ecs_task_definitions_logging_enabled, @@ -64,56 +31,35 @@ class Test_ecs_task_definitions_logging_enabled: result = check.execute() assert len(result) == 0 - @mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) + @mock_aws def test_task_definition_no_logconfiguration(self): + ecs_client = client("ecs", region_name=AWS_REGION_US_EAST_1) + + task_arn = ecs_client.register_task_definition( + family=TASK_NAME, + containerDefinitions=[ + { + "name": CONTAINER_NAME, + "image": "ubuntu", + "memory": 128, + "readonlyRootFilesystem": True, + "privileged": False, + "user": "appuser", + "environment": [], + } + ], + )["taskDefinition"]["taskDefinitionArn"] from prowler.providers.aws.services.ecs.ecs_service import ECS - aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) - with mock.patch( + with patch( "prowler.providers.common.provider.Provider.get_global_provider", - return_value=aws_provider, - ), mock.patch( + return_value=mocked_aws_provider, + ), patch( "prowler.providers.aws.services.ecs.ecs_task_definitions_logging_enabled.ecs_task_definitions_logging_enabled.ecs_client", - new=ECS(aws_provider), - ): - from prowler.providers.aws.services.ecs.ecs_task_definitions_logging_enabled.ecs_task_definitions_logging_enabled import ( - ecs_task_definitions_logging_enabled, - ) - - check = ecs_task_definitions_logging_enabled() - result = check.execute() - assert len(result) == 1 - assert result[0].status == "FAIL" - assert ( - result[0].status_extended - == f"ECS task definition {TASK_NAME} with revision {TASK_REVISION} has containers running with no logging configuration: {CONTAINER_NAME}" - ) - - def test_task_definition_no_logdriver(self): - ecs_client = mock.MagicMock - ecs_client.task_definitions = {} - ecs_client.task_definitions[TASK_ARN] = TaskDefinition( - name=TASK_NAME, - arn=TASK_ARN, - revision=TASK_REVISION, - region=AWS_REGION_US_EAST_1, - network_mode="host", - container_definitions=[ - ContainerDefinition( - name=CONTAINER_NAME, - privileged=True, - user="root", - environment=[], - log_driver="", - ) - ], - ) - - with mock.patch( - "prowler.providers.aws.services.ecs.ecs_service.ECS", - ecs_client, + new=ECS(mocked_aws_provider), ): from prowler.providers.aws.services.ecs.ecs_task_definitions_logging_enabled.ecs_task_definitions_logging_enabled import ( ecs_task_definitions_logging_enabled, @@ -127,30 +73,41 @@ class Test_ecs_task_definitions_logging_enabled: result[0].status_extended == f"ECS task definition {TASK_NAME} with revision {TASK_REVISION} has containers running with no logging configuration: {CONTAINER_NAME}" ) + assert result[0].resource_id == f"{TASK_NAME}:{TASK_REVISION}" + assert result[0].resource_arn == task_arn + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] + @mock_aws def test_task_definition_privileged_container(self): - ecs_client = mock.MagicMock - ecs_client.task_definitions = {} - ecs_client.task_definitions[TASK_ARN] = TaskDefinition( - name=TASK_NAME, - arn=TASK_ARN, - revision=TASK_REVISION, - region=AWS_REGION_US_EAST_1, - network_mode="host", - container_definitions=[ - ContainerDefinition( - name=CONTAINER_NAME, - privileged=True, - user="root", - environment=[], - log_driver="awslogs", - ) - ], - ) + ecs_client = client("ecs", region_name=AWS_REGION_US_EAST_1) - with mock.patch( - "prowler.providers.aws.services.ecs.ecs_service.ECS", - ecs_client, + task_arn = ecs_client.register_task_definition( + family=TASK_NAME, + containerDefinitions=[ + { + "name": CONTAINER_NAME, + "image": "ubuntu", + "memory": 128, + "readonlyRootFilesystem": True, + "privileged": True, + "user": "root", + "environment": [], + "logConfiguration": {"logDriver": "awslogs"}, + } + ], + )["taskDefinition"]["taskDefinitionArn"] + + from prowler.providers.aws.services.ecs.ecs_service import ECS + + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_task_definitions_logging_enabled.ecs_task_definitions_logging_enabled.ecs_client", + new=ECS(mocked_aws_provider), ): from prowler.providers.aws.services.ecs.ecs_task_definitions_logging_enabled.ecs_task_definitions_logging_enabled import ( ecs_task_definitions_logging_enabled, @@ -164,3 +121,7 @@ class Test_ecs_task_definitions_logging_enabled: result[0].status_extended == f"ECS task definition {TASK_NAME} with revision {TASK_REVISION} containers have logging configured." ) + assert result[0].resource_id == f"{TASK_NAME}:{TASK_REVISION}" + assert result[0].resource_arn == task_arn + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] diff --git a/tests/providers/aws/services/ecs/ecs_task_definitions_no_environment_secrets/ecs_task_definitions_no_environment_secrets_test.py b/tests/providers/aws/services/ecs/ecs_task_definitions_no_environment_secrets/ecs_task_definitions_no_environment_secrets_test.py index 7defe40eac..8f8490a726 100644 --- a/tests/providers/aws/services/ecs/ecs_task_definitions_no_environment_secrets/ecs_task_definitions_no_environment_secrets_test.py +++ b/tests/providers/aws/services/ecs/ecs_task_definitions_no_environment_secrets/ecs_task_definitions_no_environment_secrets_test.py @@ -1,30 +1,31 @@ -from unittest import mock +from unittest.mock import patch -from prowler.providers.aws.services.ecs.ecs_service import ( - ContainerDefinition, - ContainerEnvVariable, - TaskDefinition, -) +from boto3 import client +from moto import mock_aws -AWS_REGION = "eu-west-1" -AWS_ACCOUNT_NUMBER = "123456789012" -task_name = "test-task" -task_revision = "1" -task_arn = f"arn:aws:ecs:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:task-definition/{task_name}:{task_revision}" -env_var_name_no_secrets = "host" -env_var_value_no_secrets = "localhost:1234" -env_var_name_with_secrets = "DB_PASSWORD" -env_var_value_with_secrets = "pass-12343" +from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider + +TASK_NAME = "test-task" +TASK_REVISION = "1" +CONTAINER_NAME = "test-container" +ENV_VAR_NAME_NO_SECRETS = "host" +ENV_VAR_VALUE_NO_SECRETS = "localhost:1234" +ENV_VAR_NAME_WITH_SECRETS = "DB_PASSWORD" +ENV_VAR_VALUE_WITH_SECRETS = "pass-12343" class Test_ecs_task_definitions_no_environment_secrets: def test_no_task_definitions(self): - ecs_client = mock.MagicMock - ecs_client.task_definitions = {} + from prowler.providers.aws.services.ecs.ecs_service import ECS - with mock.patch( - "prowler.providers.aws.services.ecs.ecs_service.ECS", - ecs_client, + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_task_definitions_no_environment_secrets.ecs_task_definitions_no_environment_secrets.ecs_client", + new=ECS(mocked_aws_provider), ): from prowler.providers.aws.services.ecs.ecs_task_definitions_no_environment_secrets.ecs_task_definitions_no_environment_secrets import ( ecs_task_definitions_no_environment_secrets, @@ -34,31 +35,40 @@ class Test_ecs_task_definitions_no_environment_secrets: result = check.execute() assert len(result) == 0 + @mock_aws def test_container_env_var_no_secrets(self): - ecs_client = mock.MagicMock - ecs_client.task_definitions = {} - ecs_client.task_definitions[task_arn] = TaskDefinition( - name=task_name, - arn=f"arn:aws:ecs:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:task-definition/{task_name}:{task_revision}", - revision="1", - region=AWS_REGION, - container_definitions=[ - ContainerDefinition( - name="container1", - privileged=False, - user="", - environment=[ - ContainerEnvVariable( - name=env_var_name_no_secrets, value=env_var_value_no_secrets - ) - ], - ) - ], - ) + ecs_client = client("ecs", region_name=AWS_REGION_US_EAST_1) - with mock.patch( - "prowler.providers.aws.services.ecs.ecs_service.ECS", - ecs_client, + task_arn = ecs_client.register_task_definition( + family=TASK_NAME, + containerDefinitions=[ + { + "name": CONTAINER_NAME, + "image": "ubuntu", + "memory": 128, + "readonlyRootFilesystem": True, + "privileged": False, + "user": "appuser", + "environment": [ + { + "name": ENV_VAR_NAME_NO_SECRETS, + "value": ENV_VAR_VALUE_NO_SECRETS, + } + ], + } + ], + )["taskDefinition"]["taskDefinitionArn"] + + from prowler.providers.aws.services.ecs.ecs_service import ECS + + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_task_definitions_no_environment_secrets.ecs_task_definitions_no_environment_secrets.ecs_client", + new=ECS(mocked_aws_provider), ): from prowler.providers.aws.services.ecs.ecs_task_definitions_no_environment_secrets.ecs_task_definitions_no_environment_secrets import ( ecs_task_definitions_no_environment_secrets, @@ -70,40 +80,47 @@ class Test_ecs_task_definitions_no_environment_secrets: assert result[0].status == "PASS" assert ( result[0].status_extended - == f"No secrets found in variables of ECS task definition {task_name} with revision {task_revision}." - ) - assert result[0].resource_id == f"{task_name}:1" - assert ( - result[0].resource_arn - == f"arn:aws:ecs:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:task-definition/{task_name}:{task_revision}" + == f"No secrets found in variables of ECS task definition {TASK_NAME} with revision {TASK_REVISION}." ) + assert result[0].resource_id == f"{TASK_NAME}:{TASK_REVISION}" + assert result[0].resource_arn == task_arn + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] + @mock_aws def test_container_env_var_with_secrets(self): - ecs_client = mock.MagicMock - ecs_client.task_definitions = {} - ecs_client.task_definitions[task_arn] = TaskDefinition( - name=task_name, - arn=f"arn:aws:ecs:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:task-definition/{task_name}:{task_revision}", - revision="1", - region=AWS_REGION, - container_definitions=[ - ContainerDefinition( - name="container1", - privileged=False, - user="", - environment=[ - ContainerEnvVariable( - name=env_var_name_with_secrets, - value=env_var_value_with_secrets, - ) - ], - ) - ], - ) + ecs_client = client("ecs", region_name=AWS_REGION_US_EAST_1) - with mock.patch( - "prowler.providers.aws.services.ecs.ecs_service.ECS", - ecs_client, + task_arn = ecs_client.register_task_definition( + family=TASK_NAME, + containerDefinitions=[ + { + "name": CONTAINER_NAME, + "image": "ubuntu", + "memory": 128, + "readonlyRootFilesystem": True, + "privileged": False, + "user": "appuser", + "environment": [ + { + "name": ENV_VAR_NAME_WITH_SECRETS, + "value": ENV_VAR_VALUE_WITH_SECRETS, + } + ], + } + ], + )["taskDefinition"]["taskDefinitionArn"] + + from prowler.providers.aws.services.ecs.ecs_service import ECS + + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_task_definitions_no_environment_secrets.ecs_task_definitions_no_environment_secrets.ecs_client", + new=ECS(mocked_aws_provider), ): from prowler.providers.aws.services.ecs.ecs_task_definitions_no_environment_secrets.ecs_task_definitions_no_environment_secrets import ( ecs_task_definitions_no_environment_secrets, @@ -115,10 +132,9 @@ class Test_ecs_task_definitions_no_environment_secrets: assert result[0].status == "FAIL" assert ( result[0].status_extended - == f"Potential secrets found in ECS task definition {task_name} with revision {task_revision}: Secrets in container container1 -> Secret Keyword on line 2." - ) - assert result[0].resource_id == f"{task_name}:1" - assert ( - result[0].resource_arn - == f"arn:aws:ecs:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:task-definition/{task_name}:{task_revision}" + == f"Potential secrets found in ECS task definition {TASK_NAME} with revision {TASK_REVISION}: Secrets in container test-container -> Secret Keyword on line 2." ) + assert result[0].resource_id == f"{TASK_NAME}:{TASK_REVISION}" + assert result[0].resource_arn == task_arn + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] diff --git a/tests/providers/aws/services/ecs/ecs_task_definitions_no_privileged_containers/ecs_task_definitions_no_privileged_containers_test.py b/tests/providers/aws/services/ecs/ecs_task_definitions_no_privileged_containers/ecs_task_definitions_no_privileged_containers_test.py index e167283ef8..a18f0c1205 100644 --- a/tests/providers/aws/services/ecs/ecs_task_definitions_no_privileged_containers/ecs_task_definitions_no_privileged_containers_test.py +++ b/tests/providers/aws/services/ecs/ecs_task_definitions_no_privileged_containers/ecs_task_definitions_no_privileged_containers_test.py @@ -1,26 +1,27 @@ -from unittest import mock +from unittest.mock import patch -from prowler.providers.aws.services.ecs.ecs_service import ( - ContainerDefinition, - ContainerEnvVariable, - TaskDefinition, -) -from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER, AWS_REGION_US_EAST_1 +from boto3 import client +from moto import mock_aws + +from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider TASK_NAME = "test-task" TASK_REVISION = "1" CONTAINER_NAME = "test-container" -TASK_ARN = f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:task-definition/{TASK_NAME}:{TASK_REVISION}" class Test_ecs_task_definitions_no_privileged_containers: def test_no_task_definitions(self): - ecs_client = mock.MagicMock - ecs_client.task_definitions = {} + from prowler.providers.aws.services.ecs.ecs_service import ECS - with mock.patch( - "prowler.providers.aws.services.ecs.ecs_service.ECS", - ecs_client, + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_task_definitions_no_privileged_containers.ecs_task_definitions_no_privileged_containers.ecs_client", + new=ECS(mocked_aws_provider), ): from prowler.providers.aws.services.ecs.ecs_task_definitions_no_privileged_containers.ecs_task_definitions_no_privileged_containers import ( ecs_task_definitions_no_privileged_containers, @@ -30,33 +31,35 @@ class Test_ecs_task_definitions_no_privileged_containers: result = check.execute() assert len(result) == 0 + @mock_aws def test_task_definition_no_priviled_container(self): - ecs_client = mock.MagicMock - ecs_client.task_definitions = {} - ecs_client.task_definitions[TASK_ARN] = TaskDefinition( - name=TASK_NAME, - arn=TASK_ARN, - revision=TASK_REVISION, - region=AWS_REGION_US_EAST_1, - network_mode="bridge", - container_definitions=[ - ContainerDefinition( - name=CONTAINER_NAME, - privileged=False, - user="", - environment=[ - ContainerEnvVariable( - name="env_var_name_no_secrets", - value="env_var_value_no_secrets", - ) - ], - ) - ], - ) + ecs_client = client("ecs", region_name=AWS_REGION_US_EAST_1) - with mock.patch( - "prowler.providers.aws.services.ecs.ecs_service.ECS", - ecs_client, + task_arn = ecs_client.register_task_definition( + family=TASK_NAME, + containerDefinitions=[ + { + "name": CONTAINER_NAME, + "image": "ubuntu", + "memory": 128, + "readonlyRootFilesystem": True, + "privileged": False, + "user": "appuser", + "environment": [], + } + ], + )["taskDefinition"]["taskDefinitionArn"] + + from prowler.providers.aws.services.ecs.ecs_service import ECS + + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_task_definitions_no_privileged_containers.ecs_task_definitions_no_privileged_containers.ecs_client", + new=ECS(mocked_aws_provider), ): from prowler.providers.aws.services.ecs.ecs_task_definitions_no_privileged_containers.ecs_task_definitions_no_privileged_containers import ( ecs_task_definitions_no_privileged_containers, @@ -70,29 +73,40 @@ class Test_ecs_task_definitions_no_privileged_containers: result[0].status_extended == f"ECS task definition {TASK_NAME} with revision {TASK_REVISION} does not have privileged containers." ) + assert result[0].resource_id == f"{TASK_NAME}:{TASK_REVISION}" + assert result[0].resource_arn == task_arn + assert result[0].resource_tags == [] + assert result[0].region == AWS_REGION_US_EAST_1 + @mock_aws def test_task_definition_privileged_container(self): - ecs_client = mock.MagicMock - ecs_client.task_definitions = {} - ecs_client.task_definitions[TASK_ARN] = TaskDefinition( - name=TASK_NAME, - arn=TASK_ARN, - revision=TASK_REVISION, - region=AWS_REGION_US_EAST_1, - network_mode="host", - container_definitions=[ - ContainerDefinition( - name=CONTAINER_NAME, - privileged=True, - user="root", - environment=[], - ) - ], - ) + ecs_client = client("ecs", region_name=AWS_REGION_US_EAST_1) - with mock.patch( - "prowler.providers.aws.services.ecs.ecs_service.ECS", - ecs_client, + task_arn = ecs_client.register_task_definition( + family=TASK_NAME, + containerDefinitions=[ + { + "name": CONTAINER_NAME, + "image": "ubuntu", + "memory": 128, + "readonlyRootFilesystem": True, + "privileged": True, + "user": "root", + "environment": [], + } + ], + )["taskDefinition"]["taskDefinitionArn"] + + from prowler.providers.aws.services.ecs.ecs_service import ECS + + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), patch( + "prowler.providers.aws.services.ecs.ecs_task_definitions_no_privileged_containers.ecs_task_definitions_no_privileged_containers.ecs_client", + new=ECS(mocked_aws_provider), ): from prowler.providers.aws.services.ecs.ecs_task_definitions_no_privileged_containers.ecs_task_definitions_no_privileged_containers import ( ecs_task_definitions_no_privileged_containers, @@ -106,3 +120,7 @@ class Test_ecs_task_definitions_no_privileged_containers: result[0].status_extended == f"ECS task definition {TASK_NAME} with revision {TASK_REVISION} has privileged containers: {CONTAINER_NAME}" ) + assert result[0].resource_id == f"{TASK_NAME}:{TASK_REVISION}" + assert result[0].resource_arn == task_arn + assert result[0].resource_tags == [] + assert result[0].region == AWS_REGION_US_EAST_1 diff --git a/tests/providers/aws/services/efs/efs_access_point_enforce_root_directory/efs_access_point_enforce_root_directory_test.py b/tests/providers/aws/services/efs/efs_access_point_enforce_root_directory/efs_access_point_enforce_root_directory_test.py new file mode 100644 index 0000000000..f7d1fe2cf9 --- /dev/null +++ b/tests/providers/aws/services/efs/efs_access_point_enforce_root_directory/efs_access_point_enforce_root_directory_test.py @@ -0,0 +1,138 @@ +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, +) + +CREATION_TOKEN = "fs-123" + + +class Test_efs_access_point_enforce_root_directory: + @mock_aws + def test_efs_no_file_system(self): + from prowler.providers.aws.services.efs.efs_service import EFS + + 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.efs.efs_access_point_enforce_root_directory.efs_access_point_enforce_root_directory.efs_client", + new=EFS(aws_provider), + ): + from prowler.providers.aws.services.efs.efs_access_point_enforce_root_directory.efs_access_point_enforce_root_directory import ( + efs_access_point_enforce_root_directory, + ) + + check = efs_access_point_enforce_root_directory() + result = check.execute() + assert len(result) == 0 + + @mock_aws + def test_efs_no_access_point(self): + efs_client = client("efs", region_name=AWS_REGION_US_EAST_1) + efs_client.create_file_system(CreationToken=CREATION_TOKEN) + from prowler.providers.aws.services.efs.efs_service import EFS + + 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.efs.efs_access_point_enforce_root_directory.efs_access_point_enforce_root_directory.efs_client", + new=EFS(aws_provider), + ): + from prowler.providers.aws.services.efs.efs_access_point_enforce_root_directory.efs_access_point_enforce_root_directory import ( + efs_access_point_enforce_root_directory, + ) + + check = efs_access_point_enforce_root_directory() + result = check.execute() + assert len(result) == 0 + + @mock_aws + def test_efs_access_point_default_root_directory(self): + efs_client = client("efs", region_name=AWS_REGION_US_EAST_1) + file_system = efs_client.create_file_system(CreationToken=CREATION_TOKEN) + + access_point = efs_client.create_access_point( + FileSystemId=file_system["FileSystemId"], + PosixUser={"Uid": 1000, "Gid": 1000}, + RootDirectory={"Path": "/"}, + ) + + from prowler.providers.aws.services.efs.efs_service import EFS + + 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.efs.efs_access_point_enforce_root_directory.efs_access_point_enforce_root_directory.efs_client", + new=EFS(aws_provider), + ): + from prowler.providers.aws.services.efs.efs_access_point_enforce_root_directory.efs_access_point_enforce_root_directory import ( + efs_access_point_enforce_root_directory, + ) + + check = efs_access_point_enforce_root_directory() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"EFS {file_system['FileSystemId']} has access points which allow access to the root directory: {access_point['AccessPointId']}." + ) + assert result[0].resource_id == file_system["FileSystemId"] + assert ( + result[0].resource_arn + == f"arn:aws:elasticfilesystem:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:file-system/{file_system['FileSystemId']}" + ) + + @mock_aws + def test_efs_access_point_enforced_root_directory(self): + efs_client = client("efs", region_name=AWS_REGION_US_EAST_1) + file_system = efs_client.create_file_system(CreationToken=CREATION_TOKEN) + + efs_client.create_access_point( + FileSystemId=file_system["FileSystemId"], + PosixUser={"Uid": 1000, "Gid": 1000}, + RootDirectory={"Path": "/notdefault"}, + ) + + from prowler.providers.aws.services.efs.efs_service import EFS + + 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.efs.efs_access_point_enforce_root_directory.efs_access_point_enforce_root_directory.efs_client", + new=EFS(aws_provider), + ): + from prowler.providers.aws.services.efs.efs_access_point_enforce_root_directory.efs_access_point_enforce_root_directory import ( + efs_access_point_enforce_root_directory, + ) + + check = efs_access_point_enforce_root_directory() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"EFS {file_system['FileSystemId']} does not have any access point allowing access to the root directory." + ) + assert result[0].resource_id == file_system["FileSystemId"] + assert ( + result[0].resource_arn + == f"arn:aws:elasticfilesystem:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:file-system/{file_system['FileSystemId']}" + ) diff --git a/tests/providers/aws/services/efs/efs_access_point_enforce_user_identity/efs_access_point_enforce_user_identity_test.py b/tests/providers/aws/services/efs/efs_access_point_enforce_user_identity/efs_access_point_enforce_user_identity_test.py new file mode 100644 index 0000000000..51d20e1668 --- /dev/null +++ b/tests/providers/aws/services/efs/efs_access_point_enforce_user_identity/efs_access_point_enforce_user_identity_test.py @@ -0,0 +1,137 @@ +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, +) + +CREATION_TOKEN = "fs-123" + + +class Test_efs_access_point_enforce_user_identity: + @mock_aws + def test_efs_no_file_system(self): + from prowler.providers.aws.services.efs.efs_service import EFS + + 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.efs.efs_access_point_enforce_user_identity.efs_access_point_enforce_user_identity.efs_client", + new=EFS(aws_provider), + ): + from prowler.providers.aws.services.efs.efs_access_point_enforce_user_identity.efs_access_point_enforce_user_identity import ( + efs_access_point_enforce_user_identity, + ) + + check = efs_access_point_enforce_user_identity() + result = check.execute() + assert len(result) == 0 + + @mock_aws + def test_efs_no_access_point(self): + efs_client = client("efs", region_name=AWS_REGION_US_EAST_1) + efs_client.create_file_system(CreationToken=CREATION_TOKEN) + from prowler.providers.aws.services.efs.efs_service import EFS + + 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.efs.efs_access_point_enforce_user_identity.efs_access_point_enforce_user_identity.efs_client", + new=EFS(aws_provider), + ): + from prowler.providers.aws.services.efs.efs_access_point_enforce_user_identity.efs_access_point_enforce_user_identity import ( + efs_access_point_enforce_user_identity, + ) + + check = efs_access_point_enforce_user_identity() + result = check.execute() + assert len(result) == 0 + + @mock_aws + def test_efs_access_point_no_posix_user(self): + efs_client = client("efs", region_name=AWS_REGION_US_EAST_1) + file_system = efs_client.create_file_system(CreationToken=CREATION_TOKEN) + + access_point = efs_client.create_access_point( + FileSystemId=file_system["FileSystemId"], + RootDirectory={"Path": "/"}, + ) + + from prowler.providers.aws.services.efs.efs_service import EFS + + 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.efs.efs_access_point_enforce_user_identity.efs_access_point_enforce_user_identity.efs_client", + new=EFS(aws_provider), + ): + from prowler.providers.aws.services.efs.efs_access_point_enforce_user_identity.efs_access_point_enforce_user_identity import ( + efs_access_point_enforce_user_identity, + ) + + check = efs_access_point_enforce_user_identity() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"EFS {file_system['FileSystemId']} has access points with no POSIX user: {access_point['AccessPointId']}." + ) + assert result[0].resource_id == file_system["FileSystemId"] + assert ( + result[0].resource_arn + == f"arn:aws:elasticfilesystem:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:file-system/{file_system['FileSystemId']}" + ) + + @mock_aws + def test_efs_access_point_defined_posix_user(self): + efs_client = client("efs", region_name=AWS_REGION_US_EAST_1) + file_system = efs_client.create_file_system(CreationToken=CREATION_TOKEN) + + efs_client.create_access_point( + FileSystemId=file_system["FileSystemId"], + PosixUser={"Uid": 1000, "Gid": 1000}, + RootDirectory={"Path": "/notdefault"}, + ) + + from prowler.providers.aws.services.efs.efs_service import EFS + + 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.efs.efs_access_point_enforce_user_identity.efs_access_point_enforce_user_identity.efs_client", + new=EFS(aws_provider), + ): + from prowler.providers.aws.services.efs.efs_access_point_enforce_user_identity.efs_access_point_enforce_user_identity import ( + efs_access_point_enforce_user_identity, + ) + + check = efs_access_point_enforce_user_identity() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"EFS {file_system['FileSystemId']} has all access points with defined POSIX user." + ) + assert result[0].resource_id == file_system["FileSystemId"] + assert ( + result[0].resource_arn + == f"arn:aws:elasticfilesystem:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:file-system/{file_system['FileSystemId']}" + ) diff --git a/tests/providers/aws/services/efs/efs_encryption_at_rest_enabled/efs_encryption_at_rest_enabled_test.py b/tests/providers/aws/services/efs/efs_encryption_at_rest_enabled/efs_encryption_at_rest_enabled_test.py index 9d0036a159..9670fceb7c 100644 --- a/tests/providers/aws/services/efs/efs_encryption_at_rest_enabled/efs_encryption_at_rest_enabled_test.py +++ b/tests/providers/aws/services/efs/efs_encryption_at_rest_enabled/efs_encryption_at_rest_enabled_test.py @@ -1,34 +1,37 @@ -from re import search from unittest import mock -from prowler.providers.aws.services.efs.efs_service import FileSystem +from boto3 import client +from moto import mock_aws -# Mock Test Region -AWS_REGION = "eu-west-1" -AWS_ACCOUNT_NUMBER = "123456789012" +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) -file_system_id = "fs-c7a0456e" - -backup_valid_policy_status = "ENABLED" +CREATION_TOKEN = "fs-123" class Test_efs_encryption_at_rest_enabled: + @mock_aws def test_efs_encryption_enabled(self): - efs_client = mock.MagicMock - efs_arn = f"arn:aws:elasticfilesystem:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:file-system/{file_system_id}" - efs_client.filesystems = [ - FileSystem( - id=file_system_id, - arn=efs_arn, - region=AWS_REGION, - policy=None, - backup_policy=backup_valid_policy_status, - encrypted=True, - ) - ] + efs_client = client("efs", region_name=AWS_REGION_US_EAST_1) + filesystem = efs_client.create_file_system( + CreationToken=CREATION_TOKEN, Encrypted=True + ) + + efs_arn = f"arn:aws:elasticfilesystem:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:file-system/{filesystem['FileSystemId']}" + + from prowler.providers.aws.services.efs.efs_service import EFS + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + with mock.patch( - "prowler.providers.aws.services.efs.efs_service.EFS", - efs_client, + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.efs.efs_encryption_at_rest_enabled.efs_encryption_at_rest_enabled.efs_client", + new=EFS(aws_provider), ): from prowler.providers.aws.services.efs.efs_encryption_at_rest_enabled.efs_encryption_at_rest_enabled import ( efs_encryption_at_rest_enabled, @@ -38,26 +41,33 @@ class Test_efs_encryption_at_rest_enabled: result = check.execute() assert len(result) == 1 assert result[0].status == "PASS" - assert search("has encryption at rest enabled", result[0].status_extended) - assert result[0].resource_id == file_system_id + assert result[0].region == AWS_REGION_US_EAST_1 + assert ( + result[0].status_extended + == f"EFS {filesystem['FileSystemId']} has encryption at rest enabled." + ) + assert result[0].resource_id == filesystem["FileSystemId"] assert result[0].resource_arn == efs_arn + @mock_aws def test_efs_encryption_disabled(self): - efs_client = mock.MagicMock - efs_arn = f"arn:aws:elasticfilesystem:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:file-system/{file_system_id}" - efs_client.filesystems = [ - FileSystem( - id=file_system_id, - arn=efs_arn, - region=AWS_REGION, - policy=None, - backup_policy=backup_valid_policy_status, - encrypted=False, - ) - ] + efs_client = client("efs", region_name=AWS_REGION_US_EAST_1) + filesystem = efs_client.create_file_system( + CreationToken=CREATION_TOKEN, Encrypted=False + ) + + efs_arn = f"arn:aws:elasticfilesystem:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:file-system/{filesystem['FileSystemId']}" + + from prowler.providers.aws.services.efs.efs_service import EFS + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + with mock.patch( - "prowler.providers.aws.services.efs.efs_service.EFS", - efs_client, + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.efs.efs_encryption_at_rest_enabled.efs_encryption_at_rest_enabled.efs_client", + new=EFS(aws_provider), ): from prowler.providers.aws.services.efs.efs_encryption_at_rest_enabled.efs_encryption_at_rest_enabled import ( efs_encryption_at_rest_enabled, @@ -67,8 +77,10 @@ class Test_efs_encryption_at_rest_enabled: result = check.execute() assert len(result) == 1 assert result[0].status == "FAIL" - assert search( - "does not have encryption at rest enabled", result[0].status_extended + assert result[0].region == AWS_REGION_US_EAST_1 + assert ( + result[0].status_extended + == f"EFS {filesystem['FileSystemId']} does not have encryption at rest enabled." ) - assert result[0].resource_id == file_system_id + assert result[0].resource_id == filesystem["FileSystemId"] assert result[0].resource_arn == efs_arn diff --git a/tests/providers/aws/services/efs/efs_have_backup_enabled/efs_have_backup_enabled_test.py b/tests/providers/aws/services/efs/efs_have_backup_enabled/efs_have_backup_enabled_test.py index 4c53c10010..3bdaf808ba 100644 --- a/tests/providers/aws/services/efs/efs_have_backup_enabled/efs_have_backup_enabled_test.py +++ b/tests/providers/aws/services/efs/efs_have_backup_enabled/efs_have_backup_enabled_test.py @@ -1,36 +1,36 @@ from re import search from unittest import mock -from prowler.providers.aws.services.efs.efs_service import FileSystem +from boto3 import client +from moto import mock_aws -# Mock Test Region -AWS_REGION = "eu-west-1" -AWS_ACCOUNT_NUMBER = "123456789012" +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) -file_system_id = "fs-c7a0456e" - -backup_valid_policy_status = "ENABLED" -backup_valid_invalid_policy_status_1 = "DISABLING" -backup_valid_invalid_policy_status_2 = "DISABLED" +CREATION_TOKEN = "fs-123" class Test_efs_have_backup_enabled: + @mock_aws def test_efs_valid_backup_policy(self): - efs_client = mock.MagicMock - efs_arn = f"arn:aws:elasticfilesystem:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:file-system/{file_system_id}" - efs_client.filesystems = [ - FileSystem( - id=file_system_id, - arn=efs_arn, - region=AWS_REGION, - policy=None, - backup_policy=backup_valid_policy_status, - encrypted=True, - ) - ] + efs_client = client("efs", region_name=AWS_REGION_US_EAST_1) + file_system = efs_client.create_file_system( + CreationToken=CREATION_TOKEN, Backup=True + ) + + from prowler.providers.aws.services.efs.efs_service import EFS + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + with mock.patch( - "prowler.providers.aws.services.efs.efs_service.EFS", - efs_client, + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.efs.efs_have_backup_enabled.efs_have_backup_enabled.efs_client", + new=EFS(aws_provider), ): from prowler.providers.aws.services.efs.efs_have_backup_enabled.efs_have_backup_enabled import ( efs_have_backup_enabled, @@ -41,25 +41,29 @@ class Test_efs_have_backup_enabled: assert len(result) == 1 assert result[0].status == "PASS" assert search("has backup enabled", result[0].status_extended) - assert result[0].resource_id == file_system_id - assert result[0].resource_arn == efs_arn + assert result[0].resource_id == file_system["FileSystemId"] + assert ( + result[0].resource_arn + == f"arn:aws:elasticfilesystem:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:file-system/{file_system['FileSystemId']}" + ) + @mock_aws def test_efs_invalid_policy_backup_1(self): - efs_client = mock.MagicMock - efs_arn = f"arn:aws:elasticfilesystem:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:file-system/{file_system_id}" - efs_client.filesystems = [ - FileSystem( - id=file_system_id, - arn=efs_arn, - region=AWS_REGION, - policy=None, - backup_policy=backup_valid_invalid_policy_status_1, - encrypted=True, - ) - ] + efs_client = client("efs", region_name=AWS_REGION_US_EAST_1) + file_system = efs_client.create_file_system( + CreationToken=CREATION_TOKEN, Backup=False + ) + + from prowler.providers.aws.services.efs.efs_service import EFS + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + with mock.patch( - "prowler.providers.aws.services.efs.efs_service.EFS", - efs_client, + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.efs.efs_have_backup_enabled.efs_have_backup_enabled.efs_client", + new=EFS(aws_provider), ): from prowler.providers.aws.services.efs.efs_have_backup_enabled.efs_have_backup_enabled import ( efs_have_backup_enabled, @@ -70,25 +74,29 @@ class Test_efs_have_backup_enabled: assert len(result) == 1 assert result[0].status == "FAIL" assert search("does not have backup enabled", result[0].status_extended) - assert result[0].resource_id == file_system_id - assert result[0].resource_arn == efs_arn + assert result[0].resource_id == file_system["FileSystemId"] + assert ( + result[0].resource_arn + == f"arn:aws:elasticfilesystem:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:file-system/{file_system['FileSystemId']}" + ) + @mock_aws def test_efs_invalid_policy_backup_2(self): - efs_client = mock.MagicMock - efs_arn = f"arn:aws:elasticfilesystem:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:file-system/{file_system_id}" - efs_client.filesystems = [ - FileSystem( - id=file_system_id, - arn=efs_arn, - region=AWS_REGION, - policy=None, - backup_policy=backup_valid_invalid_policy_status_2, - encrypted=True, - ) - ] + efs_client = client("efs", region_name=AWS_REGION_US_EAST_1) + file_system = efs_client.create_file_system( + CreationToken=CREATION_TOKEN, Backup=False + ) + + from prowler.providers.aws.services.efs.efs_service import EFS + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + with mock.patch( - "prowler.providers.aws.services.efs.efs_service.EFS", - efs_client, + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.efs.efs_have_backup_enabled.efs_have_backup_enabled.efs_client", + new=EFS(aws_provider), ): from prowler.providers.aws.services.efs.efs_have_backup_enabled.efs_have_backup_enabled import ( efs_have_backup_enabled, @@ -99,5 +107,8 @@ class Test_efs_have_backup_enabled: assert len(result) == 1 assert result[0].status == "FAIL" assert search("does not have backup enabled", result[0].status_extended) - assert result[0].resource_id == file_system_id - assert result[0].resource_arn == efs_arn + assert result[0].resource_id == file_system["FileSystemId"] + assert ( + result[0].resource_arn + == f"arn:aws:elasticfilesystem:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:file-system/{file_system['FileSystemId']}" + ) diff --git a/tests/providers/aws/services/efs/efs_mount_target_not_publicly_accessible/efs_mount_target_not_publicly_accessible_test.py b/tests/providers/aws/services/efs/efs_mount_target_not_publicly_accessible/efs_mount_target_not_publicly_accessible_test.py new file mode 100644 index 0000000000..5893b96a01 --- /dev/null +++ b/tests/providers/aws/services/efs/efs_mount_target_not_publicly_accessible/efs_mount_target_not_publicly_accessible_test.py @@ -0,0 +1,188 @@ +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, +) + +CREATION_TOKEN = "fs-123" + + +class Test_efs_mount_target_not_publicly_accessible: + @mock_aws + def test_efs_no_file_system(self): + from prowler.providers.aws.services.efs.efs_service import EFS + + 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.efs.efs_mount_target_not_publicly_accessible.efs_mount_target_not_publicly_accessible.efs_client", + new=EFS(aws_provider), + ): + from prowler.providers.aws.services.efs.efs_mount_target_not_publicly_accessible.efs_mount_target_not_publicly_accessible import ( + efs_mount_target_not_publicly_accessible, + ) + + check = efs_mount_target_not_publicly_accessible() + result = check.execute() + assert len(result) == 0 + + @mock_aws + def test_efs_no_mount_target(self): + efs_client = client("efs", region_name=AWS_REGION_US_EAST_1) + file_system = efs_client.create_file_system(CreationToken=CREATION_TOKEN) + + from prowler.providers.aws.services.efs.efs_service import EFS + + 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.efs.efs_mount_target_not_publicly_accessible.efs_mount_target_not_publicly_accessible.efs_client", + new=EFS(aws_provider), + ): + from prowler.providers.aws.services.efs.efs_mount_target_not_publicly_accessible.efs_mount_target_not_publicly_accessible import ( + efs_mount_target_not_publicly_accessible, + ) + + check = efs_mount_target_not_publicly_accessible() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"EFS {file_system['FileSystemId']} does not have any public mount targets." + ) + assert result[0].resource_id == file_system["FileSystemId"] + assert ( + result[0].resource_arn + == f"arn:aws:elasticfilesystem:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:file-system/{file_system['FileSystemId']}" + ) + + @mock_aws + def test_efs_mount_target_public_subnet(self): + efs_client = client("efs", region_name=AWS_REGION_US_EAST_1) + file_system = efs_client.create_file_system(CreationToken=CREATION_TOKEN) + + ec2_client = client("ec2", region_name=AWS_REGION_US_EAST_1) + vpc = ec2_client.create_vpc( + CidrBlock="172.28.7.0/24", InstanceTenancy="default" + ) + igw = ec2_client.create_internet_gateway() + ec2_client.attach_internet_gateway( + InternetGatewayId=igw["InternetGateway"]["InternetGatewayId"], + VpcId=vpc["Vpc"]["VpcId"], + ) + subnet_public = ec2_client.create_subnet( + VpcId=vpc["Vpc"]["VpcId"], + CidrBlock="172.28.7.192/26", + AvailabilityZone=f"{AWS_REGION_US_EAST_1}a", + ) + route_table = ec2_client.create_route_table(VpcId=vpc["Vpc"]["VpcId"]) + ec2_client.create_route( + RouteTableId=route_table["RouteTable"]["RouteTableId"], + DestinationCidrBlock="0.0.0.0/0", # Ruta predeterminada para todo el tráfico + GatewayId=igw["InternetGateway"]["InternetGatewayId"], + ) + ec2_client.associate_route_table( + RouteTableId=route_table["RouteTable"]["RouteTableId"], + SubnetId=subnet_public["Subnet"]["SubnetId"], + ) + mount_target = efs_client.create_mount_target( + FileSystemId=file_system["FileSystemId"], + SubnetId=subnet_public["Subnet"]["SubnetId"], + ) + + from prowler.providers.aws.services.efs.efs_service import EFS + from prowler.providers.aws.services.vpc.vpc_service import VPC + + 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.efs.efs_mount_target_not_publicly_accessible.efs_mount_target_not_publicly_accessible.efs_client", + new=EFS(aws_provider), + ), mock.patch( + "prowler.providers.aws.services.efs.efs_mount_target_not_publicly_accessible.efs_mount_target_not_publicly_accessible.vpc_client", + new=VPC(aws_provider), + ): + from prowler.providers.aws.services.efs.efs_mount_target_not_publicly_accessible.efs_mount_target_not_publicly_accessible import ( + efs_mount_target_not_publicly_accessible, + ) + + check = efs_mount_target_not_publicly_accessible() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"EFS {file_system['FileSystemId']} has public mount targets: {mount_target['MountTargetId']}" + ) + assert result[0].resource_id == file_system["FileSystemId"] + assert ( + result[0].resource_arn + == f"arn:aws:elasticfilesystem:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:file-system/{file_system['FileSystemId']}" + ) + + @mock_aws + def test_efs_mount_target_private_subnet(self): + efs_client = client("efs", region_name=AWS_REGION_US_EAST_1) + file_system = efs_client.create_file_system(CreationToken=CREATION_TOKEN) + + ec2_client = client("ec2", region_name=AWS_REGION_US_EAST_1) + vpc = ec2_client.create_vpc( + CidrBlock="172.28.7.0/24", InstanceTenancy="default" + ) + subnet_private = ec2_client.create_subnet( + VpcId=vpc["Vpc"]["VpcId"], + CidrBlock="172.28.7.192/26", + AvailabilityZone=f"{AWS_REGION_US_EAST_1}a", + ) + efs_client.create_mount_target( + FileSystemId=file_system["FileSystemId"], + SubnetId=subnet_private["Subnet"]["SubnetId"], + ) + + from prowler.providers.aws.services.efs.efs_service import EFS + from prowler.providers.aws.services.vpc.vpc_service import VPC + + 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.efs.efs_mount_target_not_publicly_accessible.efs_mount_target_not_publicly_accessible.efs_client", + new=EFS(aws_provider), + ), mock.patch( + "prowler.providers.aws.services.efs.efs_mount_target_not_publicly_accessible.efs_mount_target_not_publicly_accessible.vpc_client", + new=VPC(aws_provider), + ): + from prowler.providers.aws.services.efs.efs_mount_target_not_publicly_accessible.efs_mount_target_not_publicly_accessible import ( + efs_mount_target_not_publicly_accessible, + ) + + check = efs_mount_target_not_publicly_accessible() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"EFS {file_system['FileSystemId']} does not have any public mount targets." + ) + assert result[0].resource_id == file_system["FileSystemId"] + assert ( + result[0].resource_arn + == f"arn:aws:elasticfilesystem:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:file-system/{file_system['FileSystemId']}" + ) diff --git a/tests/providers/aws/services/efs/efs_not_publicly_accessible/efs_not_publicly_accessible_test.py b/tests/providers/aws/services/efs/efs_not_publicly_accessible/efs_not_publicly_accessible_test.py index 2ea9111cde..14adf7ac22 100644 --- a/tests/providers/aws/services/efs/efs_not_publicly_accessible/efs_not_publicly_accessible_test.py +++ b/tests/providers/aws/services/efs/efs_not_publicly_accessible/efs_not_publicly_accessible_test.py @@ -1,96 +1,137 @@ +import json from unittest import mock -from prowler.providers.aws.services.efs.efs_service import FileSystem +import botocore +from boto3 import client +from moto import mock_aws -# Mock Test Region -AWS_REGION = "eu-west-1" -AWS_ACCOUNT_NUMBER = "123456789012" +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) -file_system_id = "fs-c7a0456e" +CREATION_TOKEN = "fs-123" -filesystem_policy = { - "Id": "1", - "Statement": [ - { - "Effect": "Allow", - "Action": ["elasticfilesystem:ClientMount"], - "Principal": {"AWS": f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root"}, - } - ], -} +FILE_SYSTEM_POLICY = json.dumps( + { + "Id": "1", + "Statement": [ + { + "Effect": "Allow", + "Action": ["elasticfilesystem:ClientMount"], + "Principal": {"AWS": "arn:aws:iam::123456789012:root"}, + } + ], + } +) -filesystem_invalid_policy = { - "Id": "1", - "Statement": [ - { - "Effect": "Allow", - "Action": ["elasticfilesystem:ClientMount"], - "Principal": {"AWS": "*"}, - } - ], -} +FILE_SYSTEM_INVALID_POLICY = json.dumps( + { + "Id": "1", + "Statement": [ + { + "Effect": "Allow", + "Action": ["elasticfilesystem:ClientMount"], + "Principal": {"AWS": "*"}, + } + ], + } +) # https://docs.aws.amazon.com/efs/latest/ug/access-control-block-public-access.html#what-is-a-public-policy -filesystem_policy_with_source_arn_condition = { - "Version": "2012-10-17", - "Id": "efs-policy-wizard-15ad9567-2546-4bbb-8168-5541b6fc0e55", - "Statement": [ - { - "Sid": "efs-statement-14a7191c-9401-40e7-a388-6af6cfb7dd9c", - "Effect": "Allow", - "Principal": {"AWS": "*"}, - "Action": [ - "elasticfilesystem:ClientMount", - "elasticfilesystem:ClientWrite", - "elasticfilesystem:ClientRootAccess", - ], - "Condition": { - "ArnEquals": { - "aws:SourceArn": f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root" - } - }, - } - ], -} +FILE_SYSTEM_POLICY_WITH_SOURCE_ARN_CONDITION = json.dumps( + { + "Version": "2012-10-17", + "Id": "efs-policy-wizard-15ad9567-2546-4bbb-8168-5541b6fc0e55", + "Statement": [ + { + "Sid": "efs-statement-14a7191c-9401-40e7-a388-6af6cfb7dd9c", + "Effect": "Allow", + "Principal": {"AWS": "*"}, + "Action": [ + "elasticfilesystem:ClientMount", + "elasticfilesystem:ClientWrite", + "elasticfilesystem:ClientRootAccess", + ], + "Condition": { + "ArnEquals": { + "aws:SourceArn": f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root" + } + }, + } + ], + } +) # https://docs.aws.amazon.com/efs/latest/ug/access-control-block-public-access.html#what-is-a-public-policy -filesystem_policy_with_mount_target_condition = { - "Version": "2012-10-17", - "Id": "efs-policy-wizard-15ad9567-2546-4bbb-8168-5541b6fc0e55", - "Statement": [ - { - "Sid": "efs-statement-14a7191c-9401-40e7-a388-6af6cfb7dd9c", - "Effect": "Allow", - "Principal": {"AWS": "*"}, - "Action": [ - "elasticfilesystem:ClientMount", - "elasticfilesystem:ClientWrite", - "elasticfilesystem:ClientRootAccess", - ], - "Condition": {"Bool": {"elasticfilesystem:AccessedViaMountTarget": "true"}}, - } - ], -} +FILE_SYSTEM_POLICY_WITH_MOUNT_TARGET_CONDITION = json.dumps( + { + "Version": "2012-10-17", + "Id": "efs-policy-wizard-15ad9567-2546-4bbb-8168-5541b6fc0e55", + "Statement": [ + { + "Sid": "efs-statement-14a7191c-9401-40e7-a388-6af6cfb7dd9c", + "Effect": "Allow", + "Principal": {"AWS": "*"}, + "Action": [ + "elasticfilesystem:ClientMount", + "elasticfilesystem:ClientWrite", + "elasticfilesystem:ClientRootAccess", + ], + "Condition": { + "Bool": {"elasticfilesystem:AccessedViaMountTarget": "true"} + }, + } + ], + } +) + +make_api_call = botocore.client.BaseClient._make_api_call + + +def mock_make_api_call(self, operation_name, kwarg): + if operation_name == "DescribeFileSystemPolicy": + return {"Policy": FILE_SYSTEM_POLICY} + + return make_api_call(self, operation_name, kwarg) + + +def mock_make_api_call_v2(self, operation_name, kwarg): + if operation_name == "DescribeFileSystemPolicy": + return {"Policy": FILE_SYSTEM_POLICY_WITH_MOUNT_TARGET_CONDITION} + return make_api_call(self, operation_name, kwarg) + + +def mock_make_api_call_v3(self, operation_name, kwarg): + if operation_name == "DescribeFileSystemPolicy": + return {"Policy": FILE_SYSTEM_POLICY_WITH_SOURCE_ARN_CONDITION} + return make_api_call(self, operation_name, kwarg) + + +def mock_make_api_call_v4(self, operation_name, kwarg): + if operation_name == "DescribeFileSystemPolicy": + return {"Policy": FILE_SYSTEM_INVALID_POLICY} + return make_api_call(self, operation_name, kwarg) class Test_efs_not_publicly_accessible: + @mock_aws + @mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) def test_efs_valid_policy(self): - efs_client = mock.MagicMock - efs_arn = f"arn:aws:elasticfilesystem:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:file-system/{file_system_id}" - efs_client.filesystems = [ - FileSystem( - id=file_system_id, - arn=efs_arn, - region=AWS_REGION, - policy=filesystem_policy, - backup_policy=None, - encrypted=True, - ) - ] + efs_client = client("efs", region_name=AWS_REGION_US_EAST_1) + file_system = efs_client.create_file_system(CreationToken=CREATION_TOKEN) + from prowler.providers.aws.services.efs.efs_service import EFS + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + with mock.patch( - "prowler.providers.aws.services.efs.efs_service.EFS", - efs_client, + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.efs.efs_not_publicly_accessible.efs_not_publicly_accessible.efs_client", + new=EFS(aws_provider), ): from prowler.providers.aws.services.efs.efs_not_publicly_accessible.efs_not_publicly_accessible import ( efs_not_publicly_accessible, @@ -102,29 +143,32 @@ class Test_efs_not_publicly_accessible: assert result[0].status == "PASS" assert ( result[0].status_extended - == f"EFS {file_system_id} has a policy which does not allow access to any client within the VPC." + == f"EFS {file_system['FileSystemId']} has a policy which does not allow access to any client within the VPC." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == file_system["FileSystemId"] + assert ( + result[0].resource_arn + == f"arn:aws:elasticfilesystem:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:file-system/{file_system['FileSystemId']}" ) - assert result[0].resource_id == file_system_id - assert result[0].resource_arn == efs_arn - assert result[0].region == AWS_REGION assert result[0].resource_tags == [] + @mock_aws + @mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call_v2) def test_efs_valid_policy_with_mount_target_condition(self): - efs_client = mock.MagicMock - efs_arn = f"arn:aws:elasticfilesystem:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:file-system/{file_system_id}" - efs_client.filesystems = [ - FileSystem( - id=file_system_id, - arn=efs_arn, - region=AWS_REGION, - policy=filesystem_policy_with_mount_target_condition, - backup_policy=None, - encrypted=True, - ) - ] + efs_client = client("efs", region_name=AWS_REGION_US_EAST_1) + file_system = efs_client.create_file_system(CreationToken=CREATION_TOKEN) + + from prowler.providers.aws.services.efs.efs_service import EFS + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + with mock.patch( - "prowler.providers.aws.services.efs.efs_service.EFS", - efs_client, + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.efs.efs_not_publicly_accessible.efs_not_publicly_accessible.efs_client", + new=EFS(aws_provider), ): from prowler.providers.aws.services.efs.efs_not_publicly_accessible.efs_not_publicly_accessible import ( efs_not_publicly_accessible, @@ -136,29 +180,32 @@ class Test_efs_not_publicly_accessible: assert result[0].status == "PASS" assert ( result[0].status_extended - == f"EFS {file_system_id} has a policy which does not allow access to any client within the VPC." + == f"EFS {file_system['FileSystemId']} has a policy which does not allow access to any client within the VPC." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == file_system["FileSystemId"] + assert ( + result[0].resource_arn + == f"arn:aws:elasticfilesystem:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:file-system/{file_system['FileSystemId']}" ) - assert result[0].resource_id == file_system_id - assert result[0].resource_arn == efs_arn - assert result[0].region == AWS_REGION assert result[0].resource_tags == [] + @mock_aws + @mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call_v3) def test_efs_valid_policy_with_source_arn_condition(self): - efs_client = mock.MagicMock - efs_arn = f"arn:aws:elasticfilesystem:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:file-system/{file_system_id}" - efs_client.filesystems = [ - FileSystem( - id=file_system_id, - arn=efs_arn, - region=AWS_REGION, - policy=filesystem_policy_with_source_arn_condition, - backup_policy=None, - encrypted=True, - ) - ] + efs_client = client("efs", region_name=AWS_REGION_US_EAST_1) + file_system = efs_client.create_file_system(CreationToken=CREATION_TOKEN) + + from prowler.providers.aws.services.efs.efs_service import EFS + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + with mock.patch( - "prowler.providers.aws.services.efs.efs_service.EFS", - efs_client, + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.efs.efs_not_publicly_accessible.efs_not_publicly_accessible.efs_client", + new=EFS(aws_provider), ): from prowler.providers.aws.services.efs.efs_not_publicly_accessible.efs_not_publicly_accessible import ( efs_not_publicly_accessible, @@ -170,30 +217,32 @@ class Test_efs_not_publicly_accessible: assert result[0].status == "PASS" assert ( result[0].status_extended - == f"EFS {file_system_id} has a policy which does not allow access to any client within the VPC." + == f"EFS {file_system['FileSystemId']} has a policy which does not allow access to any client within the VPC." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == file_system["FileSystemId"] + assert ( + result[0].resource_arn + == f"arn:aws:elasticfilesystem:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:file-system/{file_system['FileSystemId']}" ) - assert result[0].resource_id == file_system_id - assert result[0].resource_arn == efs_arn - assert result[0].region == AWS_REGION assert result[0].resource_tags == [] + @mock_aws + @mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call_v4) def test_efs_invalid_policy(self): - efs_client = mock.MagicMock - efs_arn = f"arn:aws:elasticfilesystem:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:file-system/{file_system_id}" + efs_client = client("efs", region_name=AWS_REGION_US_EAST_1) + file_system = efs_client.create_file_system(CreationToken=CREATION_TOKEN) + + from prowler.providers.aws.services.efs.efs_service import EFS + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) - efs_client.filesystems = [ - FileSystem( - id=file_system_id, - arn=efs_arn, - region=AWS_REGION, - policy=filesystem_invalid_policy, - backup_policy=None, - encrypted=True, - ) - ] with mock.patch( - "prowler.providers.aws.services.efs.efs_service.EFS", - efs_client, + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.efs.efs_not_publicly_accessible.efs_not_publicly_accessible.efs_client", + new=EFS(aws_provider), ): from prowler.providers.aws.services.efs.efs_not_publicly_accessible.efs_not_publicly_accessible import ( efs_not_publicly_accessible, @@ -205,29 +254,31 @@ class Test_efs_not_publicly_accessible: assert result[0].status == "FAIL" assert ( result[0].status_extended - == f"EFS {file_system_id} has a policy which allows access to any client within the VPC." + == f"EFS {file_system['FileSystemId']} has a policy which allows access to any client within the VPC." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == file_system["FileSystemId"] + assert ( + result[0].resource_arn + == f"arn:aws:elasticfilesystem:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:file-system/{file_system['FileSystemId']}" ) - assert result[0].resource_id == file_system_id - assert result[0].resource_arn == efs_arn - assert result[0].region == AWS_REGION assert result[0].resource_tags == [] + @mock_aws def test_efs_no_policy(self): - efs_client = mock.MagicMock - efs_arn = f"arn:aws:elasticfilesystem:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:file-system/{file_system_id}" - efs_client.filesystems = [ - FileSystem( - id=file_system_id, - arn=efs_arn, - region=AWS_REGION, - policy=None, - backup_policy=None, - encrypted=True, - ) - ] + efs_client = client("efs", region_name=AWS_REGION_US_EAST_1) + file_system = efs_client.create_file_system(CreationToken=CREATION_TOKEN) + + from prowler.providers.aws.services.efs.efs_service import EFS + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + with mock.patch( - "prowler.providers.aws.services.efs.efs_service.EFS", - efs_client, + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.efs.efs_not_publicly_accessible.efs_not_publicly_accessible.efs_client", + new=EFS(aws_provider), ): from prowler.providers.aws.services.efs.efs_not_publicly_accessible.efs_not_publicly_accessible import ( efs_not_publicly_accessible, @@ -239,9 +290,12 @@ class Test_efs_not_publicly_accessible: assert result[0].status == "FAIL" assert ( result[0].status_extended - == f"EFS {file_system_id} doesn't have any policy which means it grants full access to any client within the VPC." + == f"EFS {file_system['FileSystemId']} doesn't have any policy which means it grants full access to any client within the VPC." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == file_system["FileSystemId"] + assert ( + result[0].resource_arn + == f"arn:aws:elasticfilesystem:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:file-system/{file_system['FileSystemId']}" ) - assert result[0].resource_id == file_system_id - assert result[0].resource_arn == efs_arn - assert result[0].region == AWS_REGION assert result[0].resource_tags == [] diff --git a/tests/providers/aws/services/efs/efs_service_test.py b/tests/providers/aws/services/efs/efs_service_test.py index ac0859e006..0bd6fd5517 100644 --- a/tests/providers/aws/services/efs/efs_service_test.py +++ b/tests/providers/aws/services/efs/efs_service_test.py @@ -2,8 +2,6 @@ import json from unittest.mock import patch import botocore -from boto3 import client -from moto import mock_aws from prowler.providers.aws.services.efs.efs_service import EFS from tests.providers.aws.utils import ( @@ -15,13 +13,11 @@ from tests.providers.aws.utils import ( # Mocking Access Analyzer Calls make_api_call = botocore.client.BaseClient._make_api_call -file_system_id = "fs-c7a0456e" +FILE_SYSTEM_ID = "fs-c7a0456e" -creation_token = "console-d215fa78-1f83-4651-b026-facafd8a7da7" +CREATION_TOKEN = "console-d215fa78-1f83-4651-b026-facafd8a7da7" -backup_policy_status = "ENABLED" - -filesystem_policy = { +FILESYSTEM_POLICY = { "Id": "1", "Statement": [ { @@ -34,10 +30,47 @@ filesystem_policy = { def mock_make_api_call(self, operation_name, kwarg): + if operation_name == "DescribeFileSystems": + return { + "FileSystems": [ + { + "FileSystemId": FILE_SYSTEM_ID, + "Encrypted": True, + "Tags": [{"Key": "test", "Value": "test"}], + "BackupPolicy": {"Status": "ENABLED"}, + "Policy": json.dumps(FILESYSTEM_POLICY), + } + ] + } + if operation_name == "DescribeMountTargets": + return { + "MountTargets": [ + { + "MountTargetId": "fsmt-123", + "FileSystemId": FILE_SYSTEM_ID, + "SubnetId": "subnet-123", + "LifeCycleState": "available", + "OwnerId": AWS_ACCOUNT_NUMBER, + "VpcId": "vpc-123", + } + ] + } + if operation_name == "DescribeAccessPoints": + return { + "AccessPoints": [ + { + "AccessPointId": "fsap-123", + "AccessPointArn": f"arn:aws:elasticfilesystem:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:access-point/{FILE_SYSTEM_ID}/fsap-123", + "FileSystemId": FILE_SYSTEM_ID, + "RootDirectory": {"Path": "/"}, + "PosixUser": {"Uid": 1000, "Gid": 1000}, + } + ] + } if operation_name == "DescribeFileSystemPolicy": - return {"FileSystemId": file_system_id, "Policy": json.dumps(filesystem_policy)} + return {"FileSystemId": FILE_SYSTEM_ID, "Policy": json.dumps(FILESYSTEM_POLICY)} if operation_name == "DescribeBackupPolicy": - return {"BackupPolicy": {"Status": backup_policy_status}} + return {"BackupPolicy": {"Status": "ENABLED"}} return make_api_call(self, operation_name, kwarg) @@ -66,35 +99,57 @@ class Test_EFS: access_analyzer = EFS(set_mocked_aws_provider()) assert access_analyzer.service == "efs" - @mock_aws + @patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) # Test EFS describe file systems def test_describe_file_systems(self): - efs_client = client("efs", AWS_REGION_EU_WEST_1) - efs = efs_client.create_file_system( - CreationToken=creation_token, - Encrypted=True, - Tags=[ - {"Key": "test", "Value": "test"}, - ], - ) - filesystem = EFS(set_mocked_aws_provider()) - assert len(filesystem.filesystems) == 1 - assert filesystem.filesystems[0].id == efs["FileSystemId"] - assert filesystem.filesystems[0].encrypted == efs["Encrypted"] - assert filesystem.filesystems[0].tags == [ + aws_provider = set_mocked_aws_provider() + efs = EFS(aws_provider) + efs_arn = f"arn:aws:elasticfilesystem:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:file-system/{FILE_SYSTEM_ID}" + assert len(efs.filesystems) == 1 + assert efs.filesystems[efs_arn].id == FILE_SYSTEM_ID + assert efs.filesystems[efs_arn].encrypted + assert efs.filesystems[efs_arn].tags == [ {"Key": "test", "Value": "test"}, ] - @mock_aws - # Test EFS describe file systems + @patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) + # Test EFS describe file systems policies def test_describe_file_system_policies(self): - efs_client = client("efs", AWS_REGION_EU_WEST_1) - efs = efs_client.create_file_system( - CreationToken=creation_token, Encrypted=True + aws_provider = set_mocked_aws_provider() + efs = EFS(aws_provider) + efs_arn = f"arn:aws:elasticfilesystem:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:file-system/{FILE_SYSTEM_ID}" + assert len(efs.filesystems) == 1 + assert efs.filesystems[efs_arn].id == FILE_SYSTEM_ID + assert efs.filesystems[efs_arn].encrypted + assert efs.filesystems[efs_arn].backup_policy == "ENABLED" + assert efs.filesystems[efs_arn].policy == FILESYSTEM_POLICY + + @patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) + # Test EFS describe mount targets + def test_describe_mount_targets(self): + aws_provider = set_mocked_aws_provider() + efs = EFS(aws_provider) + assert len(efs.filesystems) == 1 + efs_arn = f"arn:aws:elasticfilesystem:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:file-system/{FILE_SYSTEM_ID}" + assert ( + efs.filesystems[efs_arn].mount_targets[0].file_system_id == FILE_SYSTEM_ID ) - filesystem = EFS(set_mocked_aws_provider()) - assert len(filesystem.filesystems) == 1 - assert filesystem.filesystems[0].id == efs["FileSystemId"] - assert filesystem.filesystems[0].encrypted == efs["Encrypted"] - assert filesystem.filesystems[0].backup_policy == backup_policy_status - assert filesystem.filesystems[0].policy == filesystem_policy + assert efs.filesystems[efs_arn].mount_targets[0].id == "fsmt-123" + assert efs.filesystems[efs_arn].mount_targets[0].subnet_id == "subnet-123" + + @patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) + # Test EFS describe access points + def test_describe_access_points(self): + aws_provider = set_mocked_aws_provider() + efs = EFS(aws_provider) + assert len(efs.filesystems) == 1 + efs_arn = f"arn:aws:elasticfilesystem:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:file-system/{FILE_SYSTEM_ID}" + assert ( + efs.filesystems[efs_arn].access_points[0].file_system_id == FILE_SYSTEM_ID + ) + assert efs.filesystems[efs_arn].access_points[0].id == "fsap-123" + assert efs.filesystems[efs_arn].access_points[0].root_directory_path == "/" + assert efs.filesystems[efs_arn].access_points[0].posix_user == { + "Uid": 1000, + "Gid": 1000, + } diff --git a/tests/providers/aws/services/elasticbeanstalk/elasticbeanstalk_service_test.py b/tests/providers/aws/services/elasticbeanstalk/elasticbeanstalk_service_test.py new file mode 100644 index 0000000000..e435bfec49 --- /dev/null +++ b/tests/providers/aws/services/elasticbeanstalk/elasticbeanstalk_service_test.py @@ -0,0 +1,165 @@ +from unittest.mock import patch + +import botocore +from boto3 import client +from moto import mock_aws + +from prowler.providers.aws.services.elasticbeanstalk.elasticbeanstalk_service import ( + ElasticBeanstalk, +) +from tests.providers.aws.utils import AWS_REGION_EU_WEST_1, set_mocked_aws_provider + +# Mocking Access Analyzer Calls +make_api_call = botocore.client.BaseClient._make_api_call + + +def mock_make_api_call(self, operation_name, kwarg): + if operation_name == "DescribeConfigurationSettings": + return { + "ConfigurationSettings": [ + { + "OptionSettings": [ + { + "Namespace": "aws:elasticbeanstalk:healthreporting:system", + "OptionName": "SystemType", + "Value": "enhanced", + }, + { + "Namespace": "aws:elasticbeanstalk:managedactions", + "OptionName": "ManagedActionsEnabled", + "Value": "true", + }, + { + "Namespace": "aws:elasticbeanstalk:cloudwatch:logs", + "OptionName": "StreamLogs", + "Value": "true", + }, + ], + } + ] + } + + return make_api_call(self, operation_name, kwarg) + + +# Mock generate_regional_clients() +def mock_generate_regional_clients(provider, service): + regional_client = provider._session.current_session.client( + service, region_name=AWS_REGION_EU_WEST_1 + ) + regional_client.region = AWS_REGION_EU_WEST_1 + return {AWS_REGION_EU_WEST_1: regional_client} + + +@patch( + "prowler.providers.aws.aws_provider.AwsProvider.generate_regional_clients", + new=mock_generate_regional_clients, +) +@patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) +class Test_ElasticBeanstalk_Service: + # Test ElasticBeanstalk Client + @mock_aws + def test_get_client(self): + elasticbeanstalk = ElasticBeanstalk(set_mocked_aws_provider()) + assert ( + elasticbeanstalk.regional_clients[AWS_REGION_EU_WEST_1].__class__.__name__ + == "ElasticBeanstalk" + ) + + # Test ElasticBeanstalk Session + @mock_aws + def test__get_session__(self): + elasticbeanstalk = ElasticBeanstalk(set_mocked_aws_provider()) + assert elasticbeanstalk.session.__class__.__name__ == "Session" + + # Test ElasticBeanstalk Service + @mock_aws + def test__get_service__(self): + elasticbeanstalk = ElasticBeanstalk(set_mocked_aws_provider()) + assert elasticbeanstalk.service == "elasticbeanstalk" + + # Test _describe_environments + @mock_aws + def test_describe_environments(self): + # Create ElasticBeanstalk app and env + elasticbeanstalk_client = client( + "elasticbeanstalk", region_name=AWS_REGION_EU_WEST_1 + ) + elasticbeanstalk_client.create_application(ApplicationName="test-app") + environment = elasticbeanstalk_client.create_environment( + ApplicationName="test-app", + EnvironmentName="test-env", + ) + # ElasticBeanstalk Class + elasticbeanstalk = ElasticBeanstalk(set_mocked_aws_provider()) + + assert len(elasticbeanstalk.environments) == 1 + assert ( + elasticbeanstalk.environments[environment["EnvironmentArn"]].id + == environment["EnvironmentId"] + ) + assert ( + elasticbeanstalk.environments[environment["EnvironmentArn"]].name + == "test-env" + ) + assert ( + elasticbeanstalk.environments[environment["EnvironmentArn"]].region + == AWS_REGION_EU_WEST_1 + ) + assert ( + elasticbeanstalk.environments[ + environment["EnvironmentArn"] + ].application_name + == "test-app" + ) + + # Test _describe_configuration_settings + @mock_aws + def test_describe_configuration_settings(self): + # Create ElasticBeanstalk app and env + elasticbeanstalk_client = client( + "elasticbeanstalk", region_name=AWS_REGION_EU_WEST_1 + ) + elasticbeanstalk_client.create_application(ApplicationName="test-app") + environment = elasticbeanstalk_client.create_environment( + ApplicationName="test-app", + EnvironmentName="test-env", + ) + # ElasticBeanstalk Class + elasticbeanstalk = ElasticBeanstalk(set_mocked_aws_provider()) + assert ( + elasticbeanstalk.environments[ + environment["EnvironmentArn"] + ].health_reporting + == "enhanced" + ) + assert ( + elasticbeanstalk.environments[ + environment["EnvironmentArn"] + ].managed_platform_updates + == "true" + ) + assert ( + elasticbeanstalk.environments[ + environment["EnvironmentArn"] + ].cloudwatch_stream_logs + == "true" + ) + + @mock_aws + def test_list_tags_for_resource(self): + # Create ElasticBeanstalk app and env + elasticbeanstalk_client = client( + "elasticbeanstalk", region_name=AWS_REGION_EU_WEST_1 + ) + elasticbeanstalk_client.create_application(ApplicationName="test-app") + environment = elasticbeanstalk_client.create_environment( + ApplicationName="test-app", + EnvironmentName="test-env", + Tags=[{"Key": "test-key", "Value": "test-value"}], + ) + # ElasticBeanstalk Class + elasticbeanstalk = ElasticBeanstalk(set_mocked_aws_provider()) + assert elasticbeanstalk.environments[environment["EnvironmentArn"]].tags == [ + {"Key": "test-key", "Value": "test-value"} + ] diff --git a/tests/providers/aws/services/elbv2/elbv2_waf_acl_attached/elbv2_waf_acl_attached_test.py b/tests/providers/aws/services/elbv2/elbv2_waf_acl_attached/elbv2_waf_acl_attached_test.py index 0b4ed286ce..4b1f91f566 100644 --- a/tests/providers/aws/services/elbv2/elbv2_waf_acl_attached/elbv2_waf_acl_attached_test.py +++ b/tests/providers/aws/services/elbv2/elbv2_waf_acl_attached/elbv2_waf_acl_attached_test.py @@ -226,7 +226,7 @@ class Test_elbv2_waf_acl_attached: elbv2_waf_acl_attached, ) - service_client.web_acls[0].albs.append(lb["LoadBalancerArn"]) + service_client.web_acls[waf["ARN"]].albs.append(lb["LoadBalancerArn"]) check = elbv2_waf_acl_attached() result = check.execute() diff --git a/tests/providers/aws/services/glue/glue_data_catalogs_connection_passwords_encryption_enabled/glue_data_catalogs_connection_passwords_encryption_enabled_test.py b/tests/providers/aws/services/glue/glue_data_catalogs_connection_passwords_encryption_enabled/glue_data_catalogs_connection_passwords_encryption_enabled_test.py index 631b0a6bb0..93042bcf4f 100644 --- a/tests/providers/aws/services/glue/glue_data_catalogs_connection_passwords_encryption_enabled/glue_data_catalogs_connection_passwords_encryption_enabled_test.py +++ b/tests/providers/aws/services/glue/glue_data_catalogs_connection_passwords_encryption_enabled/glue_data_catalogs_connection_passwords_encryption_enabled_test.py @@ -1,4 +1,4 @@ -from unittest import mock +from unittest.mock import MagicMock, patch from prowler.providers.aws.services.glue.glue_service import CatalogEncryptionSetting from tests.providers.aws.utils import ( @@ -11,13 +11,16 @@ from tests.providers.aws.utils import ( class Test_glue_data_catalogs_connection_passwords_encryption_enabled: def test_glue_no_settings(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.provider = set_mocked_aws_provider() glue_client.catalog_encryption_settings = [] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_data_catalogs_connection_passwords_encryption_enabled.glue_data_catalogs_connection_passwords_encryption_enabled import ( @@ -30,7 +33,7 @@ class Test_glue_data_catalogs_connection_passwords_encryption_enabled: assert len(result) == 0 def test_glue_catalog_password_unencrypted(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.provider = set_mocked_aws_provider() glue_client.catalog_encryption_settings = [ CatalogEncryptionSetting( @@ -46,12 +49,15 @@ class Test_glue_data_catalogs_connection_passwords_encryption_enabled: glue_client.audited_partition = AWS_COMMERCIAL_PARTITION glue_client.region = AWS_REGION_US_EAST_1 glue_client.data_catalog_arn_template = f"arn:{glue_client.audited_partition}:glue:{glue_client.region}:{glue_client.audited_account}:data-catalog" - glue_client._get_data_catalog_arn_template = mock.MagicMock( + glue_client._get_data_catalog_arn_template = MagicMock( return_value=glue_client.data_catalog_arn_template ) - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_data_catalogs_connection_passwords_encryption_enabled.glue_data_catalogs_connection_passwords_encryption_enabled import ( @@ -72,7 +78,7 @@ class Test_glue_data_catalogs_connection_passwords_encryption_enabled: assert result[0].region == AWS_REGION_US_EAST_1 def test_glue_catalog_password_unencrypted_ignoring(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.provider = set_mocked_aws_provider() glue_client.catalog_encryption_settings = [ CatalogEncryptionSetting( @@ -88,13 +94,16 @@ class Test_glue_data_catalogs_connection_passwords_encryption_enabled: glue_client.audited_partition = AWS_COMMERCIAL_PARTITION glue_client.region = AWS_REGION_US_EAST_1 glue_client.data_catalog_arn_template = f"arn:{glue_client.audited_partition}:glue:{glue_client.region}:{glue_client.audited_account}:data-catalog" - glue_client._get_data_catalog_arn_template = mock.MagicMock( + glue_client._get_data_catalog_arn_template = MagicMock( return_value=glue_client.data_catalog_arn_template ) glue_client.provider._scan_unused_services = False - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_data_catalogs_connection_passwords_encryption_enabled.glue_data_catalogs_connection_passwords_encryption_enabled import ( @@ -107,7 +116,7 @@ class Test_glue_data_catalogs_connection_passwords_encryption_enabled: assert len(result) == 0 def test_glue_catalog_password_unencrypted_ignoring_with_tables(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.provider = set_mocked_aws_provider() glue_client.catalog_encryption_settings = [ CatalogEncryptionSetting( @@ -123,13 +132,16 @@ class Test_glue_data_catalogs_connection_passwords_encryption_enabled: glue_client.region = AWS_REGION_US_EAST_1 glue_client.audited_account = AWS_ACCOUNT_NUMBER glue_client.data_catalog_arn_template = f"arn:{glue_client.audited_partition}:glue:{glue_client.region}:{glue_client.audited_account}:data-catalog" - glue_client._get_data_catalog_arn_template = mock.MagicMock( + glue_client._get_data_catalog_arn_template = MagicMock( return_value=glue_client.data_catalog_arn_template ) glue_client.provider._scan_unused_services = False - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_data_catalogs_connection_passwords_encryption_enabled.glue_data_catalogs_connection_passwords_encryption_enabled import ( @@ -150,7 +162,7 @@ class Test_glue_data_catalogs_connection_passwords_encryption_enabled: assert result[0].region == AWS_REGION_US_EAST_1 def test_glue_catalog_encrypted(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.provider = set_mocked_aws_provider() glue_client.catalog_encryption_settings = [ CatalogEncryptionSetting( @@ -165,13 +177,16 @@ class Test_glue_data_catalogs_connection_passwords_encryption_enabled: glue_client.region = AWS_REGION_US_EAST_1 glue_client.audited_account = AWS_ACCOUNT_NUMBER glue_client.data_catalog_arn_template = f"arn:{glue_client.audited_partition}:glue:{glue_client.region}:{glue_client.audited_account}:data-catalog" - glue_client._get_data_catalog_arn_template = mock.MagicMock( + glue_client._get_data_catalog_arn_template = MagicMock( return_value=glue_client.data_catalog_arn_template ) - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_data_catalogs_connection_passwords_encryption_enabled.glue_data_catalogs_connection_passwords_encryption_enabled import ( diff --git a/tests/providers/aws/services/glue/glue_data_catalogs_metadata_encryption_enabled/glue_data_catalogs_metadata_encryption_enabled_test.py b/tests/providers/aws/services/glue/glue_data_catalogs_metadata_encryption_enabled/glue_data_catalogs_metadata_encryption_enabled_test.py index bcd244056a..c13b0dc3a5 100644 --- a/tests/providers/aws/services/glue/glue_data_catalogs_metadata_encryption_enabled/glue_data_catalogs_metadata_encryption_enabled_test.py +++ b/tests/providers/aws/services/glue/glue_data_catalogs_metadata_encryption_enabled/glue_data_catalogs_metadata_encryption_enabled_test.py @@ -1,5 +1,5 @@ from re import search -from unittest import mock +from unittest.mock import MagicMock, patch from prowler.providers.aws.services.glue.glue_service import CatalogEncryptionSetting from tests.providers.aws.utils import ( @@ -12,13 +12,16 @@ from tests.providers.aws.utils import ( class Test_glue_data_catalogs_metadata_encryption_enabled: def test_glue_no_settings(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.provider = set_mocked_aws_provider glue_client.catalog_encryption_settings = [] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_data_catalogs_metadata_encryption_enabled.glue_data_catalogs_metadata_encryption_enabled import ( @@ -31,7 +34,7 @@ class Test_glue_data_catalogs_metadata_encryption_enabled: assert len(result) == 0 def test_glue_catalog_unencrypted(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.provider = set_mocked_aws_provider() glue_client.catalog_encryption_settings = [ CatalogEncryptionSetting( @@ -47,12 +50,15 @@ class Test_glue_data_catalogs_metadata_encryption_enabled: glue_client.audited_account = AWS_ACCOUNT_NUMBER glue_client.audited_partition = AWS_COMMERCIAL_PARTITION glue_client.data_catalog_arn_template = f"arn:{glue_client.audited_partition}:glue:{glue_client.region}:{glue_client.audited_account}:data-catalog" - glue_client._get_data_catalog_arn_template = mock.MagicMock( + glue_client._get_data_catalog_arn_template = MagicMock( return_value=glue_client.data_catalog_arn_template ) - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_data_catalogs_metadata_encryption_enabled.glue_data_catalogs_metadata_encryption_enabled import ( @@ -73,7 +79,7 @@ class Test_glue_data_catalogs_metadata_encryption_enabled: assert result[0].region == AWS_REGION_US_EAST_1 def test_glue_catalog_unencrypted_ignoring(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.provider = set_mocked_aws_provider() glue_client.catalog_encryption_settings = [ CatalogEncryptionSetting( @@ -90,12 +96,15 @@ class Test_glue_data_catalogs_metadata_encryption_enabled: glue_client.audited_account = AWS_ACCOUNT_NUMBER glue_client.audited_partition = AWS_COMMERCIAL_PARTITION glue_client.data_catalog_arn_template = f"arn:{glue_client.audited_partition}:glue:{glue_client.region}:{glue_client.audited_account}:data-catalog" - glue_client._get_data_catalog_arn_template = mock.MagicMock( + glue_client._get_data_catalog_arn_template = MagicMock( return_value=glue_client.data_catalog_arn_template ) - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_data_catalogs_metadata_encryption_enabled.glue_data_catalogs_metadata_encryption_enabled import ( @@ -108,7 +117,7 @@ class Test_glue_data_catalogs_metadata_encryption_enabled: assert len(result) == 0 def test_glue_catalog_unencrypted_ignoring_with_tables(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.provider = set_mocked_aws_provider() glue_client.catalog_encryption_settings = [ CatalogEncryptionSetting( @@ -125,12 +134,15 @@ class Test_glue_data_catalogs_metadata_encryption_enabled: glue_client.audited_account = AWS_ACCOUNT_NUMBER glue_client.audited_partition = AWS_COMMERCIAL_PARTITION glue_client.data_catalog_arn_template = f"arn:{glue_client.audited_partition}:glue:{glue_client.region}:{glue_client.audited_account}:data-catalog" - glue_client._get_data_catalog_arn_template = mock.MagicMock( + glue_client._get_data_catalog_arn_template = MagicMock( return_value=glue_client.data_catalog_arn_template ) - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_data_catalogs_metadata_encryption_enabled.glue_data_catalogs_metadata_encryption_enabled import ( @@ -151,7 +163,7 @@ class Test_glue_data_catalogs_metadata_encryption_enabled: assert result[0].region == AWS_REGION_US_EAST_1 def test_glue_catalog_encrypted(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.provider = set_mocked_aws_provider() glue_client.catalog_encryption_settings = [ CatalogEncryptionSetting( @@ -167,12 +179,15 @@ class Test_glue_data_catalogs_metadata_encryption_enabled: glue_client.audited_account = AWS_ACCOUNT_NUMBER glue_client.audited_partition = AWS_COMMERCIAL_PARTITION glue_client.data_catalog_arn_template = f"arn:{glue_client.audited_partition}:glue:{glue_client.region}:{glue_client.audited_account}:data-catalog" - glue_client._get_data_catalog_arn_template = mock.MagicMock( + glue_client._get_data_catalog_arn_template = MagicMock( return_value=glue_client.data_catalog_arn_template ) - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_data_catalogs_metadata_encryption_enabled.glue_data_catalogs_metadata_encryption_enabled import ( diff --git a/tests/providers/aws/services/glue/glue_database_connections_ssl_enabled/glue_database_connections_ssl_enabled_test.py b/tests/providers/aws/services/glue/glue_database_connections_ssl_enabled/glue_database_connections_ssl_enabled_test.py index b7cff5a676..1794d9cf7c 100644 --- a/tests/providers/aws/services/glue/glue_database_connections_ssl_enabled/glue_database_connections_ssl_enabled_test.py +++ b/tests/providers/aws/services/glue/glue_database_connections_ssl_enabled/glue_database_connections_ssl_enabled_test.py @@ -1,5 +1,5 @@ from re import search -from unittest import mock +from unittest.mock import MagicMock, patch from prowler.providers.aws.services.glue.glue_service import Connection from tests.providers.aws.utils import AWS_REGION_US_EAST_1 @@ -7,12 +7,15 @@ from tests.providers.aws.utils import AWS_REGION_US_EAST_1 class Test_glue_database_connections_ssl_enabled: def test_glue_no_conns(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.connections = [] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_database_connections_ssl_enabled.glue_database_connections_ssl_enabled import ( @@ -25,7 +28,7 @@ class Test_glue_database_connections_ssl_enabled: assert len(result) == 0 def test_glue_table_no_SSL(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.connections = [ Connection( name="test", @@ -42,9 +45,12 @@ class Test_glue_database_connections_ssl_enabled: ) ] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_database_connections_ssl_enabled.glue_database_connections_ssl_enabled import ( @@ -65,7 +71,7 @@ class Test_glue_database_connections_ssl_enabled: assert result[0].resource_tags == [{"test": "test"}] def test_glue_table_with_SSL(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.connections = [ Connection( name="test", @@ -83,9 +89,12 @@ class Test_glue_database_connections_ssl_enabled: ) ] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_database_connections_ssl_enabled.glue_database_connections_ssl_enabled import ( diff --git a/tests/providers/aws/services/glue/glue_development_endpoints_cloudwatch_logs_encryption_enabled/glue_development_endpoints_cloudwatch_logs_encryption_enabled_test.py b/tests/providers/aws/services/glue/glue_development_endpoints_cloudwatch_logs_encryption_enabled/glue_development_endpoints_cloudwatch_logs_encryption_enabled_test.py index 49a21bd768..01151d53f9 100644 --- a/tests/providers/aws/services/glue/glue_development_endpoints_cloudwatch_logs_encryption_enabled/glue_development_endpoints_cloudwatch_logs_encryption_enabled_test.py +++ b/tests/providers/aws/services/glue/glue_development_endpoints_cloudwatch_logs_encryption_enabled/glue_development_endpoints_cloudwatch_logs_encryption_enabled_test.py @@ -1,5 +1,5 @@ from re import search -from unittest import mock +from unittest.mock import MagicMock, patch from prowler.providers.aws.services.glue.glue_service import DevEndpoint, SecurityConfig from tests.providers.aws.utils import AWS_REGION_US_EAST_1 @@ -7,12 +7,15 @@ from tests.providers.aws.utils import AWS_REGION_US_EAST_1 class Test_glue_development_endpoints_cloudwatch_logs_encryption_enabled: def test_glue_no_endpoints(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.dev_endpoints = [] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_development_endpoints_cloudwatch_logs_encryption_enabled.glue_development_endpoints_cloudwatch_logs_encryption_enabled import ( @@ -25,7 +28,7 @@ class Test_glue_development_endpoints_cloudwatch_logs_encryption_enabled: assert len(result) == 0 def test_glue_encrypted_endpoint(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.dev_endpoints = [ DevEndpoint( name="test", @@ -46,9 +49,12 @@ class Test_glue_development_endpoints_cloudwatch_logs_encryption_enabled: ) ] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_development_endpoints_cloudwatch_logs_encryption_enabled.glue_development_endpoints_cloudwatch_logs_encryption_enabled import ( @@ -69,7 +75,7 @@ class Test_glue_development_endpoints_cloudwatch_logs_encryption_enabled: assert result[0].resource_tags == [{"key_test": "value_test"}] def test_glue_unencrypted_endpoint(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.dev_endpoints = [ DevEndpoint( name="test", @@ -89,9 +95,12 @@ class Test_glue_development_endpoints_cloudwatch_logs_encryption_enabled: ) ] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_development_endpoints_cloudwatch_logs_encryption_enabled.glue_development_endpoints_cloudwatch_logs_encryption_enabled import ( @@ -112,7 +121,7 @@ class Test_glue_development_endpoints_cloudwatch_logs_encryption_enabled: assert result[0].resource_tags == [{"key_test": "value_test"}] def test_glue_no_sec_configs(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.dev_endpoints = [ DevEndpoint( name="test", @@ -124,9 +133,12 @@ class Test_glue_development_endpoints_cloudwatch_logs_encryption_enabled: ] glue_client.security_configs = [] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_development_endpoints_cloudwatch_logs_encryption_enabled.glue_development_endpoints_cloudwatch_logs_encryption_enabled import ( diff --git a/tests/providers/aws/services/glue/glue_development_endpoints_job_bookmark_encryption_enabled/glue_development_endpoints_job_bookmark_encryption_enabled_test.py b/tests/providers/aws/services/glue/glue_development_endpoints_job_bookmark_encryption_enabled/glue_development_endpoints_job_bookmark_encryption_enabled_test.py index 4c3011faeb..d92003b817 100644 --- a/tests/providers/aws/services/glue/glue_development_endpoints_job_bookmark_encryption_enabled/glue_development_endpoints_job_bookmark_encryption_enabled_test.py +++ b/tests/providers/aws/services/glue/glue_development_endpoints_job_bookmark_encryption_enabled/glue_development_endpoints_job_bookmark_encryption_enabled_test.py @@ -1,5 +1,5 @@ from re import search -from unittest import mock +from unittest.mock import MagicMock, patch from prowler.providers.aws.services.glue.glue_service import DevEndpoint, SecurityConfig from tests.providers.aws.utils import AWS_REGION_US_EAST_1 @@ -7,12 +7,15 @@ from tests.providers.aws.utils import AWS_REGION_US_EAST_1 class Test_glue_development_endpoints_job_bookmark_encryption_enabled: def test_glue_no_endpoints(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.dev_endpoints = [] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_development_endpoints_job_bookmark_encryption_enabled.glue_development_endpoints_job_bookmark_encryption_enabled import ( @@ -25,7 +28,7 @@ class Test_glue_development_endpoints_job_bookmark_encryption_enabled: assert len(result) == 0 def test_glue_encrypted_endpoint(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.dev_endpoints = [ DevEndpoint( name="test", @@ -46,9 +49,12 @@ class Test_glue_development_endpoints_job_bookmark_encryption_enabled: ) ] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_development_endpoints_job_bookmark_encryption_enabled.glue_development_endpoints_job_bookmark_encryption_enabled import ( @@ -69,7 +75,7 @@ class Test_glue_development_endpoints_job_bookmark_encryption_enabled: assert result[0].resource_tags == [{"test": "value"}] def test_glue_unencrypted_endpoint(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.dev_endpoints = [ DevEndpoint( name="test", @@ -89,9 +95,12 @@ class Test_glue_development_endpoints_job_bookmark_encryption_enabled: ) ] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_development_endpoints_job_bookmark_encryption_enabled.glue_development_endpoints_job_bookmark_encryption_enabled import ( @@ -112,7 +121,7 @@ class Test_glue_development_endpoints_job_bookmark_encryption_enabled: assert result[0].resource_tags == [{"test": "value"}] def test_glue_no_sec_configs(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.dev_endpoints = [ DevEndpoint( name="test", @@ -124,9 +133,12 @@ class Test_glue_development_endpoints_job_bookmark_encryption_enabled: ] glue_client.security_configs = [] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_development_endpoints_job_bookmark_encryption_enabled.glue_development_endpoints_job_bookmark_encryption_enabled import ( diff --git a/tests/providers/aws/services/glue/glue_development_endpoints_s3_encryption_enabled/glue_development_endpoints_s3_encryption_enabled_test.py b/tests/providers/aws/services/glue/glue_development_endpoints_s3_encryption_enabled/glue_development_endpoints_s3_encryption_enabled_test.py index c0b88ce77a..8859e5faba 100644 --- a/tests/providers/aws/services/glue/glue_development_endpoints_s3_encryption_enabled/glue_development_endpoints_s3_encryption_enabled_test.py +++ b/tests/providers/aws/services/glue/glue_development_endpoints_s3_encryption_enabled/glue_development_endpoints_s3_encryption_enabled_test.py @@ -1,5 +1,5 @@ from re import search -from unittest import mock +from unittest.mock import MagicMock, patch from prowler.providers.aws.services.glue.glue_service import DevEndpoint, SecurityConfig from tests.providers.aws.utils import AWS_REGION_US_EAST_1 @@ -7,12 +7,15 @@ from tests.providers.aws.utils import AWS_REGION_US_EAST_1 class Test_glue_development_endpoints_s3_encryption_enabled: def test_glue_no_endpoints(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.dev_endpoints = [] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_development_endpoints_s3_encryption_enabled.glue_development_endpoints_s3_encryption_enabled import ( @@ -25,7 +28,7 @@ class Test_glue_development_endpoints_s3_encryption_enabled: assert len(result) == 0 def test_glue_encrypted_endpoint(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.dev_endpoints = [ DevEndpoint( name="test", @@ -46,9 +49,12 @@ class Test_glue_development_endpoints_s3_encryption_enabled: ) ] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_development_endpoints_s3_encryption_enabled.glue_development_endpoints_s3_encryption_enabled import ( @@ -69,7 +75,7 @@ class Test_glue_development_endpoints_s3_encryption_enabled: assert result[0].resource_tags == [{"key_test": "value_test"}] def test_glue_unencrypted_endpoint(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.dev_endpoints = [ DevEndpoint( name="test", @@ -89,9 +95,12 @@ class Test_glue_development_endpoints_s3_encryption_enabled: ) ] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_development_endpoints_s3_encryption_enabled.glue_development_endpoints_s3_encryption_enabled import ( @@ -112,7 +121,7 @@ class Test_glue_development_endpoints_s3_encryption_enabled: assert result[0].resource_tags == [{"key_test": "value_test"}] def test_glue_no_sec_configs(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.dev_endpoints = [ DevEndpoint( name="test", @@ -124,9 +133,12 @@ class Test_glue_development_endpoints_s3_encryption_enabled: ] glue_client.security_configs = [] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_development_endpoints_s3_encryption_enabled.glue_development_endpoints_s3_encryption_enabled import ( diff --git a/tests/providers/aws/services/glue/glue_etl_jobs_amazon_s3_encryption_enabled/glue_etl_jobs_amazon_s3_encryption_enabled_test.py b/tests/providers/aws/services/glue/glue_etl_jobs_amazon_s3_encryption_enabled/glue_etl_jobs_amazon_s3_encryption_enabled_test.py index e617fa81f9..81fa6a669b 100644 --- a/tests/providers/aws/services/glue/glue_etl_jobs_amazon_s3_encryption_enabled/glue_etl_jobs_amazon_s3_encryption_enabled_test.py +++ b/tests/providers/aws/services/glue/glue_etl_jobs_amazon_s3_encryption_enabled/glue_etl_jobs_amazon_s3_encryption_enabled_test.py @@ -1,5 +1,5 @@ from re import search -from unittest import mock +from unittest.mock import MagicMock, patch from prowler.providers.aws.services.glue.glue_service import Job, SecurityConfig from tests.providers.aws.utils import AWS_REGION_US_EAST_1 @@ -7,12 +7,15 @@ from tests.providers.aws.utils import AWS_REGION_US_EAST_1 class Test_glue_etl_jobs_amazon_s3_encryption_enabled: def test_glue_no_jobs(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.jobs = [] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_etl_jobs_amazon_s3_encryption_enabled.glue_etl_jobs_amazon_s3_encryption_enabled import ( @@ -25,7 +28,7 @@ class Test_glue_etl_jobs_amazon_s3_encryption_enabled: assert len(result) == 0 def test_glue_encrypted_job(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.jobs = [ Job( name="test", @@ -47,9 +50,12 @@ class Test_glue_etl_jobs_amazon_s3_encryption_enabled: ) ] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_etl_jobs_amazon_s3_encryption_enabled.glue_etl_jobs_amazon_s3_encryption_enabled import ( @@ -70,7 +76,7 @@ class Test_glue_etl_jobs_amazon_s3_encryption_enabled: assert result[0].resource_tags == [{"key_test": "value_test"}] def test_glue_unencrypted_job(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.jobs = [ Job( name="test", @@ -91,9 +97,12 @@ class Test_glue_etl_jobs_amazon_s3_encryption_enabled: ) ] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_etl_jobs_amazon_s3_encryption_enabled.glue_etl_jobs_amazon_s3_encryption_enabled import ( @@ -114,7 +123,7 @@ class Test_glue_etl_jobs_amazon_s3_encryption_enabled: assert result[0].resource_tags == [{"key_test": "value_test"}] def test_glue_no_sec_configs(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.jobs = [ Job( name="test", @@ -126,9 +135,12 @@ class Test_glue_etl_jobs_amazon_s3_encryption_enabled: ] glue_client.security_configs = [] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_etl_jobs_amazon_s3_encryption_enabled.glue_etl_jobs_amazon_s3_encryption_enabled import ( @@ -149,7 +161,7 @@ class Test_glue_etl_jobs_amazon_s3_encryption_enabled: assert result[0].resource_tags == [{"key_test": "value_test"}] def test_glue_encrypted_job_with_argument(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.jobs = [ Job( name="test", @@ -165,9 +177,12 @@ class Test_glue_etl_jobs_amazon_s3_encryption_enabled: ] glue_client.security_configs = [] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_etl_jobs_amazon_s3_encryption_enabled.glue_etl_jobs_amazon_s3_encryption_enabled import ( diff --git a/tests/providers/aws/services/glue/glue_etl_jobs_cloudwatch_logs_encryption_enabled/glue_etl_jobs_cloudwatch_logs_encryption_enabled_test.py b/tests/providers/aws/services/glue/glue_etl_jobs_cloudwatch_logs_encryption_enabled/glue_etl_jobs_cloudwatch_logs_encryption_enabled_test.py index 1841f93db4..2147542e4a 100644 --- a/tests/providers/aws/services/glue/glue_etl_jobs_cloudwatch_logs_encryption_enabled/glue_etl_jobs_cloudwatch_logs_encryption_enabled_test.py +++ b/tests/providers/aws/services/glue/glue_etl_jobs_cloudwatch_logs_encryption_enabled/glue_etl_jobs_cloudwatch_logs_encryption_enabled_test.py @@ -1,5 +1,5 @@ from re import search -from unittest import mock +from unittest.mock import MagicMock, patch from prowler.providers.aws.services.glue.glue_service import Job, SecurityConfig from tests.providers.aws.utils import AWS_REGION_US_EAST_1 @@ -7,12 +7,15 @@ from tests.providers.aws.utils import AWS_REGION_US_EAST_1 class Test_glue_etl_jobs_cloudwatch_logs_encryption_enabled: def test_glue_no_jobs(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.jobs = [] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_etl_jobs_cloudwatch_logs_encryption_enabled.glue_etl_jobs_cloudwatch_logs_encryption_enabled import ( @@ -25,7 +28,7 @@ class Test_glue_etl_jobs_cloudwatch_logs_encryption_enabled: assert len(result) == 0 def test_glue_encrypted_job(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.jobs = [ Job( name="test", @@ -47,9 +50,12 @@ class Test_glue_etl_jobs_cloudwatch_logs_encryption_enabled: ) ] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_etl_jobs_cloudwatch_logs_encryption_enabled.glue_etl_jobs_cloudwatch_logs_encryption_enabled import ( @@ -70,7 +76,7 @@ class Test_glue_etl_jobs_cloudwatch_logs_encryption_enabled: assert result[0].resource_tags == [{"key_test": "value_test"}] def test_glue_unencrypted_job(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.jobs = [ Job( name="test", @@ -91,9 +97,12 @@ class Test_glue_etl_jobs_cloudwatch_logs_encryption_enabled: ) ] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_etl_jobs_cloudwatch_logs_encryption_enabled.glue_etl_jobs_cloudwatch_logs_encryption_enabled import ( @@ -114,7 +123,7 @@ class Test_glue_etl_jobs_cloudwatch_logs_encryption_enabled: assert result[0].resource_tags == [{"key_test": "value_test"}] def test_glue_no_sec_configs(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.jobs = [ Job( name="test", @@ -126,9 +135,12 @@ class Test_glue_etl_jobs_cloudwatch_logs_encryption_enabled: ] glue_client.security_configs = [] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_etl_jobs_cloudwatch_logs_encryption_enabled.glue_etl_jobs_cloudwatch_logs_encryption_enabled import ( diff --git a/tests/providers/aws/services/glue/glue_etl_jobs_job_bookmark_encryption_enabled/glue_etl_jobs_job_bookmark_encryption_enabled_test.py b/tests/providers/aws/services/glue/glue_etl_jobs_job_bookmark_encryption_enabled/glue_etl_jobs_job_bookmark_encryption_enabled_test.py index 1431369fd6..4bd10d30e9 100644 --- a/tests/providers/aws/services/glue/glue_etl_jobs_job_bookmark_encryption_enabled/glue_etl_jobs_job_bookmark_encryption_enabled_test.py +++ b/tests/providers/aws/services/glue/glue_etl_jobs_job_bookmark_encryption_enabled/glue_etl_jobs_job_bookmark_encryption_enabled_test.py @@ -1,5 +1,5 @@ from re import search -from unittest import mock +from unittest.mock import MagicMock, patch from prowler.providers.aws.services.glue.glue_service import Job, SecurityConfig from tests.providers.aws.utils import AWS_REGION_US_EAST_1 @@ -7,12 +7,15 @@ from tests.providers.aws.utils import AWS_REGION_US_EAST_1 class Test_glue_etl_jobs_job_bookmark_encryption_enabled: def test_glue_no_jobs(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.jobs = [] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_etl_jobs_job_bookmark_encryption_enabled.glue_etl_jobs_job_bookmark_encryption_enabled import ( @@ -25,7 +28,7 @@ class Test_glue_etl_jobs_job_bookmark_encryption_enabled: assert len(result) == 0 def test_glue_encrypted_job(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.jobs = [ Job( name="test", @@ -47,9 +50,12 @@ class Test_glue_etl_jobs_job_bookmark_encryption_enabled: ) ] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_etl_jobs_job_bookmark_encryption_enabled.glue_etl_jobs_job_bookmark_encryption_enabled import ( @@ -70,7 +76,7 @@ class Test_glue_etl_jobs_job_bookmark_encryption_enabled: assert result[0].resource_tags == [{"key_test": "value_test"}] def test_glue_unencrypted_job(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.jobs = [ Job( name="test", @@ -91,9 +97,12 @@ class Test_glue_etl_jobs_job_bookmark_encryption_enabled: ) ] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_etl_jobs_job_bookmark_encryption_enabled.glue_etl_jobs_job_bookmark_encryption_enabled import ( @@ -114,7 +123,7 @@ class Test_glue_etl_jobs_job_bookmark_encryption_enabled: assert result[0].resource_tags == [{"key_test": "value_test"}] def test_glue_no_sec_configs(self): - glue_client = mock.MagicMock + glue_client = MagicMock glue_client.jobs = [ Job( name="test", @@ -126,9 +135,12 @@ class Test_glue_etl_jobs_job_bookmark_encryption_enabled: ] glue_client.security_configs = [] - with mock.patch( + with patch( "prowler.providers.aws.services.glue.glue_service.Glue", - glue_client, + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, ): # Test Check from prowler.providers.aws.services.glue.glue_etl_jobs_job_bookmark_encryption_enabled.glue_etl_jobs_job_bookmark_encryption_enabled import ( diff --git a/tests/providers/aws/services/glue/glue_ml_transform_encrypted_at_rest/glue_ml_transform_encrypted_at_rest_test.py b/tests/providers/aws/services/glue/glue_ml_transform_encrypted_at_rest/glue_ml_transform_encrypted_at_rest_test.py new file mode 100644 index 0000000000..19e9f88cbe --- /dev/null +++ b/tests/providers/aws/services/glue/glue_ml_transform_encrypted_at_rest/glue_ml_transform_encrypted_at_rest_test.py @@ -0,0 +1,109 @@ +from unittest.mock import MagicMock, patch + +from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER, AWS_REGION_EU_WEST_1 + + +class Test_glue_ml_transform_encrypted_at_rest: + def test_no_ml_transfroms(self): + glue_client = MagicMock + glue_client.ml_transforms = {} + + with patch( + "prowler.providers.aws.services.glue.glue_service.Glue", + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, + ): + from prowler.providers.aws.services.glue.glue_ml_transform_encrypted_at_rest.glue_ml_transform_encrypted_at_rest import ( + glue_ml_transform_encrypted_at_rest, + ) + + check = glue_ml_transform_encrypted_at_rest() + result = check.execute() + + assert len(result) == 0 + + def test_ml_transform_encryption_disabled(self): + glue_client = MagicMock + ml_transform_id = "transform1" + ml_transform_arn = f"arn:aws:glue:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:mlTransform/{ml_transform_id}" + + from prowler.providers.aws.services.glue.glue_service import MLTransform + + glue_client.ml_transforms = { + ml_transform_arn: MLTransform( + arn=ml_transform_arn, + id=ml_transform_id, + name="ml-transform1", + user_data_encryption="DISABLED", + region=AWS_REGION_EU_WEST_1, + tags=[{"test_key": "test_value"}], + ) + } + + with patch( + "prowler.providers.aws.services.glue.glue_service.Glue", + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, + ): + from prowler.providers.aws.services.glue.glue_ml_transform_encrypted_at_rest.glue_ml_transform_encrypted_at_rest import ( + glue_ml_transform_encrypted_at_rest, + ) + + check = glue_ml_transform_encrypted_at_rest() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].resource_id == ml_transform_id + assert result[0].resource_arn == ml_transform_arn + assert result[0].region == AWS_REGION_EU_WEST_1 + assert ( + result[0].status_extended + == "Glue ML Transform ml-transform1 is not encrypted at rest." + ) + + def test_ml_transform_encryption_enabled(self): + glue_client = MagicMock + ml_transform_id = "transform2" + ml_transform_arn = f"arn:aws:glue:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:mlTransform/{ml_transform_id}" + + from prowler.providers.aws.services.glue.glue_service import MLTransform + + glue_client.ml_transforms = { + ml_transform_arn: MLTransform( + arn=ml_transform_arn, + id=ml_transform_id, + name="ml-transform2", + user_data_encryption="SSE-KMS", + region=AWS_REGION_EU_WEST_1, + tags=[{"test_key": "test_value"}], + ) + } + + with patch( + "prowler.providers.aws.services.glue.glue_service.Glue", + new=glue_client, + ), patch( + "prowler.providers.aws.services.glue.glue_client.glue_client", + new=glue_client, + ): + from prowler.providers.aws.services.glue.glue_ml_transform_encrypted_at_rest.glue_ml_transform_encrypted_at_rest import ( + glue_ml_transform_encrypted_at_rest, + ) + + check = glue_ml_transform_encrypted_at_rest() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].resource_id == ml_transform_id + assert result[0].resource_arn == ml_transform_arn + assert result[0].region == AWS_REGION_EU_WEST_1 + assert ( + result[0].status_extended + == "Glue ML Transform ml-transform2 is encrypted at rest." + ) diff --git a/tests/providers/aws/services/glue/glue_service_test.py b/tests/providers/aws/services/glue/glue_service_test.py index 7a1a69ee50..9a2c72c81c 100644 --- a/tests/providers/aws/services/glue/glue_service_test.py +++ b/tests/providers/aws/services/glue/glue_service_test.py @@ -100,6 +100,16 @@ def mock_make_api_call(self, operation_name, kwarg): }, ], } + elif operation_name == "GetMLTransforms": + return { + "Transforms": [ + { + "Name": "ml-transform1", + "TransformId": "transform1", + "UserDefinedEncryption": "DISABLED", + } + ] + } elif operation_name == "GetTags": return { "Tags": { @@ -234,6 +244,20 @@ class Test_Glue_Service: } assert glue.jobs[0].region == AWS_REGION_US_EAST_1 + @mock_aws + def test_get_ml_transforms(self): + aws_provider = set_mocked_aws_provider() + glue = Glue(aws_provider) + arn_transform = f"arn:aws:glue:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:mlTransform/transform1" + + assert len(glue.ml_transforms) == 1 + assert arn_transform in glue.ml_transforms + assert glue.ml_transforms[arn_transform].arn == arn_transform + assert glue.ml_transforms[arn_transform].id == "transform1" + assert glue.ml_transforms[arn_transform].name == "ml-transform1" + assert glue.ml_transforms[arn_transform].user_data_encryption == "DISABLED" + assert glue.ml_transforms[arn_transform].region == AWS_REGION_US_EAST_1 + @mock_aws def test_get_tags(self): aws_provider = set_mocked_aws_provider() diff --git a/tests/providers/aws/services/guardduty/guardduty_centrally_managed/guardduty_centrally_managed_test.py b/tests/providers/aws/services/guardduty/guardduty_centrally_managed/guardduty_centrally_managed_test.py index 7ff4d46a82..739d9728df 100644 --- a/tests/providers/aws/services/guardduty/guardduty_centrally_managed/guardduty_centrally_managed_test.py +++ b/tests/providers/aws/services/guardduty/guardduty_centrally_managed/guardduty_centrally_managed_test.py @@ -1,46 +1,79 @@ -from unittest import mock -from uuid import uuid4 +from unittest.mock import patch -from prowler.providers.aws.services.guardduty.guardduty_service import Detector -from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER, AWS_REGION_EU_WEST_1 +import botocore +from boto3 import client +from moto import mock_aws -AWS_ACCOUNT_NUMBER_ADMIN = "123456789013" -DETECTOR_ID = str(uuid4()) -DETECTOR_ARN = f"arn:aws:guardduty:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:detector/{DETECTOR_ID}" +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_EU_WEST_1, + set_mocked_aws_provider, +) + +orig = botocore.client.BaseClient._make_api_call + + +def mock_make_api_call_admin_enabled(self, operation_name, api_params): + if operation_name == "GetAdministratorAccount": + return { + "Administrator": { + "AccountId": "210987654321", + } + } + return orig(self, operation_name, api_params) + + +def mock_make_api_call_members_managers(self, operation_name, api_params): + if operation_name == "ListMembers": + return { + "Members": [ + { + "AccountId": "210987654321", + "RelationshipStatus": "Enabled", + } + ] + } + return orig(self, operation_name, api_params) class Test_guardduty_centrally_managed: + @mock_aws def test_no_detectors(self): - guardduty_client = mock.MagicMock - guardduty_client.detectors = [] - with mock.patch( - "prowler.providers.aws.services.guardduty.guardduty_service.GuardDuty", - guardduty_client, + aws_provider = set_mocked_aws_provider() + + from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), patch( + "prowler.providers.aws.services.guardduty.guardduty_centrally_managed.guardduty_centrally_managed.guardduty_client", + new=GuardDuty(aws_provider), ): - from prowler.providers.aws.services.guardduty.guardduty_no_high_severity_findings.guardduty_no_high_severity_findings import ( - guardduty_no_high_severity_findings, + from prowler.providers.aws.services.guardduty.guardduty_centrally_managed.guardduty_centrally_managed import ( + guardduty_centrally_managed, ) - check = guardduty_no_high_severity_findings() + check = guardduty_centrally_managed() result = check.execute() assert len(result) == 0 + @mock_aws def test_detector_no_centralized_managed(self): - guardduty_client = mock.MagicMock - guardduty_client.detectors = [] - guardduty_client.detectors.append( - Detector( - id=DETECTOR_ID, - region=AWS_REGION_EU_WEST_1, - arn=DETECTOR_ARN, - status=False, - findings=[str(uuid4())], - ) - ) + guardduty_client = client("guardduty", region_name=AWS_REGION_EU_WEST_1) - with mock.patch( - "prowler.providers.aws.services.guardduty.guardduty_service.GuardDuty", - guardduty_client, + detector_id = guardduty_client.create_detector(Enable=True)["DetectorId"] + + aws_provider = set_mocked_aws_provider() + + from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), patch( + "prowler.providers.aws.services.guardduty.guardduty_centrally_managed.guardduty_centrally_managed.guardduty_client", + new=GuardDuty(aws_provider), ): # Test Check from prowler.providers.aws.services.guardduty.guardduty_centrally_managed.guardduty_centrally_managed import ( @@ -53,54 +86,36 @@ class Test_guardduty_centrally_managed: assert result[0].status == "FAIL" assert ( result[0].status_extended - == f"GuardDuty detector {DETECTOR_ID} is not centrally managed." + == f"GuardDuty detector {detector_id} is not centrally managed." ) - assert result[0].resource_id == DETECTOR_ID + assert result[0].resource_id == detector_id assert result[0].region == AWS_REGION_EU_WEST_1 - assert result[0].resource_arn == DETECTOR_ARN - - def test_not_enabled_account_detector(self): - guardduty_client = mock.MagicMock - guardduty_client.detectors = [] - guardduty_client.detectors.append( - Detector( - id=AWS_ACCOUNT_NUMBER, - region=AWS_REGION_EU_WEST_1, - arn=DETECTOR_ARN, - enabled_in_account=False, + assert ( + result[0].resource_arn + == f"arn:aws:guardduty:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:detector/{detector_id}" ) - ) - - with mock.patch( - "prowler.providers.aws.services.guardduty.guardduty_service.GuardDuty", - guardduty_client, - ): - # Test Check - from prowler.providers.aws.services.guardduty.guardduty_centrally_managed.guardduty_centrally_managed import ( - guardduty_centrally_managed, - ) - - check = guardduty_centrally_managed() - result = check.execute() - assert len(result) == 0 + assert result[0].resource_tags == [] + @patch( + "botocore.client.BaseClient._make_api_call", + new=mock_make_api_call_admin_enabled, + ) + @mock_aws def test_detector_centralized_managed(self): - guardduty_client = mock.MagicMock - guardduty_client.detectors = [] - guardduty_client.detectors.append( - Detector( - id=DETECTOR_ID, - region=AWS_REGION_EU_WEST_1, - arn=DETECTOR_ARN, - status=False, - findings=[str(uuid4())], - administrator_account=AWS_ACCOUNT_NUMBER_ADMIN, - ) - ) + guardduty_client = client("guardduty", region_name=AWS_REGION_EU_WEST_1) - with mock.patch( - "prowler.providers.aws.services.guardduty.guardduty_service.GuardDuty", - guardduty_client, + detector_id = guardduty_client.create_detector(Enable=True)["DetectorId"] + + aws_provider = set_mocked_aws_provider() + + from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), patch( + "prowler.providers.aws.services.guardduty.guardduty_centrally_managed.guardduty_centrally_managed.guardduty_client", + new=GuardDuty(aws_provider), ): # Test Check from prowler.providers.aws.services.guardduty.guardduty_centrally_managed.guardduty_centrally_managed import ( @@ -113,29 +128,35 @@ class Test_guardduty_centrally_managed: assert result[0].status == "PASS" assert ( result[0].status_extended - == f"GuardDuty detector {DETECTOR_ID} is centrally managed by account {AWS_ACCOUNT_NUMBER_ADMIN}." + == f"GuardDuty detector {detector_id} is centrally managed by account 210987654321." ) - assert result[0].resource_id == DETECTOR_ID + assert result[0].resource_id == detector_id assert result[0].region == AWS_REGION_EU_WEST_1 - assert result[0].resource_arn == DETECTOR_ARN - - def test_detector_administrator(self): - guardduty_client = mock.MagicMock - guardduty_client.detectors = [] - guardduty_client.detectors.append( - Detector( - id=DETECTOR_ID, - region=AWS_REGION_EU_WEST_1, - arn=DETECTOR_ARN, - status=False, - findings=[str(uuid4())], - member_accounts=[AWS_ACCOUNT_NUMBER_ADMIN], + assert ( + result[0].resource_arn + == f"arn:aws:guardduty:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:detector/{detector_id}" ) - ) - with mock.patch( - "prowler.providers.aws.services.guardduty.guardduty_service.GuardDuty", - guardduty_client, + @patch( + "botocore.client.BaseClient._make_api_call", + new=mock_make_api_call_members_managers, + ) + @mock_aws + def test_detector_members_accounts(self): + guardduty_client = client("guardduty", region_name=AWS_REGION_EU_WEST_1) + + detector_id = guardduty_client.create_detector(Enable=True)["DetectorId"] + + aws_provider = set_mocked_aws_provider() + + from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), patch( + "prowler.providers.aws.services.guardduty.guardduty_centrally_managed.guardduty_centrally_managed.guardduty_client", + new=GuardDuty(aws_provider), ): # Test Check from prowler.providers.aws.services.guardduty.guardduty_centrally_managed.guardduty_centrally_managed import ( @@ -148,8 +169,11 @@ class Test_guardduty_centrally_managed: assert result[0].status == "PASS" assert ( result[0].status_extended - == f"GuardDuty detector {DETECTOR_ID} is administrator account with 1 member accounts." + == f"GuardDuty detector {detector_id} is administrator account with 1 member accounts." ) - assert result[0].resource_id == DETECTOR_ID + assert result[0].resource_id == detector_id assert result[0].region == AWS_REGION_EU_WEST_1 - assert result[0].resource_arn == DETECTOR_ARN + assert ( + result[0].resource_arn + == f"arn:aws:guardduty:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:detector/{detector_id}" + ) diff --git a/tests/providers/aws/services/guardduty/guardduty_ec2_malware_protection_enabled/guardduty_ec2_malware_protection_enabled_test.py b/tests/providers/aws/services/guardduty/guardduty_ec2_malware_protection_enabled/guardduty_ec2_malware_protection_enabled_test.py new file mode 100644 index 0000000000..afd1e90da8 --- /dev/null +++ b/tests/providers/aws/services/guardduty/guardduty_ec2_malware_protection_enabled/guardduty_ec2_malware_protection_enabled_test.py @@ -0,0 +1,186 @@ +from unittest.mock import patch + +import botocore +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, +) + +orig = botocore.client.BaseClient._make_api_call + + +def mock_make_api_call(self, operation_name, kwarg): + if operation_name == "GetDetector": + return { + "CreatedAt": "2021-01-01T00:00:00Z", + "FindingPublishingFrequency": "FIFTEEN_MINUTES", + "ServiceRole": "AWSServiceRoleForAmazonGuardDuty", + "Status": "ENABLED", + "UpdatedAt": "2021-01-01T00:00:00Z", + "DataSources": { + "S3Logs": { + "Enable": False, + }, + "CloudTrail": { + "Enable": False, + }, + "DNSLogs": { + "Enable": False, + }, + "MalwareProtection": { + "ScanEc2InstanceWithFindings": { + "EbsVolumes": {"Status": "ENABLED"}, + }, + }, + }, + } + # If we don't want to patch the API call + return orig(self, operation_name, kwarg) + + +class Test_guardduty_ec2_malware_protection_enabled: + def test_no_detectors(self): + aws_provider = set_mocked_aws_provider() + + from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), patch( + "prowler.providers.aws.services.guardduty.guardduty_ec2_malware_protection_enabled.guardduty_ec2_malware_protection_enabled.guardduty_client", + new=GuardDuty(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.guardduty.guardduty_ec2_malware_protection_enabled.guardduty_ec2_malware_protection_enabled import ( + guardduty_ec2_malware_protection_enabled, + ) + + check = guardduty_ec2_malware_protection_enabled() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_detector_disabled(self): + guardduty_client = client("guardduty", region_name=AWS_REGION_EU_WEST_1) + + guardduty_client.create_detector(Enable=False) + + aws_provider = set_mocked_aws_provider() + + from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), patch( + "prowler.providers.aws.services.guardduty.guardduty_ec2_malware_protection_enabled.guardduty_ec2_malware_protection_enabled.guardduty_client", + new=GuardDuty(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.guardduty.guardduty_ec2_malware_protection_enabled.guardduty_ec2_malware_protection_enabled import ( + guardduty_ec2_malware_protection_enabled, + ) + + check = guardduty_ec2_malware_protection_enabled() + result = check.execute() + + assert len(result) == 0 + + @patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) + @mock_aws + def test_detector_malware_protection_enabled(self): + guardduty_client = client("guardduty", region_name=AWS_REGION_EU_WEST_1) + + detector_id = guardduty_client.create_detector( + Enable=True, + DataSources={ + "MalwareProtection": { + "ScanEc2InstanceWithFindings": {"EbsVolumes": True} + } + }, + )["DetectorId"] + + aws_provider = set_mocked_aws_provider() + + from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), patch( + "prowler.providers.aws.services.guardduty.guardduty_ec2_malware_protection_enabled.guardduty_ec2_malware_protection_enabled.guardduty_client", + new=GuardDuty(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.guardduty.guardduty_ec2_malware_protection_enabled.guardduty_ec2_malware_protection_enabled import ( + guardduty_ec2_malware_protection_enabled, + ) + + check = guardduty_ec2_malware_protection_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"GuardDuty detector {detector_id} has Malware Protection for EC2 enabled." + ) + assert result[0].resource_id == detector_id + assert result[0].region == AWS_REGION_EU_WEST_1 + assert ( + result[0].resource_arn + == f"arn:aws:guardduty:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:detector/{detector_id}" + ) + assert result[0].resource_tags == [] + + @mock_aws + def test_detector_malware_protection_disabled(self): + guardduty_client = client("guardduty", region_name=AWS_REGION_EU_WEST_1) + + detector_id = guardduty_client.create_detector( + Enable=True, + DataSources={ + "MalwareProtection": { + "ScanEc2InstanceWithFindings": {"EbsVolumes": False} + } + }, + )["DetectorId"] + + aws_provider = set_mocked_aws_provider() + + from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), patch( + "prowler.providers.aws.services.guardduty.guardduty_ec2_malware_protection_enabled.guardduty_ec2_malware_protection_enabled.guardduty_client", + new=GuardDuty(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.guardduty.guardduty_ec2_malware_protection_enabled.guardduty_ec2_malware_protection_enabled import ( + guardduty_ec2_malware_protection_enabled, + ) + + check = guardduty_ec2_malware_protection_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"GuardDuty detector {detector_id} does not have Malware Protection for EC2 enabled." + ) + assert result[0].resource_id == detector_id + assert result[0].region == AWS_REGION_EU_WEST_1 + assert ( + result[0].resource_arn + == f"arn:aws:guardduty:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:detector/{detector_id}" + ) + assert result[0].resource_tags == [] diff --git a/tests/providers/aws/services/guardduty/guardduty_eks_audit_log_enabled/guardduty_eks_audit_log_enabled_test.py b/tests/providers/aws/services/guardduty/guardduty_eks_audit_log_enabled/guardduty_eks_audit_log_enabled_test.py new file mode 100644 index 0000000000..7a373f15cc --- /dev/null +++ b/tests/providers/aws/services/guardduty/guardduty_eks_audit_log_enabled/guardduty_eks_audit_log_enabled_test.py @@ -0,0 +1,143 @@ +from unittest.mock import patch + +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_guardduty_eks_audit_log_enabled: + def test_no_detectors(self): + aws_provider = set_mocked_aws_provider() + + from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), patch( + "prowler.providers.aws.services.guardduty.guardduty_eks_audit_log_enabled.guardduty_eks_audit_log_enabled.guardduty_client", + new=GuardDuty(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.guardduty.guardduty_eks_audit_log_enabled.guardduty_eks_audit_log_enabled import ( + guardduty_eks_audit_log_enabled, + ) + + check = guardduty_eks_audit_log_enabled() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_detector_disabled(self): + guardduty_client = client("guardduty", region_name=AWS_REGION_EU_WEST_1) + + guardduty_client.create_detector(Enable=False) + + aws_provider = set_mocked_aws_provider() + + from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), patch( + "prowler.providers.aws.services.guardduty.guardduty_eks_audit_log_enabled.guardduty_eks_audit_log_enabled.guardduty_client", + new=GuardDuty(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.guardduty.guardduty_eks_audit_log_enabled.guardduty_eks_audit_log_enabled import ( + guardduty_eks_audit_log_enabled, + ) + + check = guardduty_eks_audit_log_enabled() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_detector_eks_audit_log_enabled(self): + guardduty_client = client("guardduty", region_name=AWS_REGION_EU_WEST_1) + + detector_id = guardduty_client.create_detector( + Enable=True, DataSources={"Kubernetes": {"AuditLogs": {"Enable": True}}} + )["DetectorId"] + + aws_provider = set_mocked_aws_provider() + + from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), patch( + "prowler.providers.aws.services.guardduty.guardduty_eks_audit_log_enabled.guardduty_eks_audit_log_enabled.guardduty_client", + new=GuardDuty(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.guardduty.guardduty_eks_audit_log_enabled.guardduty_eks_audit_log_enabled import ( + guardduty_eks_audit_log_enabled, + ) + + check = guardduty_eks_audit_log_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"GuardDuty detector {detector_id} has EKS Audit Log Monitoring enabled." + ) + assert result[0].resource_id == detector_id + assert result[0].region == AWS_REGION_EU_WEST_1 + assert ( + result[0].resource_arn + == f"arn:aws:guardduty:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:detector/{detector_id}" + ) + assert result[0].resource_tags == [] + + @mock_aws + def test_detector_eks_audit_log_disabled(self): + guardduty_client = client("guardduty", region_name=AWS_REGION_EU_WEST_1) + + detector_id = guardduty_client.create_detector( + Enable=True, DataSources={"Kubernetes": {"AuditLogs": {"Enable": False}}} + )["DetectorId"] + + aws_provider = set_mocked_aws_provider() + + from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), patch( + "prowler.providers.aws.services.guardduty.guardduty_eks_audit_log_enabled.guardduty_eks_audit_log_enabled.guardduty_client", + new=GuardDuty(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.guardduty.guardduty_eks_audit_log_enabled.guardduty_eks_audit_log_enabled import ( + guardduty_eks_audit_log_enabled, + ) + + check = guardduty_eks_audit_log_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"GuardDuty detector {detector_id} does not have EKS Audit Log Monitoring enabled." + ) + assert result[0].resource_id == detector_id + assert result[0].region == AWS_REGION_EU_WEST_1 + assert ( + result[0].resource_arn + == f"arn:aws:guardduty:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:detector/{detector_id}" + ) + assert result[0].resource_tags == [] diff --git a/tests/providers/aws/services/guardduty/guardduty_is_enabled/guardduty_is_enabled_test.py b/tests/providers/aws/services/guardduty/guardduty_is_enabled/guardduty_is_enabled_test.py index 3564ad3dbb..25e6aaa18d 100644 --- a/tests/providers/aws/services/guardduty/guardduty_is_enabled/guardduty_is_enabled_test.py +++ b/tests/providers/aws/services/guardduty/guardduty_is_enabled/guardduty_is_enabled_test.py @@ -1,171 +1,199 @@ -from unittest import mock -from uuid import uuid4 +from unittest.mock import patch + +from boto3 import client +from moto import mock_aws -from prowler.providers.aws.services.guardduty.guardduty_service import Detector from tests.providers.aws.utils import ( - AWS_ACCOUNT_ARN, AWS_ACCOUNT_NUMBER, AWS_REGION_EU_WEST_1, + set_mocked_aws_provider, ) -DETECTOR_ID = str(uuid4()) -DETECTOR_ARN = f"arn:aws:guardduty:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:detector/{DETECTOR_ID}" - class Test_guardduty_is_enabled: + @mock_aws def test_no_detectors(self): - guardduty_client = mock.MagicMock - guardduty_client.region = AWS_REGION_EU_WEST_1 - guardduty_client.detectors = [] - guardduty_client.detectors.append( - Detector( - id=AWS_ACCOUNT_NUMBER, - region=AWS_REGION_EU_WEST_1, - arn=AWS_ACCOUNT_ARN, - enabled_in_account=False, - ) - ) - guardduty_client.audited_account_arn = AWS_ACCOUNT_ARN - with mock.patch( - "prowler.providers.aws.services.guardduty.guardduty_service.GuardDuty", - guardduty_client, - ): + aws_provider = set_mocked_aws_provider() + + from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), patch( + "prowler.providers.aws.services.guardduty.guardduty_is_enabled.guardduty_is_enabled.guardduty_client", + new=GuardDuty(aws_provider), + ) as guardduty_client: from prowler.providers.aws.services.guardduty.guardduty_is_enabled.guardduty_is_enabled import ( guardduty_is_enabled, ) - check = guardduty_is_enabled() - result = check.execute() - assert len(result) == 1 - assert result[0].status == "FAIL" - assert result[0].status_extended == "GuardDuty is not enabled." - assert result[0].resource_id == AWS_ACCOUNT_NUMBER - assert result[0].resource_arn == AWS_ACCOUNT_ARN - assert result[0].region == AWS_REGION_EU_WEST_1 + guardduty_client.detectors = [] + check = guardduty_is_enabled() + results = check.execute() + assert len(results) == 0 + + @mock_aws def test_guardduty_enabled(self): - guardduty_client = mock.MagicMock - guardduty_client.detectors = [] - guardduty_client.detectors.append( - Detector( - id=DETECTOR_ID, - region=AWS_REGION_EU_WEST_1, - arn=DETECTOR_ARN, - status=True, - ) - ) - with mock.patch( - "prowler.providers.aws.services.guardduty.guardduty_service.GuardDuty", - guardduty_client, + guardduty_client = client("guardduty", region_name=AWS_REGION_EU_WEST_1) + + detector_id = guardduty_client.create_detector(Enable=True)["DetectorId"] + + aws_provider = set_mocked_aws_provider() + + from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), patch( + "prowler.providers.aws.services.guardduty.guardduty_is_enabled.guardduty_is_enabled.guardduty_client", + new=GuardDuty(aws_provider), ): from prowler.providers.aws.services.guardduty.guardduty_is_enabled.guardduty_is_enabled import ( guardduty_is_enabled, ) check = guardduty_is_enabled() - result = check.execute() - assert len(result) == 1 - assert result[0].status == "PASS" - assert ( - result[0].status_extended - == f"GuardDuty detector {DETECTOR_ID} enabled." - ) - assert result[0].resource_id == DETECTOR_ID - assert result[0].resource_arn == DETECTOR_ARN - assert result[0].region == AWS_REGION_EU_WEST_1 + results = check.execute() + assert len(results) == 29 + for result in results: + if result.region == AWS_REGION_EU_WEST_1: + assert result.status == "PASS" + assert ( + result.status_extended + == f"GuardDuty detector {result.resource_id} enabled." + ) + assert result.resource_id == detector_id + assert ( + result.resource_arn + == f"arn:aws:guardduty:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:detector/{detector_id}" + ) + assert result.resource_tags == [] + @mock_aws def test_guardduty_configured_but_suspended(self): - guardduty_client = mock.MagicMock - guardduty_client.region = AWS_REGION_EU_WEST_1 - guardduty_client.detectors = [] - guardduty_client.detectors.append( - Detector( - id=DETECTOR_ID, - arn=DETECTOR_ARN, - region=AWS_REGION_EU_WEST_1, - status=False, - ) - ) - with mock.patch( - "prowler.providers.aws.services.guardduty.guardduty_service.GuardDuty", - guardduty_client, - ): + guardduty_client = client("guardduty", region_name=AWS_REGION_EU_WEST_1) + + detector_id = guardduty_client.create_detector(Enable=False)["DetectorId"] + + aws_provider = set_mocked_aws_provider() + + from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), patch( + "prowler.providers.aws.services.guardduty.guardduty_is_enabled.guardduty_is_enabled.guardduty_client", + new=GuardDuty(aws_provider), + ) as mock_guardduty_client: from prowler.providers.aws.services.guardduty.guardduty_is_enabled.guardduty_is_enabled import ( guardduty_is_enabled, ) - check = guardduty_is_enabled() - result = check.execute() - assert len(result) == 1 - assert result[0].status == "FAIL" - assert ( - result[0].status_extended - == f"GuardDuty detector {DETECTOR_ID} configured but suspended." - ) - assert result[0].resource_id == DETECTOR_ID - assert result[0].resource_arn == DETECTOR_ARN - assert result[0].region == AWS_REGION_EU_WEST_1 + for detector in mock_guardduty_client.detectors: + if detector.region == AWS_REGION_EU_WEST_1: + detector.status = False + check = guardduty_is_enabled() + results = check.execute() + assert len(results) == 29 + for result in results: + if result.region == AWS_REGION_EU_WEST_1: + assert result.status == "FAIL" + assert ( + result.status_extended + == f"GuardDuty detector {result.resource_id} configured but suspended." + ) + assert result.resource_id == detector_id + assert ( + result.resource_arn + == f"arn:aws:guardduty:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:detector/{detector_id}" + ) + assert result.resource_tags == [] + + @mock_aws def test_guardduty_not_configured(self): - guardduty_client = mock.MagicMock - guardduty_client.detectors = [] - guardduty_client.region = AWS_REGION_EU_WEST_1 - guardduty_client.detectors.append( - Detector( - id=DETECTOR_ID, - arn=DETECTOR_ARN, - region=AWS_REGION_EU_WEST_1, - ) - ) - with mock.patch( - "prowler.providers.aws.services.guardduty.guardduty_service.GuardDuty", - guardduty_client, - ): + guardduty_client = client("guardduty", region_name=AWS_REGION_EU_WEST_1) + + detector_id = guardduty_client.create_detector(Enable=False)["DetectorId"] + + aws_provider = set_mocked_aws_provider() + + from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), patch( + "prowler.providers.aws.services.guardduty.guardduty_is_enabled.guardduty_is_enabled.guardduty_client", + new=GuardDuty(aws_provider), + ) as mock_guardduty_client: from prowler.providers.aws.services.guardduty.guardduty_is_enabled.guardduty_is_enabled import ( guardduty_is_enabled, ) - check = guardduty_is_enabled() - result = check.execute() - assert len(result) == 1 - assert result[0].status == "FAIL" - assert ( - result[0].status_extended - == f"GuardDuty detector {DETECTOR_ID} not configured." - ) - assert result[0].resource_id == DETECTOR_ID - assert result[0].resource_arn == DETECTOR_ARN - assert result[0].region == AWS_REGION_EU_WEST_1 + for detector in mock_guardduty_client.detectors: + if detector.region == AWS_REGION_EU_WEST_1: + detector.status = None + check = guardduty_is_enabled() + results = check.execute() + assert len(results) == 29 + for result in results: + if result.region == AWS_REGION_EU_WEST_1: + assert result.status == "FAIL" + assert ( + result.status_extended + == f"GuardDuty detector {result.resource_id} not configured." + ) + assert result.resource_id == detector_id + assert ( + result.resource_arn + == f"arn:aws:guardduty:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:detector/{detector_id}" + ) + assert result.resource_tags == [] + + @mock_aws def test_guardduty_not_configured_muted(self): - guardduty_client = mock.MagicMock - guardduty_client.audit_config = {"mute_non_default_regions": True} - guardduty_client.region = "eu-south-2" - guardduty_client.detectors = [] - guardduty_client.detectors.append( - Detector( - id=DETECTOR_ID, - arn=DETECTOR_ARN, - region=AWS_REGION_EU_WEST_1, - ) - ) - with mock.patch( - "prowler.providers.aws.services.guardduty.guardduty_service.GuardDuty", - guardduty_client, - ): + guardduty_client = client("guardduty", region_name=AWS_REGION_EU_WEST_1) + + detector_id = guardduty_client.create_detector(Enable=False)["DetectorId"] + + aws_provider = set_mocked_aws_provider() + + from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), patch( + "prowler.providers.aws.services.guardduty.guardduty_is_enabled.guardduty_is_enabled.guardduty_client", + new=GuardDuty(aws_provider), + ) as mock_guardduty_client: from prowler.providers.aws.services.guardduty.guardduty_is_enabled.guardduty_is_enabled import ( guardduty_is_enabled, ) + mock_guardduty_client.audit_config = {"mute_non_default_regions": True} + check = guardduty_is_enabled() - result = check.execute() - assert len(result) == 1 - assert result[0].status == "FAIL" - assert result[0].muted - assert ( - result[0].status_extended - == f"GuardDuty detector {DETECTOR_ID} not configured." - ) - assert result[0].resource_id == DETECTOR_ID - assert result[0].resource_arn == DETECTOR_ARN - assert result[0].region == AWS_REGION_EU_WEST_1 + results = check.execute() + assert len(results) == 29 + for result in results: + if result.region == AWS_REGION_EU_WEST_1: + assert result.status == "FAIL" + assert result.muted + assert ( + result.status_extended + == f"GuardDuty detector {result.resource_id} not configured." + ) + assert result.resource_id == detector_id + assert ( + result.resource_arn + == f"arn:aws:guardduty:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:detector/{detector_id}" + ) + assert result.resource_tags == [] + assert result.muted diff --git a/tests/providers/aws/services/guardduty/guardduty_lambda_protection_enabled/guardduty_lambda_protection_enabled_test.py b/tests/providers/aws/services/guardduty/guardduty_lambda_protection_enabled/guardduty_lambda_protection_enabled_test.py new file mode 100644 index 0000000000..78eb2cc9ad --- /dev/null +++ b/tests/providers/aws/services/guardduty/guardduty_lambda_protection_enabled/guardduty_lambda_protection_enabled_test.py @@ -0,0 +1,148 @@ +from unittest.mock import patch + +import botocore +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, +) + +orig = botocore.client.BaseClient._make_api_call + + +class Test_guardduty_lambda_protection_enabled: + def test_no_detectors(self): + aws_provider = set_mocked_aws_provider() + + from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), patch( + "prowler.providers.aws.services.guardduty.guardduty_lambda_protection_enabled.guardduty_lambda_protection_enabled.guardduty_client", + new=GuardDuty(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.guardduty.guardduty_lambda_protection_enabled.guardduty_lambda_protection_enabled import ( + guardduty_lambda_protection_enabled, + ) + + check = guardduty_lambda_protection_enabled() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_detector_disabled(self): + guardduty_client = client("guardduty", region_name=AWS_REGION_EU_WEST_1) + + guardduty_client.create_detector(Enable=False) + + aws_provider = set_mocked_aws_provider() + + from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), patch( + "prowler.providers.aws.services.guardduty.guardduty_lambda_protection_enabled.guardduty_lambda_protection_enabled.guardduty_client", + new=GuardDuty(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.guardduty.guardduty_lambda_protection_enabled.guardduty_lambda_protection_enabled import ( + guardduty_lambda_protection_enabled, + ) + + check = guardduty_lambda_protection_enabled() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_detector_lambda_protection_enabled(self): + guardduty_client = client("guardduty", region_name=AWS_REGION_EU_WEST_1) + + detector_id = guardduty_client.create_detector( + Enable=True, + Features=[{"Name": "LAMBDA_NETWORK_LOGS", "Status": "ENABLED"}], + )["DetectorId"] + + aws_provider = set_mocked_aws_provider() + + from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), patch( + "prowler.providers.aws.services.guardduty.guardduty_lambda_protection_enabled.guardduty_lambda_protection_enabled.guardduty_client", + new=GuardDuty(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.guardduty.guardduty_lambda_protection_enabled.guardduty_lambda_protection_enabled import ( + guardduty_lambda_protection_enabled, + ) + + check = guardduty_lambda_protection_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"GuardDuty detector {detector_id} has Lambda Protection enabled." + ) + assert result[0].resource_id == detector_id + assert result[0].region == AWS_REGION_EU_WEST_1 + assert ( + result[0].resource_arn + == f"arn:aws:guardduty:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:detector/{detector_id}" + ) + assert result[0].resource_tags == [] + + @mock_aws + def test_detector_lambda_protection_disabled(self): + guardduty_client = client("guardduty", region_name=AWS_REGION_EU_WEST_1) + + detector_id = guardduty_client.create_detector( + Enable=True, + Features=[{"Name": "LAMBDA_NETWORK_LOGS", "Status": "DISABLED"}], + )["DetectorId"] + + aws_provider = set_mocked_aws_provider() + + from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), patch( + "prowler.providers.aws.services.guardduty.guardduty_lambda_protection_enabled.guardduty_lambda_protection_enabled.guardduty_client", + new=GuardDuty(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.guardduty.guardduty_lambda_protection_enabled.guardduty_lambda_protection_enabled import ( + guardduty_lambda_protection_enabled, + ) + + check = guardduty_lambda_protection_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"GuardDuty detector {detector_id} does not have Lambda Protection enabled." + ) + assert result[0].resource_id == detector_id + assert result[0].region == AWS_REGION_EU_WEST_1 + assert ( + result[0].resource_arn + == f"arn:aws:guardduty:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:detector/{detector_id}" + ) + assert result[0].resource_tags == [] diff --git a/tests/providers/aws/services/guardduty/guardduty_no_high_severity_findings/guardduty_no_high_severity_findings_test.py b/tests/providers/aws/services/guardduty/guardduty_no_high_severity_findings/guardduty_no_high_severity_findings_test.py index 402cf57c79..b068b89225 100644 --- a/tests/providers/aws/services/guardduty/guardduty_no_high_severity_findings/guardduty_no_high_severity_findings_test.py +++ b/tests/providers/aws/services/guardduty/guardduty_no_high_severity_findings/guardduty_no_high_severity_findings_test.py @@ -1,21 +1,43 @@ -from re import search -from unittest import mock -from uuid import uuid4 +from unittest.mock import patch -from prowler.providers.aws.services.guardduty.guardduty_service import Detector -from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER, AWS_REGION_EU_WEST_1 +import botocore +from boto3 import client +from moto import mock_aws -DETECTOR_ID = str(uuid4()) -DETECTOR_ARN = f"arn:aws:guardduty:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:detector/{DETECTOR_ID}" +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_EU_WEST_1, + set_mocked_aws_provider, +) + +orig = botocore.client.BaseClient._make_api_call + + +def mock_make_api_call(self, operation_name, kwarg): + if operation_name == "ListFindings": + return { + "FindingIds": [ + "f1", + "f2", + ] + } + # If we don't want to patch the API call + return orig(self, operation_name, kwarg) class Test_guardduty_no_high_severity_findings: + @mock_aws def test_no_detectors(self): - guardduty_client = mock.MagicMock - guardduty_client.detectors = [] - with mock.patch( - "prowler.providers.aws.services.guardduty.guardduty_service.GuardDuty", - guardduty_client, + aws_provider = set_mocked_aws_provider() + + from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), patch( + "prowler.providers.aws.services.guardduty.guardduty_no_high_severity_findings.guardduty_no_high_severity_findings.guardduty_client", + new=GuardDuty(aws_provider), ): from prowler.providers.aws.services.guardduty.guardduty_no_high_severity_findings.guardduty_no_high_severity_findings import ( guardduty_no_high_severity_findings, @@ -25,19 +47,22 @@ class Test_guardduty_no_high_severity_findings: result = check.execute() assert len(result) == 0 + @mock_aws def test_no_high_findings(self): - guardduty_client = mock.MagicMock - guardduty_client.detectors = [] - guardduty_client.detectors.append( - Detector( - id=DETECTOR_ID, - arn=DETECTOR_ARN, - region=AWS_REGION_EU_WEST_1, - ) - ) - with mock.patch( - "prowler.providers.aws.services.guardduty.guardduty_service.GuardDuty", - guardduty_client, + guardduty_client = client("guardduty", region_name=AWS_REGION_EU_WEST_1) + + detector_id = guardduty_client.create_detector(Enable=True)["DetectorId"] + + aws_provider = set_mocked_aws_provider() + + from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), patch( + "prowler.providers.aws.services.guardduty.guardduty_no_high_severity_findings.guardduty_no_high_severity_findings.guardduty_client", + new=GuardDuty(aws_provider), ): from prowler.providers.aws.services.guardduty.guardduty_no_high_severity_findings.guardduty_no_high_severity_findings import ( guardduty_no_high_severity_findings, @@ -47,51 +72,35 @@ class Test_guardduty_no_high_severity_findings: result = check.execute() assert len(result) == 1 assert result[0].status == "PASS" - assert search( - "does not have high severity findings.", result[0].status_extended + assert ( + result[0].status_extended + == f"GuardDuty detector {detector_id} does not have high severity findings." ) - assert result[0].resource_id == DETECTOR_ID - assert result[0].resource_arn == DETECTOR_ARN + assert result[0].resource_id == detector_id assert result[0].region == AWS_REGION_EU_WEST_1 - - def test_not_enabled_account_detector(self): - guardduty_client = mock.MagicMock - guardduty_client.detectors = [] - guardduty_client.detectors.append( - Detector( - id=AWS_ACCOUNT_NUMBER, - arn=DETECTOR_ARN, - region=AWS_REGION_EU_WEST_1, - enabled_in_account=False, - ) - ) - with mock.patch( - "prowler.providers.aws.services.guardduty.guardduty_service.GuardDuty", - guardduty_client, - ): - from prowler.providers.aws.services.guardduty.guardduty_no_high_severity_findings.guardduty_no_high_severity_findings import ( - guardduty_no_high_severity_findings, + assert ( + result[0].resource_arn + == f"arn:aws:guardduty:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:detector/{detector_id}" ) + assert result[0].resource_tags == [] - check = guardduty_no_high_severity_findings() - result = check.execute() - assert len(result) == 0 - + @patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) + @mock_aws def test_high_findings(self): - guardduty_client = mock.MagicMock - guardduty_client.detectors = [] - guardduty_client.detectors.append( - Detector( - id=DETECTOR_ID, - region=AWS_REGION_EU_WEST_1, - arn=DETECTOR_ARN, - status=False, - findings=[str(uuid4())], - ) - ) - with mock.patch( - "prowler.providers.aws.services.guardduty.guardduty_service.GuardDuty", - guardduty_client, + guardduty_client = client("guardduty", region_name=AWS_REGION_EU_WEST_1) + + detector_id = guardduty_client.create_detector(Enable=True)["DetectorId"] + + aws_provider = set_mocked_aws_provider() + + from prowler.providers.aws.services.guardduty.guardduty_service import GuardDuty + + with patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), patch( + "prowler.providers.aws.services.guardduty.guardduty_no_high_severity_findings.guardduty_no_high_severity_findings.guardduty_client", + new=GuardDuty(aws_provider), ): from prowler.providers.aws.services.guardduty.guardduty_no_high_severity_findings.guardduty_no_high_severity_findings import ( guardduty_no_high_severity_findings, @@ -101,7 +110,14 @@ class Test_guardduty_no_high_severity_findings: result = check.execute() assert len(result) == 1 assert result[0].status == "FAIL" - assert search("has 1 high severity findings", result[0].status_extended) - assert result[0].resource_id == DETECTOR_ID - assert result[0].resource_arn == DETECTOR_ARN + assert ( + result[0].status_extended + == f"GuardDuty detector {detector_id} has 2 high severity findings." + ) + assert result[0].resource_id == detector_id assert result[0].region == AWS_REGION_EU_WEST_1 + assert ( + result[0].resource_arn + == f"arn:aws:guardduty:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:detector/{detector_id}" + ) + assert result[0].resource_tags == [] diff --git a/tests/providers/aws/services/guardduty/guardduty_service_test.py b/tests/providers/aws/services/guardduty/guardduty_service_test.py index 9ed2ac0049..532020f083 100644 --- a/tests/providers/aws/services/guardduty/guardduty_service_test.py +++ b/tests/providers/aws/services/guardduty/guardduty_service_test.py @@ -111,7 +111,11 @@ class Test_GuardDuty_Service: guardduty_client = client("guardduty", region_name=AWS_REGION_EU_WEST_1) response = guardduty_client.create_detector( Enable=True, - DataSources={"S3Logs": {"Enable": True}}, + DataSources={ + "S3Logs": {"Enable": True}, + "Kubernetes": {"AuditLogs": {"Enable": True}}, + }, + Features=[{"Name": "LAMBDA_NETWORK_LOGS", "Status": "ENABLED"}], ) aws_provider = set_mocked_aws_provider() @@ -129,6 +133,9 @@ class Test_GuardDuty_Service: assert guardduty.detectors[0].administrator_account == "123456789013" assert guardduty.detectors[0].s3_protection assert not guardduty.detectors[0].rds_protection + assert guardduty.detectors[0].eks_audit_log_protection + assert guardduty.detectors[0].lambda_protection + assert not guardduty.detectors[0].ec2_malware_protection assert guardduty.detectors[0].region == AWS_REGION_EU_WEST_1 assert guardduty.detectors[0].tags == [{"test": "test"}] diff --git a/tests/providers/aws/services/iam/iam_check_saml_providers_sts/iam_check_saml_providers_sts_test.py b/tests/providers/aws/services/iam/iam_check_saml_providers_sts/iam_check_saml_providers_sts_test.py index 2200d75a9e..a7dd4871a9 100644 --- a/tests/providers/aws/services/iam/iam_check_saml_providers_sts/iam_check_saml_providers_sts_test.py +++ b/tests/providers/aws/services/iam/iam_check_saml_providers_sts/iam_check_saml_providers_sts_test.py @@ -1,12 +1,31 @@ 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 +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + +orig = botocore.client.BaseClient._make_api_call + + +def mock_make_api_call(self, operation_name, kwarg): + if operation_name == "ListSAMLProviderTags": + return { + "Tags": [ + {"Key": "Name", "Value": "test"}, + {"Key": "Owner", "Value": "test"}, + ] + } + return orig(self, operation_name, kwarg) class Test_iam_check_saml_providers_sts: + @mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) @mock_aws def test_iam_check_saml_providers_sts(self): iam_client = client("iam") @@ -63,3 +82,46 @@ nTTxU4a7x1naFxzYXK1iQ1vMARKMjDb19QEJIEJKZlDK4uS7yMlf1nFS check = iam_check_saml_providers_sts() result = check.execute() assert result[0].status == "PASS" + assert result[0].resource_id == saml_provider_name + assert ( + result[0].resource_arn + == f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:saml-provider/{saml_provider_name}" + ) + assert result[0].resource_tags == [ + {"Key": "Name", "Value": "test"}, + {"Key": "Owner", "Value": "test"}, + ] + assert result[0].region == AWS_REGION_US_EAST_1 + assert ( + result[0].status_extended + == f"SAML Provider {saml_provider_name} has been found." + ) + + @mock_aws + def test_iam_check_saml_providers_sts_no_saml_providers(self): + from prowler.providers.aws.services.iam.iam_service import IAM + + 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.iam.iam_check_saml_providers_sts.iam_check_saml_providers_sts.iam_client", + new=IAM(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.iam.iam_check_saml_providers_sts.iam_check_saml_providers_sts import ( + iam_check_saml_providers_sts, + ) + + check = iam_check_saml_providers_sts() + result = check.execute() + assert result[0].status == "FAIL" + assert result[0].resource_id == AWS_ACCOUNT_NUMBER + assert ( + result[0].resource_arn == f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root" + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].status_extended == "No SAML Providers found." diff --git a/tests/providers/aws/services/iam/iam_no_expired_server_certificates_stored/iam_no_expired_server_certificates_stored_test.py b/tests/providers/aws/services/iam/iam_no_expired_server_certificates_stored/iam_no_expired_server_certificates_stored_test.py index d160435e41..70fd1a5f06 100644 --- a/tests/providers/aws/services/iam/iam_no_expired_server_certificates_stored/iam_no_expired_server_certificates_stored_test.py +++ b/tests/providers/aws/services/iam/iam_no_expired_server_certificates_stored/iam_no_expired_server_certificates_stored_test.py @@ -1,11 +1,23 @@ from re import search 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 +# Original botocore _make_api_call function +orig = botocore.client.BaseClient._make_api_call + + +# Mocked botocore _make_api_call function +def mock_make_api_call(self, operation_name, kwarg): + if operation_name == "ListServerCertificateTags": + return {"Tags": [{"Key": "Name", "Value": "certname"}]} + # If we don't want to patch the API call + return orig(self, operation_name, kwarg) + class Test_iam_no_expired_server_certificates_stored_test: @mock_aws @@ -31,6 +43,7 @@ class Test_iam_no_expired_server_certificates_stored_test: assert len(result) == 0 + @mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) @mock_aws def test_expired_certificate(self): iam_client = client("iam") @@ -39,6 +52,7 @@ class Test_iam_no_expired_server_certificates_stored_test: ServerCertificateName="certname", CertificateBody="certbody", PrivateKey="privatekey", + Tags=[{"Key": "Name", "Value": "certname"}], )["ServerCertificateMetadata"] from prowler.providers.aws.services.iam.iam_service import IAM @@ -68,3 +82,5 @@ class Test_iam_no_expired_server_certificates_stored_test: ) assert result[0].resource_id == cert["ServerCertificateId"] assert result[0].resource_arn == cert["Arn"] + assert result[0].resource_tags == [{"Key": "Name", "Value": "certname"}] + assert result[0].region == AWS_REGION_US_EAST_1 diff --git a/tests/providers/aws/services/iam/iam_policy_attached_only_to_group_or_roles/iam_policy_attached_only_to_group_or_roles_test.py b/tests/providers/aws/services/iam/iam_policy_attached_only_to_group_or_roles/iam_policy_attached_only_to_group_or_roles_test.py index c7e73d54de..1ca291c135 100644 --- a/tests/providers/aws/services/iam/iam_policy_attached_only_to_group_or_roles/iam_policy_attached_only_to_group_or_roles_test.py +++ b/tests/providers/aws/services/iam/iam_policy_attached_only_to_group_or_roles/iam_policy_attached_only_to_group_or_roles_test.py @@ -133,6 +133,9 @@ class Test_iam_policy_attached_only_to_group_or_roles: UserName=user, PolicyName=policyName, PolicyDocument=dumps(policyDocument) ) + # Tag the user + iam_client.tag_user(UserName=user, Tags=[{"Key": "tag1", "Value": "value1"}]) + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) from prowler.providers.aws.services.iam.iam_service import IAM @@ -160,6 +163,7 @@ class Test_iam_policy_attached_only_to_group_or_roles: result[0].resource_arn == f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:user/{user}" ) + assert result[0].resource_tags == [{"Key": "tag1", "Value": "value1"}] @mock_aws def test_iam_user_no_policies(self): @@ -168,6 +172,9 @@ class Test_iam_policy_attached_only_to_group_or_roles: user = "test_no_policies" iam_client.create_user(UserName=user) + # Tag the user + iam_client.tag_user(UserName=user, Tags=[{"Key": "tag1", "Value": "value1"}]) + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) from prowler.providers.aws.services.iam.iam_service import IAM @@ -195,3 +202,4 @@ class Test_iam_policy_attached_only_to_group_or_roles: result[0].resource_arn == f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:user/{user}" ) + assert result[0].resource_tags == [{"Key": "tag1", "Value": "value1"}] 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 7b20c6ec2e..57c697062f 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 @@ -15,6 +15,8 @@ class Test_iam_rotate_access_key_90_days_test: user = "test-user" arn = iam_client.create_user(UserName=user)["User"]["Arn"] + iam_client.tag_user(UserName=user, Tags=[{"Key": "test-tag", "Value": "test"}]) + from prowler.providers.aws.services.iam.iam_service import IAM aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) @@ -43,6 +45,7 @@ class Test_iam_rotate_access_key_90_days_test: assert result[0].resource_id == user 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"}] @mock_aws def test_user_access_key_1_not_rotated(self): @@ -53,6 +56,8 @@ class Test_iam_rotate_access_key_90_days_test: user = "test-user" arn = iam_client.create_user(UserName=user)["User"]["Arn"] + iam_client.tag_user(UserName=user, Tags=[{"Key": "test-tag", "Value": "test"}]) + from prowler.providers.aws.services.iam.iam_service import IAM aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) @@ -84,6 +89,7 @@ class Test_iam_rotate_access_key_90_days_test: assert result[0].resource_id == user 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"}] @mock_aws def test_user_access_key_2_not_rotated(self): @@ -94,6 +100,8 @@ class Test_iam_rotate_access_key_90_days_test: user = "test-user" arn = iam_client.create_user(UserName=user)["User"]["Arn"] + iam_client.tag_user(UserName=user, Tags=[{"Key": "test-tag", "Value": "test"}]) + from prowler.providers.aws.services.iam.iam_service import IAM aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) @@ -125,6 +133,7 @@ class Test_iam_rotate_access_key_90_days_test: assert result[0].resource_id == user 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"}] @mock_aws def test_user_both_access_keys_not_rotated(self): @@ -135,6 +144,8 @@ class Test_iam_rotate_access_key_90_days_test: user = "test-user" arn = iam_client.create_user(UserName=user)["User"]["Arn"] + iam_client.tag_user(UserName=user, Tags=[{"Key": "test-tag", "Value": "test"}]) + from prowler.providers.aws.services.iam.iam_service import IAM aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) @@ -171,6 +182,7 @@ class Test_iam_rotate_access_key_90_days_test: assert result[0].resource_id == user 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"}] assert result[1].status == "FAIL" assert ( result[1].status_extended @@ -179,6 +191,7 @@ class Test_iam_rotate_access_key_90_days_test: assert result[1].resource_id == user 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"}] @mock_aws def test_user_both_access_keys_rotated(self): @@ -189,6 +202,8 @@ class Test_iam_rotate_access_key_90_days_test: user = "test-user" arn = iam_client.create_user(UserName=user)["User"]["Arn"] + iam_client.tag_user(UserName=user, Tags=[{"Key": "test-tag", "Value": "test"}]) + from prowler.providers.aws.services.iam.iam_service import IAM aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) @@ -226,3 +241,4 @@ class Test_iam_rotate_access_key_90_days_test: assert result[0].resource_id == user 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"}] diff --git a/tests/providers/aws/services/iam/iam_service_test.py b/tests/providers/aws/services/iam/iam_service_test.py index de0a2f1343..6367df390e 100644 --- a/tests/providers/aws/services/iam/iam_service_test.py +++ b/tests/providers/aws/services/iam/iam_service_test.py @@ -79,7 +79,6 @@ def mock_make_api_call(self, operation_name, kwargs): # Patch every AWS call using Boto3 @patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) class Test_IAM_Service: - # Test IAM Client @mock_aws def test_get_client(self): @@ -806,16 +805,18 @@ nTTxU4a7x1naFxzYXK1iQ1vMARKMjDb19QEJIEJKZlDK4uS7yMlf1nFS """ saml_provider_name = "test" - iam_client.create_saml_provider( + saml_arn = iam_client.create_saml_provider( SAMLMetadataDocument=xml_template, Name=saml_provider_name - ) + )["SAMLProviderArn"] # IAM client for this test class aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) iam = IAM(aws_provider) assert len(iam.saml_providers) == 1 - assert iam.saml_providers[0]["Arn"].split("/")[1] == saml_provider_name + assert saml_arn in iam.saml_providers + assert iam.saml_providers[saml_arn].name == saml_provider_name + assert iam.saml_providers[saml_arn].arn == saml_arn # Test IAM User Inline Policy @mock_aws diff --git a/tests/providers/aws/services/iam/iam_user_accesskey_unused/iam_user_accesskey_unused_test.py b/tests/providers/aws/services/iam/iam_user_accesskey_unused/iam_user_accesskey_unused_test.py index 0c96a4e2ec..76510303c5 100644 --- a/tests/providers/aws/services/iam/iam_user_accesskey_unused/iam_user_accesskey_unused_test.py +++ b/tests/providers/aws/services/iam/iam_user_accesskey_unused/iam_user_accesskey_unused_test.py @@ -18,6 +18,8 @@ class Test_iam_user_accesskey_unused_test: user = "test-user" arn = iam_client.create_user(UserName=user)["User"]["Arn"] + iam_client.tag_user(UserName=user, Tags=[{"Key": "Name", "Value": "test-user"}]) + from prowler.providers.aws.services.iam.iam_service import IAM aws_provider = set_mocked_aws_provider( @@ -54,6 +56,9 @@ class Test_iam_user_accesskey_unused_test: assert result[0].resource_id == user assert result[0].resource_arn == arn assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [ + {"Key": "Name", "Value": "test-user"} + ] @mock_aws def test_user_access_key_1_not_used(self): @@ -64,6 +69,8 @@ class Test_iam_user_accesskey_unused_test: user = "test-user" arn = iam_client.create_user(UserName=user)["User"]["Arn"] + iam_client.tag_user(UserName=user, Tags=[{"Key": "Name", "Value": "test-user"}]) + from prowler.providers.aws.services.iam.iam_service import IAM aws_provider = set_mocked_aws_provider( @@ -98,6 +105,9 @@ class Test_iam_user_accesskey_unused_test: assert result[0].resource_id == user + "/AccessKey1" assert result[0].resource_arn == arn assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [ + {"Key": "Name", "Value": "test-user"} + ] @mock_aws def test_user_access_key_2_not_used(self): @@ -108,6 +118,8 @@ class Test_iam_user_accesskey_unused_test: user = "test-user" arn = iam_client.create_user(UserName=user)["User"]["Arn"] + iam_client.tag_user(UserName=user, Tags=[{"Key": "Name", "Value": "test-user"}]) + from prowler.providers.aws.services.iam.iam_service import IAM aws_provider = set_mocked_aws_provider( @@ -142,6 +154,9 @@ class Test_iam_user_accesskey_unused_test: assert result[0].resource_id == user + "/AccessKey2" assert result[0].resource_arn == arn assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [ + {"Key": "Name", "Value": "test-user"} + ] @mock_aws def test_user_both_access_keys_not_used(self): @@ -152,6 +167,8 @@ class Test_iam_user_accesskey_unused_test: user = "test-user" arn = iam_client.create_user(UserName=user)["User"]["Arn"] + iam_client.tag_user(UserName=user, Tags=[{"Key": "Name", "Value": "test-user"}]) + from prowler.providers.aws.services.iam.iam_service import IAM aws_provider = set_mocked_aws_provider( @@ -191,6 +208,9 @@ class Test_iam_user_accesskey_unused_test: assert result[0].resource_id == user + "/AccessKey1" assert result[0].resource_arn == arn assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [ + {"Key": "Name", "Value": "test-user"} + ] assert result[1].status == "FAIL" assert ( @@ -210,6 +230,8 @@ class Test_iam_user_accesskey_unused_test: user = "test-user" arn = iam_client.create_user(UserName=user)["User"]["Arn"] + iam_client.tag_user(UserName=user, Tags=[{"Key": "Name", "Value": "test-user"}]) + from prowler.providers.aws.services.iam.iam_service import IAM aws_provider = set_mocked_aws_provider( @@ -249,3 +271,6 @@ class Test_iam_user_accesskey_unused_test: assert result[0].resource_id == user assert result[0].resource_arn == arn assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [ + {"Key": "Name", "Value": "test-user"} + ] diff --git a/tests/providers/aws/services/iam/iam_user_mfa_enabled_console_access/iam_user_mfa_enabled_console_access_test.py b/tests/providers/aws/services/iam/iam_user_mfa_enabled_console_access/iam_user_mfa_enabled_console_access_test.py index f0a66723c4..92588f9d38 100644 --- a/tests/providers/aws/services/iam/iam_user_mfa_enabled_console_access/iam_user_mfa_enabled_console_access_test.py +++ b/tests/providers/aws/services/iam/iam_user_mfa_enabled_console_access/iam_user_mfa_enabled_console_access_test.py @@ -70,6 +70,8 @@ class Test_iam_user_mfa_enabled_console_access_test: user = "test-user" arn = iam_client.create_user(UserName=user)["User"]["Arn"] + iam_client.tag_user(UserName=user, Tags=[{"Key": "Name", "Value": "test-user"}]) + from prowler.providers.aws.services.iam.iam_service import IAM aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) @@ -96,6 +98,7 @@ class Test_iam_user_mfa_enabled_console_access_test: ) assert result[0].resource_id == user assert result[0].resource_arn == arn + assert result[0].resource_tags == [{"Key": "Name", "Value": "test-user"}] @mock_aws def test_user_password_console_and_mfa_enabled(self): @@ -103,6 +106,8 @@ class Test_iam_user_mfa_enabled_console_access_test: user = "test-user" arn = iam_client.create_user(UserName=user)["User"]["Arn"] + iam_client.tag_user(UserName=user, Tags=[{"Key": "Name", "Value": "test-user"}]) + from prowler.providers.aws.services.iam.iam_service import IAM aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) @@ -130,6 +135,7 @@ class Test_iam_user_mfa_enabled_console_access_test: ) assert result[0].resource_id == user assert result[0].resource_arn == arn + assert result[0].resource_tags == [{"Key": "Name", "Value": "test-user"}] @mock_aws def test_user_password_console_enabled_and_mfa_not_enabled(self): @@ -137,6 +143,8 @@ class Test_iam_user_mfa_enabled_console_access_test: user = "test-user" arn = iam_client.create_user(UserName=user)["User"]["Arn"] + iam_client.tag_user(UserName=user, Tags=[{"Key": "Name", "Value": "test-user"}]) + from prowler.providers.aws.services.iam.iam_service import IAM aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) @@ -164,3 +172,4 @@ class Test_iam_user_mfa_enabled_console_access_test: ) assert result[0].resource_id == user assert result[0].resource_arn == arn + assert result[0].resource_tags == [{"Key": "Name", "Value": "test-user"}] diff --git a/tests/providers/aws/services/iam/iam_user_no_setup_initial_access_key/iam_user_no_setup_initial_access_key_test.py b/tests/providers/aws/services/iam/iam_user_no_setup_initial_access_key/iam_user_no_setup_initial_access_key_test.py index bcf74ffeab..f22a3d6292 100644 --- a/tests/providers/aws/services/iam/iam_user_no_setup_initial_access_key/iam_user_no_setup_initial_access_key_test.py +++ b/tests/providers/aws/services/iam/iam_user_no_setup_initial_access_key/iam_user_no_setup_initial_access_key_test.py @@ -2,22 +2,10 @@ from csv import DictReader from re import search from unittest import mock -from moto import mock_aws - from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider -AWS_ACCOUNT_NUMBER = "123456789012" - class Test_iam_user_no_setup_initial_access_key_test: - from tests.providers.aws.utils import ( - AWS_ACCOUNT_ARN, - AWS_ACCOUNT_NUMBER, - AWS_REGION_US_EAST_1, - set_mocked_aws_provider, - ) - - @mock_aws def test_setup_access_key_1_fail(self): raw_credential_report = r"""user,arn,user_creation_time,password_enabled,password_last_used,password_last_changed,password_next_rotation,mfa_active,access_key_1_active,access_key_1_last_rotated,access_key_1_last_used_date,access_key_1_last_used_region,access_key_1_last_used_service,access_key_2_active,access_key_2_last_rotated,access_key_2_last_used_date,access_key_2_last_used_region,access_key_2_last_used_service,cert_1_active,cert_1_last_rotated,cert_2_active,cert_2_last_rotated test_false_access_key_1,arn:aws:iam::123456789012:test_false_access_key_1,2022-04-17T14:59:38+00:00,true,no_information,not_supported,not_supported,false,true,N/A,N/A,N/A,N/A,false,N/A,N/A,N/A,N/A,false,N/A,false,N/A""" @@ -40,13 +28,28 @@ test_false_access_key_1,arn:aws:iam::123456789012:test_false_access_key_1,2022-0 ) service_client.credential_report = credential_list + service_client.users = [ + mock.MagicMock( + name="test_false_access_key_1", + arn="arn:aws:iam::123456789012:test_false_access_key_1", + tags=[{"Key": "Name", "Value": "test_false_access_key_1"}], + ) + ] check = iam_user_no_setup_initial_access_key() result = check.execute() assert result[0].status == "FAIL" assert search("has never used access key 1", result[0].status_extended) + assert result[0].resource_id == "test_false_access_key_1" + assert ( + result[0].resource_arn + == "arn:aws:iam::123456789012:test_false_access_key_1" + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [ + {"Key": "Name", "Value": "test_false_access_key_1"} + ] - @mock_aws def test_setup_access_key_2_fail(self): raw_credential_report = r"""user,arn,user_creation_time,password_enabled,password_last_used,password_last_changed,password_next_rotation,mfa_active,access_key_1_active,access_key_1_last_rotated,access_key_1_last_used_date,access_key_1_last_used_region,access_key_1_last_used_service,access_key_2_active,access_key_2_last_rotated,access_key_2_last_used_date,access_key_2_last_used_region,access_key_2_last_used_service,cert_1_active,cert_1_last_rotated,cert_2_active,cert_2_last_rotated test_false_access_key_2,arn:aws:iam::123456789012:test_false_access_key_2,2022-04-17T14:59:38+00:00,true,no_information,not_supported,not_supported,false,false,N/A,N/A,N/A,N/A,true,N/A,N/A,N/A,N/A,false,N/A,false,N/A""" @@ -69,13 +72,28 @@ test_false_access_key_2,arn:aws:iam::123456789012:test_false_access_key_2,2022-0 ) service_client.credential_report = credential_list + service_client.users = [ + mock.MagicMock( + name="test_false_access_key_2", + arn="arn:aws:iam::123456789012:test_false_access_key_2", + tags=[{"Key": "Name", "Value": "test_false_access_key_2"}], + ) + ] check = iam_user_no_setup_initial_access_key() result = check.execute() assert result[0].status == "FAIL" assert search("has never used access key 2", result[0].status_extended) + assert result[0].resource_id == "test_false_access_key_2" + assert ( + result[0].resource_arn + == "arn:aws:iam::123456789012:test_false_access_key_2" + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [ + {"Key": "Name", "Value": "test_false_access_key_2"} + ] - @mock_aws def test_setup_both_access_keys_fail(self): raw_credential_report = r"""user,arn,user_creation_time,password_enabled,password_last_used,password_last_changed,password_next_rotation,mfa_active,access_key_1_active,access_key_1_last_rotated,access_key_1_last_used_date,access_key_1_last_used_region,access_key_1_last_used_service,access_key_2_active,access_key_2_last_rotated,access_key_2_last_used_date,access_key_2_last_used_region,access_key_2_last_used_service,cert_1_active,cert_1_last_rotated,cert_2_active,cert_2_last_rotated test_false_both_access_keys,arn:aws:iam::123456789012:test_false_both_access_keys,2022-04-17T14:59:38+00:00,true,no_information,not_supported,not_supported,false,true,N/A,N/A,N/A,N/A,true,N/A,N/A,N/A,N/A,false,N/A,false,N/A""" @@ -98,15 +116,39 @@ test_false_both_access_keys,arn:aws:iam::123456789012:test_false_both_access_key ) service_client.credential_report = credential_list + service_client.users = [ + mock.MagicMock( + name="test_false_both_access_keys", + arn="arn:aws:iam::123456789012:test_false_both_access_keys", + tags=[{"Key": "Name", "Value": "test_false_both_access_keys"}], + ) + ] check = iam_user_no_setup_initial_access_key() result = check.execute() assert result[0].status == "FAIL" assert search("has never used access key 1", result[0].status_extended) + assert result[0].resource_id == "test_false_both_access_keys" + assert ( + result[0].resource_arn + == "arn:aws:iam::123456789012:test_false_both_access_keys" + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [ + {"Key": "Name", "Value": "test_false_both_access_keys"} + ] assert result[1].status == "FAIL" assert search("has never used access key 2", result[1].status_extended) + assert result[1].resource_id == "test_false_both_access_keys" + assert ( + result[1].resource_arn + == "arn:aws:iam::123456789012:test_false_both_access_keys" + ) + assert result[1].region == AWS_REGION_US_EAST_1 + assert result[1].resource_tags == [ + {"Key": "Name", "Value": "test_false_both_access_keys"} + ] - @mock_aws def test_setup_access_key_pass(self): raw_credential_report = r"""user,arn,user_creation_time,password_enabled,password_last_used,password_last_changed,password_next_rotation,mfa_active,access_key_1_active,access_key_1_last_rotated,access_key_1_last_used_date,access_key_1_last_used_region,access_key_1_last_used_service,access_key_2_active,access_key_2_last_rotated,access_key_2_last_used_date,access_key_2_last_used_region,access_key_2_last_used_service,cert_1_active,cert_1_last_rotated,cert_2_active,cert_2_last_rotated test_pass,arn:aws:iam::123456789012:test_pass,2022-02-17T14:59:38+00:00,not_supported,no_information,not_supported,not_supported,false,false,N/A,N/A,N/A,N/A,false,N/A,N/A,N/A,N/A,false,N/A,false,N/A""" @@ -129,6 +171,13 @@ test_pass,arn:aws:iam::123456789012:test_pass,2022-02-17T14:59:38+00:00,not_supp ) service_client.credential_report = credential_list + service_client.users = [ + mock.MagicMock( + name="test_pass", + arn="arn:aws:iam::123456789012:test_pass", + tags=[{"Key": "Name", "Value": "test_pass"}], + ) + ] check = iam_user_no_setup_initial_access_key() result = check.execute() @@ -137,3 +186,7 @@ test_pass,arn:aws:iam::123456789012:test_pass,2022-02-17T14:59:38+00:00,not_supp "does not have access keys or uses the access keys configured", result[0].status_extended, ) + assert result[0].resource_id == "test_pass" + assert result[0].resource_arn == "arn:aws:iam::123456789012:test_pass" + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [{"Key": "Name", "Value": "test_pass"}] diff --git a/tests/providers/aws/services/iam/iam_user_two_active_access_key/iam_user_two_active_access_key_test.py b/tests/providers/aws/services/iam/iam_user_two_active_access_key/iam_user_two_active_access_key_test.py index 2e23f272b1..6337334613 100644 --- a/tests/providers/aws/services/iam/iam_user_two_active_access_key/iam_user_two_active_access_key_test.py +++ b/tests/providers/aws/services/iam/iam_user_two_active_access_key/iam_user_two_active_access_key_test.py @@ -23,6 +23,7 @@ class Test_iam_user_two_active_access_key: iam_client = client("iam") user = "test1" user_arn = iam_client.create_user(UserName=user)["User"]["Arn"] + iam_client.tag_user(UserName=user, Tags=[{"Key": "Name", "Value": user}]) # Create Access Key 1 iam_client.create_access_key(UserName=user) # Create Access Key 2 @@ -57,6 +58,7 @@ class Test_iam_user_two_active_access_key: assert search( f"User {user} has 2 active access keys.", result[0].status_extended ) + assert result[0].resource_tags == [{"Key": "Name", "Value": user}] @mock_aws def test_iam_user_one_active_access_key(self): @@ -64,6 +66,7 @@ class Test_iam_user_two_active_access_key: iam_client = client("iam") user = "test1" user_arn = iam_client.create_user(UserName=user)["User"]["Arn"] + iam_client.tag_user(UserName=user, Tags=[{"Key": "Name", "Value": user}]) # Create Access Key 1 iam_client.create_access_key(UserName=user) @@ -94,6 +97,7 @@ class Test_iam_user_two_active_access_key: f"User {user} does not have 2 active access keys.", result[0].status_extended, ) + assert result[0].resource_tags == [{"Key": "Name", "Value": user}] @mock_aws def test_iam_user_without_active_access_key(self): @@ -101,6 +105,7 @@ class Test_iam_user_two_active_access_key: iam_client = client("iam") user = "test1" user_arn = iam_client.create_user(UserName=user)["User"]["Arn"] + iam_client.tag_user(UserName=user, Tags=[{"Key": "Name", "Value": user}]) from prowler.providers.aws.services.iam.iam_service import IAM @@ -129,6 +134,7 @@ class Test_iam_user_two_active_access_key: f"User {user} does not have 2 active access keys.", result[0].status_extended, ) + assert result[0].resource_tags == [{"Key": "Name", "Value": user}] @mock_aws def test_iam_no_users(self): diff --git a/tests/providers/aws/services/iam/iam_user_with_temporary_credentials/iam_user_with_temporary_credentials_test.py b/tests/providers/aws/services/iam/iam_user_with_temporary_credentials/iam_user_with_temporary_credentials_test.py index dd7848d36d..e1b9c31587 100644 --- a/tests/providers/aws/services/iam/iam_user_with_temporary_credentials/iam_user_with_temporary_credentials_test.py +++ b/tests/providers/aws/services/iam/iam_user_with_temporary_credentials/iam_user_with_temporary_credentials_test.py @@ -16,6 +16,13 @@ class Test_iam_user_with_temporary_credentials: iam_client.access_keys_metadata = {} iam_client.last_accessed_services = {} + iam_client.users = [ + mock.MagicMock( + arn=IAM_USER_ARN, + tags=[{"Key": "Name", "Value": IAM_USER_NAME}], + ) + ] + # Generate temporary credentials usage iam_client.user_temporary_credentials_usage = {} iam_client._get_user_temporary_credentials_usage = ( @@ -45,6 +52,13 @@ class Test_iam_user_with_temporary_credentials: iam_client.access_keys_metadata = {USER_DATA: []} iam_client.last_accessed_services = {USER_DATA: []} + iam_client.users = [ + mock.MagicMock( + arn=IAM_USER_ARN, + tags=[{"Key": "Name", "Value": IAM_USER_NAME}], + ) + ] + # Generate temporary credentials usage iam_client.user_temporary_credentials_usage = {} iam_client._get_user_temporary_credentials_usage = ( @@ -74,6 +88,7 @@ class Test_iam_user_with_temporary_credentials: assert result[0].resource_id == IAM_USER_NAME assert result[0].resource_arn == IAM_USER_ARN assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [{"Key": "Name", "Value": IAM_USER_NAME}] def test_user_access_keys_no_accesed_services(self): iam_client = mock.MagicMock @@ -82,6 +97,13 @@ class Test_iam_user_with_temporary_credentials: iam_client.access_keys_metadata = {USER_DATA: [{"AccessKeyId": 1}]} iam_client.last_accessed_services = {USER_DATA: []} + iam_client.users = [ + mock.MagicMock( + arn=IAM_USER_ARN, + tags=[{"Key": "Name", "Value": IAM_USER_NAME}], + ) + ] + # Generate temporary credentials usage iam_client.user_temporary_credentials_usage = {} iam_client._get_user_temporary_credentials_usage = ( @@ -111,6 +133,7 @@ class Test_iam_user_with_temporary_credentials: assert result[0].resource_id == IAM_USER_NAME assert result[0].resource_arn == IAM_USER_ARN assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [{"Key": "Name", "Value": IAM_USER_NAME}] def test_user_access_keys_accesed_services_sts(self): iam_client = mock.MagicMock @@ -119,6 +142,13 @@ class Test_iam_user_with_temporary_credentials: iam_client.access_keys_metadata = {USER_DATA: [{"AccessKeyId": 1}]} iam_client.last_accessed_services = {USER_DATA: [{"ServiceNamespace": "sts"}]} + iam_client.users = [ + mock.MagicMock( + arn=IAM_USER_ARN, + tags=[{"Key": "Name", "Value": IAM_USER_NAME}], + ) + ] + # Generate temporary credentials usage iam_client.user_temporary_credentials_usage = {} iam_client._get_user_temporary_credentials_usage = ( @@ -148,6 +178,7 @@ class Test_iam_user_with_temporary_credentials: assert result[0].resource_id == IAM_USER_NAME assert result[0].resource_arn == IAM_USER_ARN assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [{"Key": "Name", "Value": IAM_USER_NAME}] def test_access_keys_with_iam_and_sts(self): iam_client = mock.MagicMock @@ -158,6 +189,13 @@ class Test_iam_user_with_temporary_credentials: USER_DATA: [{"ServiceNamespace": "sts"}, {"ServiceNamespace": "iam"}] } + iam_client.users = [ + mock.MagicMock( + arn=IAM_USER_ARN, + tags=[{"Key": "Name", "Value": IAM_USER_NAME}], + ) + ] + # Generate temporary credentials usage iam_client.user_temporary_credentials_usage = {} iam_client._get_user_temporary_credentials_usage = ( @@ -187,6 +225,7 @@ class Test_iam_user_with_temporary_credentials: assert result[0].resource_id == IAM_USER_NAME assert result[0].resource_arn == IAM_USER_ARN assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [{"Key": "Name", "Value": IAM_USER_NAME}] def test_access_keys_with_iam_and_ec2(self): iam_client = mock.MagicMock @@ -197,6 +236,13 @@ class Test_iam_user_with_temporary_credentials: USER_DATA: [{"ServiceNamespace": "iam"}, {"ServiceNamespace": "ec2"}] } + iam_client.users = [ + mock.MagicMock( + arn=IAM_USER_ARN, + tags=[{"Key": "Name", "Value": IAM_USER_NAME}], + ) + ] + # Generate temporary credentials usage iam_client.user_temporary_credentials_usage = {} iam_client._get_user_temporary_credentials_usage = ( @@ -226,3 +272,4 @@ class Test_iam_user_with_temporary_credentials: assert result[0].resource_id == IAM_USER_NAME assert result[0].resource_arn == IAM_USER_ARN assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [{"Key": "Name", "Value": IAM_USER_NAME}] diff --git a/tests/providers/aws/services/kinesis/kinesis_service_test.py b/tests/providers/aws/services/kinesis/kinesis_service_test.py new file mode 100644 index 0000000000..21dbd70804 --- /dev/null +++ b/tests/providers/aws/services/kinesis/kinesis_service_test.py @@ -0,0 +1,74 @@ +from unittest.mock import patch + +import botocore +from moto import mock_aws + +from prowler.providers.aws.services.kinesis.kinesis_service import Kinesis, StreamStatus +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 == "ListStreams": + return { + "StreamNames": ["test-stream"], + "StreamSummaries": [ + { + "StreamName": "test-stream", + "StreamARN": "arn:aws:kinesis:us-east-1:123456789012:stream/test-stream", + "StreamStatus": "ACTIVE", + } + ], + } + if operation_name == "DescribeStream": + return { + "StreamDescription": { + "StreamName": "test-stream", + "StreamARN": "arn:aws:kinesis:us-east-1:123456789012:stream/test-stream", + "StreamStatus": "ACTIVE", + "Tags": [{"Key": "test_tag", "Value": "test_value"}], + } + } + if operation_name == "ListTagsForStream": + return {"Tags": [{"Key": "test_tag", "Value": "test_value"}]} + return make_api_call(self, operation_name, kwarg) + + +# Patch every AWS call using Boto3 +@patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) +class Test_Kinesis_Service: + # Test Kinesis Client + @mock_aws + def test_get_client(self): + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + kinesis = Kinesis(aws_provider) + assert ( + kinesis.regional_clients[AWS_REGION_US_EAST_1].__class__.__name__ + == "Kinesis" + ) + + # Test Kinesis Session + def test__get_session__(self): + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + kinesis = Kinesis(aws_provider) + assert kinesis.session.__class__.__name__ == "Session" + + # Test Kinesis Service + @mock_aws + def test__get_service__(self): + kinesis = Kinesis(set_mocked_aws_provider()) + assert kinesis.service == "kinesis" + + @mock_aws + def test_list_streamscomplete(self): + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + kinesis = Kinesis(aws_provider) + + arn = "arn:aws:kinesis:us-east-1:123456789012:stream/test-stream" + assert len(kinesis.streams) == 1 + assert kinesis.streams[arn].name == "test-stream" + assert kinesis.streams[arn].status == StreamStatus.ACTIVE + assert kinesis.streams[arn].tags == [{"Key": "test_tag", "Value": "test_value"}] + assert kinesis.streams[arn].region == AWS_REGION_US_EAST_1 + assert kinesis.streams[arn].arn == arn diff --git a/tests/providers/aws/services/kinesis/kinesis_stream_encrypted_at_rest/kinesis_stream_encrypted_at_rest_test.py b/tests/providers/aws/services/kinesis/kinesis_stream_encrypted_at_rest/kinesis_stream_encrypted_at_rest_test.py new file mode 100644 index 0000000000..f277b6ad83 --- /dev/null +++ b/tests/providers/aws/services/kinesis/kinesis_stream_encrypted_at_rest/kinesis_stream_encrypted_at_rest_test.py @@ -0,0 +1,123 @@ +from unittest import mock + +from boto3 import client +from moto import mock_aws + +from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider + + +class Test_kinesis_encrypted_at_rest: + @mock_aws + def test_no_streams(self): + from prowler.providers.aws.services.kinesis.kinesis_service import Kinesis + + 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.kinesis.kinesis_stream_encrypted_at_rest.kinesis_stream_encrypted_at_rest.kinesis_client", + new=Kinesis(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.kinesis.kinesis_stream_encrypted_at_rest.kinesis_stream_encrypted_at_rest import ( + kinesis_stream_encrypted_at_rest, + ) + + check = kinesis_stream_encrypted_at_rest() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_encrypted_stream(self): + kinesis_client = client("kinesis", region_name=AWS_REGION_US_EAST_1) + stream_name = "stream_test_us" + kinesis_client.create_stream( + StreamName=stream_name, + ShardCount=1, + StreamModeDetails={"StreamMode": "PROVISIONED"}, + ) + + kinesis_client.start_stream_encryption( + StreamName=stream_name, + EncryptionType="KMS", + KeyId="arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012", + ) + + from prowler.providers.aws.services.kinesis.kinesis_service import Kinesis + + 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.kinesis.kinesis_stream_encrypted_at_rest.kinesis_stream_encrypted_at_rest.kinesis_client", + new=Kinesis(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.kinesis.kinesis_stream_encrypted_at_rest.kinesis_stream_encrypted_at_rest import ( + kinesis_stream_encrypted_at_rest, + ) + + check = kinesis_stream_encrypted_at_rest() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Kinesis Stream {stream_name} is encrypted at rest." + ) + assert result[0].resource_id == stream_name + assert ( + result[0].resource_arn + == f"arn:aws:kinesis:{AWS_REGION_US_EAST_1}:123456789012:stream/{stream_name}" + ) + assert result[0].resource_tags == [] + assert result[0].region == AWS_REGION_US_EAST_1 + + @mock_aws + def test_non_encrypted_stream(self): + kinesis_client = client("kinesis", region_name=AWS_REGION_US_EAST_1) + stream_name = "stream_test_us" + kinesis_client.create_stream( + StreamName=stream_name, + ShardCount=1, + StreamModeDetails={"StreamMode": "PROVISIONED"}, + ) + + from prowler.providers.aws.services.kinesis.kinesis_service import Kinesis + + 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.kinesis.kinesis_stream_encrypted_at_rest.kinesis_stream_encrypted_at_rest.kinesis_client", + new=Kinesis(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.kinesis.kinesis_stream_encrypted_at_rest.kinesis_stream_encrypted_at_rest import ( + kinesis_stream_encrypted_at_rest, + ) + + check = kinesis_stream_encrypted_at_rest() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Kinesis Stream {stream_name} is not encrypted at rest." + ) + assert result[0].resource_id == stream_name + assert ( + result[0].resource_arn + == f"arn:aws:kinesis:{AWS_REGION_US_EAST_1}:123456789012:stream/{stream_name}" + ) + assert result[0].resource_tags == [] + assert result[0].region == AWS_REGION_US_EAST_1 diff --git a/tests/providers/aws/services/networkfirewall/networkfirewall_logging_enabled/networkfirewall_logging_enabled_test.py b/tests/providers/aws/services/networkfirewall/networkfirewall_logging_enabled/networkfirewall_logging_enabled_test.py new file mode 100644 index 0000000000..79add91c63 --- /dev/null +++ b/tests/providers/aws/services/networkfirewall/networkfirewall_logging_enabled/networkfirewall_logging_enabled_test.py @@ -0,0 +1,147 @@ +from unittest import mock + +from prowler.providers.aws.services.networkfirewall.networkfirewall_service import ( + Firewall, + LogDestinationType, + LoggingConfiguration, + LogType, +) +from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider + +FIREWALL_ARN = "arn:aws:network-firewall:us-east-1:123456789012:firewall/my-firewall" +FIREWALL_NAME = "my-firewall" +VPC_ID_PROTECTED = "vpc-12345678901234567" +VPC_ID_UNPROTECTED = "vpc-12345678901234568" +POLICY_ARN = "arn:aws:network-firewall:us-east-1:123456789012:firewall-policy/my-policy" + + +class Test_networkfirewall_logging_enabled: + def test_no_networkfirewall(self): + networkfirewall_client = mock.MagicMock + networkfirewall_client.provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1] + ) + networkfirewall_client.region = AWS_REGION_US_EAST_1 + networkfirewall_client.network_firewalls = {} + + 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.networkfirewall.networkfirewall_logging_enabled.networkfirewall_logging_enabled.networkfirewall_client", + new=networkfirewall_client, + ): + # Test Check + from prowler.providers.aws.services.networkfirewall.networkfirewall_logging_enabled.networkfirewall_logging_enabled import ( + networkfirewall_logging_enabled, + ) + + check = networkfirewall_logging_enabled() + result = check.execute() + + assert len(result) == 0 + + def test_networkfirewall_logging_disabled(self): + networkfirewall_client = mock.MagicMock + networkfirewall_client.provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1] + ) + networkfirewall_client.region = AWS_REGION_US_EAST_1 + networkfirewall_client.network_firewalls = { + FIREWALL_ARN: Firewall( + arn=FIREWALL_ARN, + name=FIREWALL_NAME, + region=AWS_REGION_US_EAST_1, + policy_arn=POLICY_ARN, + vpc_id=VPC_ID_PROTECTED, + tags=[], + encryption_type="CUSTOMER_KMS", + logging_configuration=[], + ) + } + + 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.networkfirewall.networkfirewall_logging_enabled.networkfirewall_logging_enabled.networkfirewall_client", + new=networkfirewall_client, + ): + # Test Check + from prowler.providers.aws.services.networkfirewall.networkfirewall_logging_enabled.networkfirewall_logging_enabled import ( + networkfirewall_logging_enabled, + ) + + check = networkfirewall_logging_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Network Firewall {FIREWALL_NAME} does not have logging enabled." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == FIREWALL_NAME + assert result[0].resource_tags == [] + assert result[0].resource_arn == FIREWALL_ARN + + def test_networkfirewall_logging_enabled(self): + networkfirewall_client = mock.MagicMock + networkfirewall_client.provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1] + ) + networkfirewall_client.region = AWS_REGION_US_EAST_1 + networkfirewall_client.network_firewalls = { + FIREWALL_ARN: Firewall( + arn=FIREWALL_ARN, + name=FIREWALL_NAME, + region=AWS_REGION_US_EAST_1, + policy_arn=POLICY_ARN, + vpc_id=VPC_ID_PROTECTED, + tags=[], + encryption_type="CUSTOMER_KMS", + logging_configuration=[ + LoggingConfiguration( + log_type=LogType.flow, + log_destination_type=LogDestinationType.s3, + log_destination={"bucket_name": "my-bucket"}, + ) + ], + ), + } + + 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.networkfirewall.networkfirewall_logging_enabled.networkfirewall_logging_enabled.networkfirewall_client", + new=networkfirewall_client, + ): + # Test Check + from prowler.providers.aws.services.networkfirewall.networkfirewall_logging_enabled.networkfirewall_logging_enabled import ( + networkfirewall_logging_enabled, + ) + + check = networkfirewall_logging_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Network Firewall {FIREWALL_NAME} has logging enabled." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == FIREWALL_NAME + assert result[0].resource_tags == [] + assert result[0].resource_arn == FIREWALL_ARN diff --git a/tests/providers/aws/services/networkfirewall/networkfirewall_multi_az/networkfirewall_multi_az_test.py b/tests/providers/aws/services/networkfirewall/networkfirewall_multi_az/networkfirewall_multi_az_test.py new file mode 100644 index 0000000000..6e0d5f6750 --- /dev/null +++ b/tests/providers/aws/services/networkfirewall/networkfirewall_multi_az/networkfirewall_multi_az_test.py @@ -0,0 +1,155 @@ +from unittest import mock + +from prowler.providers.aws.services.networkfirewall.networkfirewall_service import ( + Firewall, + IPAddressType, + Subnet, +) +from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider + +FIREWALL_ARN = "arn:aws:network-firewall:us-east-1:123456789012:firewall/my-firewall" +FIREWALL_NAME = "my-firewall" +VPC_ID_PROTECTED = "vpc-12345678901234567" +VPC_ID_UNPROTECTED = "vpc-12345678901234568" +POLICY_ARN = "arn:aws:network-firewall:us-east-1:123456789012:firewall-policy/my-policy" + + +class Test_networkfirewall_multi_az: + def test_no_networkfirewall(self): + networkfirewall_client = mock.MagicMock + networkfirewall_client.provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1] + ) + networkfirewall_client.region = AWS_REGION_US_EAST_1 + networkfirewall_client.network_firewalls = {} + + 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.networkfirewall.networkfirewall_multi_az.networkfirewall_multi_az.networkfirewall_client", + new=networkfirewall_client, + ): + # Test Check + from prowler.providers.aws.services.networkfirewall.networkfirewall_multi_az.networkfirewall_multi_az import ( + networkfirewall_multi_az, + ) + + check = networkfirewall_multi_az() + result = check.execute() + + assert len(result) == 0 + + def test_networkfirewall_multi_az_disabled(self): + networkfirewall_client = mock.MagicMock + networkfirewall_client.provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1] + ) + networkfirewall_client.region = AWS_REGION_US_EAST_1 + networkfirewall_client.network_firewalls = { + FIREWALL_ARN: Firewall( + arn=FIREWALL_ARN, + name=FIREWALL_NAME, + region=AWS_REGION_US_EAST_1, + policy_arn=POLICY_ARN, + vpc_id=VPC_ID_PROTECTED, + tags=[], + encryption_type="CUSTOMER_KMS", + deletion_protection=False, + subnet_mappings=[ + Subnet( + subnet_id="subnet-12345678901234567", + ip_addr_type=IPAddressType.IPV4, + ) + ], + ) + } + 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.networkfirewall.networkfirewall_multi_az.networkfirewall_multi_az.networkfirewall_client", + new=networkfirewall_client, + ): + # Test Check + from prowler.providers.aws.services.networkfirewall.networkfirewall_multi_az.networkfirewall_multi_az import ( + networkfirewall_multi_az, + ) + + check = networkfirewall_multi_az() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Network Firewall {FIREWALL_NAME} is not deployed across multiple AZ." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == FIREWALL_NAME + assert result[0].resource_tags == [] + assert result[0].resource_arn == FIREWALL_ARN + + def test_networkfirewall_multi_az_enabled(self): + networkfirewall_client = mock.MagicMock + networkfirewall_client.provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1] + ) + networkfirewall_client.region = AWS_REGION_US_EAST_1 + networkfirewall_client.network_firewalls = { + FIREWALL_ARN: Firewall( + arn=FIREWALL_ARN, + name=FIREWALL_NAME, + region=AWS_REGION_US_EAST_1, + policy_arn=POLICY_ARN, + vpc_id=VPC_ID_PROTECTED, + tags=[], + encryption_type="CUSTOMER_KMS", + deletion_protection=True, + subnet_mappings=[ + Subnet( + subnet_id="subnet-12345678901234567", + ip_addr_type=IPAddressType.IPV4, + ), + Subnet( + subnet_id="subnet-12345678901234568", + ip_addr_type=IPAddressType.IPV4, + ), + ], + ) + } + + 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.networkfirewall.networkfirewall_multi_az.networkfirewall_multi_az.networkfirewall_client", + new=networkfirewall_client, + ): + # Test Check + from prowler.providers.aws.services.networkfirewall.networkfirewall_multi_az.networkfirewall_multi_az import ( + networkfirewall_multi_az, + ) + + check = networkfirewall_multi_az() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Network Firewall {FIREWALL_NAME} is deployed across multiple AZ." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == FIREWALL_NAME + assert result[0].resource_tags == [] + assert result[0].resource_arn == FIREWALL_ARN diff --git a/tests/providers/aws/services/networkfirewall/networkfirewall_policy_default_action_fragmented_packets/networkfirewall_policy_default_action_fragmented_packets_test.py b/tests/providers/aws/services/networkfirewall/networkfirewall_policy_default_action_fragmented_packets/networkfirewall_policy_default_action_fragmented_packets_test.py new file mode 100644 index 0000000000..cc7c8a17b1 --- /dev/null +++ b/tests/providers/aws/services/networkfirewall/networkfirewall_policy_default_action_fragmented_packets/networkfirewall_policy_default_action_fragmented_packets_test.py @@ -0,0 +1,188 @@ +from unittest import mock + +from prowler.providers.aws.services.networkfirewall.networkfirewall_service import ( + Firewall, +) +from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider + +FIREWALL_ARN = "arn:aws:network-firewall:us-east-1:123456789012:firewall/my-firewall" +FIREWALL_NAME = "my-firewall" +VPC_ID_PROTECTED = "vpc-12345678901234567" +VPC_ID_UNPROTECTED = "vpc-12345678901234568" +POLICY_ARN = "arn:aws:network-firewall:us-east-1:123456789012:firewall-policy/my-policy" + + +class Test_networkfirewall_policy_default_action_fragmented_packets: + def test_no_networkfirewall(self): + networkfirewall_client = mock.MagicMock + networkfirewall_client.provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1] + ) + networkfirewall_client.region = AWS_REGION_US_EAST_1 + networkfirewall_client.network_firewalls = {} + + 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.networkfirewall.networkfirewall_policy_default_action_fragmented_packets.networkfirewall_policy_default_action_fragmented_packets.networkfirewall_client", + new=networkfirewall_client, + ): + # Test Check + from prowler.providers.aws.services.networkfirewall.networkfirewall_policy_default_action_fragmented_packets.networkfirewall_policy_default_action_fragmented_packets import ( + networkfirewall_policy_default_action_fragmented_packets, + ) + + check = networkfirewall_policy_default_action_fragmented_packets() + result = check.execute() + + assert len(result) == 0 + + def test_networkfirewall_default_stateless_action_drop(self): + networkfirewall_client = mock.MagicMock + networkfirewall_client.provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1] + ) + networkfirewall_client.region = AWS_REGION_US_EAST_1 + networkfirewall_client.network_firewalls = { + FIREWALL_ARN: Firewall( + arn=FIREWALL_ARN, + name=FIREWALL_NAME, + region=AWS_REGION_US_EAST_1, + policy_arn=POLICY_ARN, + vpc_id=VPC_ID_PROTECTED, + tags=[], + encryption_type="CUSTOMER_KMS", + deletion_protection=False, + default_stateless_frag_actions=["aws:drop"], + ) + } + 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.networkfirewall.networkfirewall_policy_default_action_fragmented_packets.networkfirewall_policy_default_action_fragmented_packets.networkfirewall_client", + new=networkfirewall_client, + ): + # Test Check + from prowler.providers.aws.services.networkfirewall.networkfirewall_policy_default_action_fragmented_packets.networkfirewall_policy_default_action_fragmented_packets import ( + networkfirewall_policy_default_action_fragmented_packets, + ) + + check = networkfirewall_policy_default_action_fragmented_packets() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Network Firewall {FIREWALL_NAME} policy does drop or forward fragmented packets by default." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == FIREWALL_NAME + assert result[0].resource_tags == [] + assert result[0].resource_arn == FIREWALL_ARN + + def test_networkfirewall_default_stateless_action_forward(self): + networkfirewall_client = mock.MagicMock + networkfirewall_client.provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1] + ) + networkfirewall_client.region = AWS_REGION_US_EAST_1 + networkfirewall_client.network_firewalls = { + FIREWALL_ARN: Firewall( + arn=FIREWALL_ARN, + name=FIREWALL_NAME, + region=AWS_REGION_US_EAST_1, + policy_arn=POLICY_ARN, + vpc_id=VPC_ID_PROTECTED, + tags=[], + encryption_type="CUSTOMER_KMS", + deletion_protection=True, + default_stateless_frag_actions=["aws:forward_to_sfe"], + ) + } + + 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.networkfirewall.networkfirewall_policy_default_action_fragmented_packets.networkfirewall_policy_default_action_fragmented_packets.networkfirewall_client", + new=networkfirewall_client, + ): + # Test Check + from prowler.providers.aws.services.networkfirewall.networkfirewall_policy_default_action_fragmented_packets.networkfirewall_policy_default_action_fragmented_packets import ( + networkfirewall_policy_default_action_fragmented_packets, + ) + + check = networkfirewall_policy_default_action_fragmented_packets() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Network Firewall {FIREWALL_NAME} policy does drop or forward fragmented packets by default." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == FIREWALL_NAME + assert result[0].resource_tags == [] + assert result[0].resource_arn == FIREWALL_ARN + + def test_networkfirewall_default_stateless_action_pass(self): + networkfirewall_client = mock.MagicMock + networkfirewall_client.provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1] + ) + networkfirewall_client.region = AWS_REGION_US_EAST_1 + networkfirewall_client.network_firewalls = { + FIREWALL_ARN: Firewall( + arn=FIREWALL_ARN, + name=FIREWALL_NAME, + region=AWS_REGION_US_EAST_1, + policy_arn=POLICY_ARN, + vpc_id=VPC_ID_PROTECTED, + tags=[], + encryption_type="CUSTOMER_KMS", + deletion_protection=True, + default_stateless_frag_actions=["aws:pass"], + ) + } + + 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.networkfirewall.networkfirewall_policy_default_action_fragmented_packets.networkfirewall_policy_default_action_fragmented_packets.networkfirewall_client", + new=networkfirewall_client, + ): + # Test Check + from prowler.providers.aws.services.networkfirewall.networkfirewall_policy_default_action_fragmented_packets.networkfirewall_policy_default_action_fragmented_packets import ( + networkfirewall_policy_default_action_fragmented_packets, + ) + + check = networkfirewall_policy_default_action_fragmented_packets() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Network Firewall {FIREWALL_NAME} policy does not drop or forward fragmented packets by default." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == FIREWALL_NAME + assert result[0].resource_tags == [] + assert result[0].resource_arn == FIREWALL_ARN diff --git a/tests/providers/aws/services/networkfirewall/networkfirewall_policy_default_action_full_packets/networkfirewall_policy_default_action_full_packets_test.py b/tests/providers/aws/services/networkfirewall/networkfirewall_policy_default_action_full_packets/networkfirewall_policy_default_action_full_packets_test.py new file mode 100644 index 0000000000..9388458eb6 --- /dev/null +++ b/tests/providers/aws/services/networkfirewall/networkfirewall_policy_default_action_full_packets/networkfirewall_policy_default_action_full_packets_test.py @@ -0,0 +1,187 @@ +from unittest import mock + +from prowler.providers.aws.services.networkfirewall.networkfirewall_service import ( + Firewall, +) +from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider + +FIREWALL_ARN = "arn:aws:network-firewall:us-east-1:123456789012:firewall/my-firewall" +FIREWALL_NAME = "my-firewall" +VPC_ID_PROTECTED = "vpc-12345678901234567" +VPC_ID_UNPROTECTED = "vpc-12345678901234568" +POLICY_ARN = "arn:aws:network-firewall:us-east-1:123456789012:firewall-policy/my-policy" + + +class Test_networkfirewall_policy_default_action_full_packets: + def test_no_networkfirewall(self): + networkfirewall_client = mock.MagicMock + networkfirewall_client.provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1] + ) + networkfirewall_client.region = AWS_REGION_US_EAST_1 + networkfirewall_client.network_firewalls = {} + + 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.networkfirewall.networkfirewall_policy_default_action_full_packets.networkfirewall_policy_default_action_full_packets.networkfirewall_client", + new=networkfirewall_client, + ): + # Test Check + from prowler.providers.aws.services.networkfirewall.networkfirewall_policy_default_action_full_packets.networkfirewall_policy_default_action_full_packets import ( + networkfirewall_policy_default_action_full_packets, + ) + + check = networkfirewall_policy_default_action_full_packets() + result = check.execute() + + assert len(result) == 0 + + def test_networkfirewall_policy_default_action_drop(self): + networkfirewall_client = mock.MagicMock + networkfirewall_client.provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1] + ) + networkfirewall_client.region = AWS_REGION_US_EAST_1 + networkfirewall_client.network_firewalls = { + FIREWALL_ARN: Firewall( + arn=FIREWALL_ARN, + name=FIREWALL_NAME, + region=AWS_REGION_US_EAST_1, + policy_arn=POLICY_ARN, + vpc_id=VPC_ID_PROTECTED, + tags=[], + encryption_type="CUSTOMER_KMS", + deletion_protection=False, + default_stateless_actions=["aws:drop"], + ) + } + 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.networkfirewall.networkfirewall_policy_default_action_full_packets.networkfirewall_policy_default_action_full_packets.networkfirewall_client", + new=networkfirewall_client, + ): + # Test Check + from prowler.providers.aws.services.networkfirewall.networkfirewall_policy_default_action_full_packets.networkfirewall_policy_default_action_full_packets import ( + networkfirewall_policy_default_action_full_packets, + ) + + check = networkfirewall_policy_default_action_full_packets() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Network Firewall {FIREWALL_NAME} policy does drop or forward full packets by default." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == FIREWALL_NAME + assert result[0].resource_tags == [] + assert result[0].resource_arn == FIREWALL_ARN + + def test_networkfirewall_policy_default_action_forward(self): + networkfirewall_client = mock.MagicMock + networkfirewall_client.provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1] + ) + networkfirewall_client.region = AWS_REGION_US_EAST_1 + networkfirewall_client.network_firewalls = { + FIREWALL_ARN: Firewall( + arn=FIREWALL_ARN, + name=FIREWALL_NAME, + region=AWS_REGION_US_EAST_1, + policy_arn=POLICY_ARN, + vpc_id=VPC_ID_PROTECTED, + tags=[], + encryption_type="CUSTOMER_KMS", + deletion_protection=True, + default_stateless_actions=["aws:forward_to_sfe"], + ) + } + + 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.networkfirewall.networkfirewall_policy_default_action_full_packets.networkfirewall_policy_default_action_full_packets.networkfirewall_client", + new=networkfirewall_client, + ): + # Test Check + from prowler.providers.aws.services.networkfirewall.networkfirewall_policy_default_action_full_packets.networkfirewall_policy_default_action_full_packets import ( + networkfirewall_policy_default_action_full_packets, + ) + + check = networkfirewall_policy_default_action_full_packets() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Network Firewall {FIREWALL_NAME} policy does drop or forward full packets by default." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == FIREWALL_NAME + assert result[0].resource_tags == [] + assert result[0].resource_arn == FIREWALL_ARN + + def test_networkfirewall_policy_default_action_pass(self): + networkfirewall_client = mock.MagicMock + networkfirewall_client.provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1] + ) + networkfirewall_client.region = AWS_REGION_US_EAST_1 + networkfirewall_client.network_firewalls = { + FIREWALL_ARN: Firewall( + arn=FIREWALL_ARN, + name=FIREWALL_NAME, + region=AWS_REGION_US_EAST_1, + policy_arn=POLICY_ARN, + vpc_id=VPC_ID_PROTECTED, + tags=[], + encryption_type="CUSTOMER_KMS", + deletion_protection=False, + default_stateless_actions=["aws:pass"], + ) + } + 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.networkfirewall.networkfirewall_policy_default_action_full_packets.networkfirewall_policy_default_action_full_packets.networkfirewall_client", + new=networkfirewall_client, + ): + # Test Check + from prowler.providers.aws.services.networkfirewall.networkfirewall_policy_default_action_full_packets.networkfirewall_policy_default_action_full_packets import ( + networkfirewall_policy_default_action_full_packets, + ) + + check = networkfirewall_policy_default_action_full_packets() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Network Firewall {FIREWALL_NAME} policy does not drop or forward full packets by default." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == FIREWALL_NAME + assert result[0].resource_tags == [] + assert result[0].resource_arn == FIREWALL_ARN diff --git a/tests/providers/aws/services/networkfirewall/networkfirewall_service_test.py b/tests/providers/aws/services/networkfirewall/networkfirewall_service_test.py index e30de7ed69..c5299b2b76 100644 --- a/tests/providers/aws/services/networkfirewall/networkfirewall_service_test.py +++ b/tests/providers/aws/services/networkfirewall/networkfirewall_service_test.py @@ -1,8 +1,13 @@ +from unittest import mock from unittest.mock import patch import botocore from prowler.providers.aws.services.networkfirewall.networkfirewall_service import ( + Firewall, + LogDestinationType, + LoggingConfiguration, + LogType, NetworkFirewall, ) from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider @@ -86,6 +91,51 @@ class Test_NetworkFirewall_Service: ) assert networkfirewall.network_firewalls[FIREWALL_ARN].name == FIREWALL_NAME + def test_describe_logging_configuration(self): + networkfirewall = mock.MagicMock + networkfirewall.provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + networkfirewall.region = AWS_REGION_US_EAST_1 + networkfirewall.network_firewalls = { + FIREWALL_ARN: Firewall( + arn=FIREWALL_ARN, + name=FIREWALL_NAME, + region=AWS_REGION_US_EAST_1, + policy_arn=POLICY_ARN, + vpc_id=VPC_ID, + tags=[{"Key": "test_tag", "Value": "test_value"}], + encryption_type="CUSTOMER_KMS", + logging_configuration=[ + LoggingConfiguration( + log_type=LogType.flow, + log_destination_type=LogDestinationType.s3, + log_destination={ + "bucket_name": "my-bucket", + }, + ) + ], + ) + } + assert len(networkfirewall.network_firewalls) == 1 + assert ( + networkfirewall.network_firewalls[FIREWALL_ARN].region + == AWS_REGION_US_EAST_1 + ) + assert networkfirewall.network_firewalls[FIREWALL_ARN].name == FIREWALL_NAME + assert networkfirewall.network_firewalls[FIREWALL_ARN].policy_arn == POLICY_ARN + assert networkfirewall.network_firewalls[FIREWALL_ARN].vpc_id == VPC_ID + assert networkfirewall.network_firewalls[FIREWALL_ARN].tags == [ + {"Key": "test_tag", "Value": "test_value"} + ] + assert networkfirewall.network_firewalls[ + FIREWALL_ARN + ].logging_configuration == [ + LoggingConfiguration( + log_type=LogType.flow, + log_destination_type=LogDestinationType.s3, + log_destination={"bucket_name": "my-bucket"}, + ) + ] + def test_describe_firewall(self): aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) networkfirewall = NetworkFirewall(aws_provider) diff --git a/tests/providers/aws/services/redshift/redshift_cluster_audit_logging/redshift_cluster_audit_logging_test.py b/tests/providers/aws/services/redshift/redshift_cluster_audit_logging/redshift_cluster_audit_logging_test.py index b5b3353cd7..3c6b9f98f2 100644 --- a/tests/providers/aws/services/redshift/redshift_cluster_audit_logging/redshift_cluster_audit_logging_test.py +++ b/tests/providers/aws/services/redshift/redshift_cluster_audit_logging/redshift_cluster_audit_logging_test.py @@ -17,6 +17,9 @@ class Test_redshift_cluster_audit_logging: with mock.patch( "prowler.providers.aws.services.redshift.redshift_service.Redshift", redshift_client, + ), mock.patch( + "prowler.providers.aws.services.redshift.redshift_client.redshift_client", + redshift_client, ): from prowler.providers.aws.services.redshift.redshift_cluster_audit_logging.redshift_cluster_audit_logging import ( redshift_cluster_audit_logging, @@ -40,6 +43,9 @@ class Test_redshift_cluster_audit_logging: with mock.patch( "prowler.providers.aws.services.redshift.redshift_service.Redshift", redshift_client, + ), mock.patch( + "prowler.providers.aws.services.redshift.redshift_client.redshift_client", + redshift_client, ): from prowler.providers.aws.services.redshift.redshift_cluster_audit_logging.redshift_cluster_audit_logging import ( redshift_cluster_audit_logging, @@ -70,6 +76,9 @@ class Test_redshift_cluster_audit_logging: with mock.patch( "prowler.providers.aws.services.redshift.redshift_service.Redshift", redshift_client, + ), mock.patch( + "prowler.providers.aws.services.redshift.redshift_client.redshift_client", + redshift_client, ): from prowler.providers.aws.services.redshift.redshift_cluster_audit_logging.redshift_cluster_audit_logging import ( redshift_cluster_audit_logging, diff --git a/tests/providers/aws/services/redshift/redshift_cluster_automated_snapshot/redshift_cluster_automated_snapshot_test.py b/tests/providers/aws/services/redshift/redshift_cluster_automated_snapshot/redshift_cluster_automated_snapshot_test.py index 2903469a45..e13981a6d2 100644 --- a/tests/providers/aws/services/redshift/redshift_cluster_automated_snapshot/redshift_cluster_automated_snapshot_test.py +++ b/tests/providers/aws/services/redshift/redshift_cluster_automated_snapshot/redshift_cluster_automated_snapshot_test.py @@ -17,6 +17,9 @@ class Test_redshift_cluster_automated_snapshot: with mock.patch( "prowler.providers.aws.services.redshift.redshift_service.Redshift", redshift_client, + ), mock.patch( + "prowler.providers.aws.services.redshift.redshift_client.redshift_client", + redshift_client, ): from prowler.providers.aws.services.redshift.redshift_cluster_automated_snapshot.redshift_cluster_automated_snapshot import ( redshift_cluster_automated_snapshot, @@ -40,6 +43,9 @@ class Test_redshift_cluster_automated_snapshot: with mock.patch( "prowler.providers.aws.services.redshift.redshift_service.Redshift", redshift_client, + ), mock.patch( + "prowler.providers.aws.services.redshift.redshift_client.redshift_client", + redshift_client, ): from prowler.providers.aws.services.redshift.redshift_cluster_automated_snapshot.redshift_cluster_automated_snapshot import ( redshift_cluster_automated_snapshot, @@ -69,6 +75,9 @@ class Test_redshift_cluster_automated_snapshot: with mock.patch( "prowler.providers.aws.services.redshift.redshift_service.Redshift", redshift_client, + ), mock.patch( + "prowler.providers.aws.services.redshift.redshift_client.redshift_client", + redshift_client, ): from prowler.providers.aws.services.redshift.redshift_cluster_automated_snapshot.redshift_cluster_automated_snapshot import ( redshift_cluster_automated_snapshot, diff --git a/tests/providers/aws/services/redshift/redshift_cluster_automatic_upgrades/redshift_cluster_automatic_upgrades_test.py b/tests/providers/aws/services/redshift/redshift_cluster_automatic_upgrades/redshift_cluster_automatic_upgrades_test.py index 933f4aa204..2f35eae7f4 100644 --- a/tests/providers/aws/services/redshift/redshift_cluster_automatic_upgrades/redshift_cluster_automatic_upgrades_test.py +++ b/tests/providers/aws/services/redshift/redshift_cluster_automatic_upgrades/redshift_cluster_automatic_upgrades_test.py @@ -17,6 +17,9 @@ class Test_redshift_cluster_automatic_upgrades: with mock.patch( "prowler.providers.aws.services.redshift.redshift_service.Redshift", redshift_client, + ), mock.patch( + "prowler.providers.aws.services.redshift.redshift_client.redshift_client", + redshift_client, ): from prowler.providers.aws.services.redshift.redshift_cluster_automatic_upgrades.redshift_cluster_automatic_upgrades import ( redshift_cluster_automatic_upgrades, @@ -40,6 +43,9 @@ class Test_redshift_cluster_automatic_upgrades: with mock.patch( "prowler.providers.aws.services.redshift.redshift_service.Redshift", redshift_client, + ), mock.patch( + "prowler.providers.aws.services.redshift.redshift_client.redshift_client", + redshift_client, ): from prowler.providers.aws.services.redshift.redshift_cluster_automatic_upgrades.redshift_cluster_automatic_upgrades import ( redshift_cluster_automatic_upgrades, @@ -69,6 +75,9 @@ class Test_redshift_cluster_automatic_upgrades: with mock.patch( "prowler.providers.aws.services.redshift.redshift_service.Redshift", redshift_client, + ), mock.patch( + "prowler.providers.aws.services.redshift.redshift_client.redshift_client", + redshift_client, ): from prowler.providers.aws.services.redshift.redshift_cluster_automatic_upgrades.redshift_cluster_automatic_upgrades import ( redshift_cluster_automatic_upgrades, diff --git a/tests/providers/aws/services/redshift/redshift_cluster_encrypted_at_rest/redshift_cluster_encrypted_at_rest_test.py b/tests/providers/aws/services/redshift/redshift_cluster_encrypted_at_rest/redshift_cluster_encrypted_at_rest_test.py new file mode 100644 index 0000000000..a4af83a3bb --- /dev/null +++ b/tests/providers/aws/services/redshift/redshift_cluster_encrypted_at_rest/redshift_cluster_encrypted_at_rest_test.py @@ -0,0 +1,132 @@ +from unittest import mock +from uuid import uuid4 + +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, +) + +CLUSTER_ID = str(uuid4()) +CLUSTER_ARN = ( + f"arn:aws:redshift:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:cluster:{CLUSTER_ID}" +) + + +class Test_redshift_cluster_encrypted: + def test_no_clusters(self): + from prowler.providers.aws.services.redshift.redshift_service import Redshift + + 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.redshift.redshift_cluster_encrypted_at_rest.redshift_cluster_encrypted_at_rest.redshift_client", + new=Redshift(aws_provider), + ): + from prowler.providers.aws.services.redshift.redshift_cluster_encrypted_at_rest.redshift_cluster_encrypted_at_rest import ( + redshift_cluster_encrypted_at_rest, + ) + + check = redshift_cluster_encrypted_at_rest() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_cluster_not_encrypted(self): + redshift_client = client("redshift", region_name=AWS_REGION_EU_WEST_1) + redshift_client.create_cluster( + DBName="test", + ClusterIdentifier=CLUSTER_ID, + ClusterType="single-node", + NodeType="ds2.xlarge", + MasterUsername="user", + MasterUserPassword="password", + PubliclyAccessible=True, + Tags=[ + {"Key": "test", "Value": "test"}, + ], + Port=9439, + Encrypted=False, + ) + from prowler.providers.aws.services.redshift.redshift_service import Redshift + + 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.redshift.redshift_cluster_encrypted_at_rest.redshift_cluster_encrypted_at_rest.redshift_client", + new=Redshift(aws_provider), + ): + from prowler.providers.aws.services.redshift.redshift_cluster_encrypted_at_rest.redshift_cluster_encrypted_at_rest import ( + redshift_cluster_encrypted_at_rest, + ) + + check = redshift_cluster_encrypted_at_rest() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].status_extended == ( + f"Redshift Cluster {CLUSTER_ID} is not encrypted at rest." + ) + assert result[0].resource_id == CLUSTER_ID + assert result[0].resource_arn == CLUSTER_ARN + assert result[0].region == AWS_REGION_EU_WEST_1 + assert result[0].resource_tags == [{"Key": "test", "Value": "test"}] + + @mock_aws + def test_cluster_encrypted(self): + redshift_client = client("redshift", region_name=AWS_REGION_EU_WEST_1) + redshift_client.create_cluster( + DBName="test", + ClusterIdentifier=CLUSTER_ID, + ClusterType="single-node", + NodeType="ds2.xlarge", + MasterUsername="user", + MasterUserPassword="password", + PubliclyAccessible=True, + Tags=[ + {"Key": "test", "Value": "test"}, + ], + Port=9439, + Encrypted=True, + ) + from prowler.providers.aws.services.redshift.redshift_service import Redshift + + 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.redshift.redshift_cluster_encrypted_at_rest.redshift_cluster_encrypted_at_rest.redshift_client", + new=Redshift(aws_provider), + ): + from prowler.providers.aws.services.redshift.redshift_cluster_encrypted_at_rest.redshift_cluster_encrypted_at_rest import ( + redshift_cluster_encrypted_at_rest, + ) + + check = redshift_cluster_encrypted_at_rest() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].status_extended == ( + f"Redshift Cluster {CLUSTER_ID} is encrypted at rest." + ) + assert result[0].resource_id == CLUSTER_ID + assert result[0].resource_arn == CLUSTER_ARN + assert result[0].region == AWS_REGION_EU_WEST_1 + assert result[0].resource_tags == [{"Key": "test", "Value": "test"}] diff --git a/tests/providers/aws/services/redshift/redshift_cluster_in_transit_encryption_enabled/redshift_cluster_in_transit_encryption_enabled_test.py b/tests/providers/aws/services/redshift/redshift_cluster_in_transit_encryption_enabled/redshift_cluster_in_transit_encryption_enabled_test.py new file mode 100644 index 0000000000..eeccf6827d --- /dev/null +++ b/tests/providers/aws/services/redshift/redshift_cluster_in_transit_encryption_enabled/redshift_cluster_in_transit_encryption_enabled_test.py @@ -0,0 +1,160 @@ +from unittest import mock +from uuid import uuid4 + +import botocore +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, +) + +CLUSTER_ID = str(uuid4()) +CLUSTER_ARN = ( + f"arn:aws:redshift:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:cluster:{CLUSTER_ID}" +) + +make_api_call = botocore.client.BaseClient._make_api_call + + +def mock_make_api_call(self, operation_name, kwarg): + if operation_name == "DescribeClusterParameters": + return { + "Parameters": [ + { + "ParameterName": "require_ssl", + "ParameterValue": "true", + "Description": "Require SSL for connections", + "Source": "user", + "DataType": "boolean", + "AllowedValues": "true, false", + "IsModifiable": True, + "MinimumEngineVersion": "1.0", + }, + ] + } + + return make_api_call(self, operation_name, kwarg) + + +class Test_redshift_cluster_in_transit_encryption_enabled: + def test_no_clusters(self): + from prowler.providers.aws.services.redshift.redshift_service import Redshift + + 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.redshift.redshift_cluster_in_transit_encryption_enabled.redshift_cluster_in_transit_encryption_enabled.redshift_client", + new=Redshift(aws_provider), + ): + from prowler.providers.aws.services.redshift.redshift_cluster_in_transit_encryption_enabled.redshift_cluster_in_transit_encryption_enabled import ( + redshift_cluster_in_transit_encryption_enabled, + ) + + check = redshift_cluster_in_transit_encryption_enabled() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_cluster_not_encrypted_in_transit(self): + redshift_client = client("redshift", region_name=AWS_REGION_EU_WEST_1) + redshift_client.create_cluster( + DBName="test", + ClusterIdentifier=CLUSTER_ID, + ClusterType="single-node", + NodeType="ds2.xlarge", + MasterUsername="awsuser", + MasterUserPassword="password", + PubliclyAccessible=True, + Tags=[ + {"Key": "test", "Value": "test"}, + ], + Port=9439, + Encrypted=False, + ) + from prowler.providers.aws.services.redshift.redshift_service import Redshift + + 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.redshift.redshift_cluster_in_transit_encryption_enabled.redshift_cluster_in_transit_encryption_enabled.redshift_client", + new=Redshift(aws_provider), + ): + from prowler.providers.aws.services.redshift.redshift_cluster_in_transit_encryption_enabled.redshift_cluster_in_transit_encryption_enabled import ( + redshift_cluster_in_transit_encryption_enabled, + ) + + check = redshift_cluster_in_transit_encryption_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].status_extended == ( + f"Redshift Cluster {CLUSTER_ID} is not encrypted in transit." + ) + assert result[0].resource_id == CLUSTER_ID + assert result[0].resource_arn == CLUSTER_ARN + assert result[0].region == AWS_REGION_EU_WEST_1 + assert result[0].resource_tags == [{"Key": "test", "Value": "test"}] + + @mock_aws + def test_cluster_encrypted_in_transit(self): + with mock.patch( + "botocore.client.BaseClient._make_api_call", new=mock_make_api_call + ): + redshift_client = client("redshift", region_name=AWS_REGION_EU_WEST_1) + redshift_client.create_cluster( + DBName="test", + ClusterIdentifier=CLUSTER_ID, + ClusterType="single-node", + NodeType="ds2.xlarge", + MasterUsername="user", + MasterUserPassword="password", + PubliclyAccessible=True, + Tags=[ + {"Key": "test", "Value": "test"}, + ], + Port=9439, + Encrypted=True, + ) + from prowler.providers.aws.services.redshift.redshift_service import ( + Redshift, + ) + + 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.redshift.redshift_cluster_in_transit_encryption_enabled.redshift_cluster_in_transit_encryption_enabled.redshift_client", + new=Redshift(aws_provider), + ): + from prowler.providers.aws.services.redshift.redshift_cluster_in_transit_encryption_enabled.redshift_cluster_in_transit_encryption_enabled import ( + redshift_cluster_in_transit_encryption_enabled, + ) + + check = redshift_cluster_in_transit_encryption_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].status_extended == ( + f"Redshift Cluster {CLUSTER_ID} is encrypted in transit." + ) + assert result[0].resource_id == CLUSTER_ID + assert result[0].resource_arn == CLUSTER_ARN + assert result[0].region == AWS_REGION_EU_WEST_1 + assert result[0].resource_tags == [{"Key": "test", "Value": "test"}] diff --git a/tests/providers/aws/services/redshift/redshift_cluster_non_default_database_name/redshift_cluster_non_default_database_name_test.py b/tests/providers/aws/services/redshift/redshift_cluster_non_default_database_name/redshift_cluster_non_default_database_name_test.py new file mode 100644 index 0000000000..cdeb0dd422 --- /dev/null +++ b/tests/providers/aws/services/redshift/redshift_cluster_non_default_database_name/redshift_cluster_non_default_database_name_test.py @@ -0,0 +1,132 @@ +from unittest import mock +from uuid import uuid4 + +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, +) + +CLUSTER_ID = str(uuid4()) +CLUSTER_ARN = ( + f"arn:aws:redshift:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:cluster:{CLUSTER_ID}" +) + + +class Test_redshift_cluster_non_default_database_name: + def test_no_clusters(self): + from prowler.providers.aws.services.redshift.redshift_service import Redshift + + 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.redshift.redshift_cluster_non_default_database_name.redshift_cluster_non_default_database_name.redshift_client", + new=Redshift(aws_provider), + ): + from prowler.providers.aws.services.redshift.redshift_cluster_non_default_database_name.redshift_cluster_non_default_database_name import ( + redshift_cluster_non_default_database_name, + ) + + check = redshift_cluster_non_default_database_name() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_cluster_default_database_name(self): + redshift_client = client("redshift", region_name=AWS_REGION_EU_WEST_1) + redshift_client.create_cluster( + DBName="dev", + ClusterIdentifier=CLUSTER_ID, + ClusterType="single-node", + NodeType="ds2.xlarge", + MasterUsername="awsuser", + MasterUserPassword="password", + PubliclyAccessible=True, + Tags=[ + {"Key": "test", "Value": "test"}, + ], + Port=9439, + Encrypted=False, + ) + from prowler.providers.aws.services.redshift.redshift_service import Redshift + + 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.redshift.redshift_cluster_non_default_database_name.redshift_cluster_non_default_database_name.redshift_client", + new=Redshift(aws_provider), + ): + from prowler.providers.aws.services.redshift.redshift_cluster_non_default_database_name.redshift_cluster_non_default_database_name import ( + redshift_cluster_non_default_database_name, + ) + + check = redshift_cluster_non_default_database_name() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].status_extended == ( + f"Redshift Cluster {CLUSTER_ID} has the default database name: dev." + ) + assert result[0].resource_id == CLUSTER_ID + assert result[0].resource_arn == CLUSTER_ARN + assert result[0].region == AWS_REGION_EU_WEST_1 + assert result[0].resource_tags == [{"Key": "test", "Value": "test"}] + + @mock_aws + def test_cluster_non_default_database_name(self): + redshift_client = client("redshift", region_name=AWS_REGION_EU_WEST_1) + redshift_client.create_cluster( + DBName="test", + ClusterIdentifier=CLUSTER_ID, + ClusterType="single-node", + NodeType="ds2.xlarge", + MasterUsername="user", + MasterUserPassword="password", + PubliclyAccessible=True, + Tags=[ + {"Key": "test", "Value": "test"}, + ], + Port=9439, + Encrypted=True, + ) + from prowler.providers.aws.services.redshift.redshift_service import Redshift + + 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.redshift.redshift_cluster_non_default_database_name.redshift_cluster_non_default_database_name.redshift_client", + new=Redshift(aws_provider), + ): + from prowler.providers.aws.services.redshift.redshift_cluster_non_default_database_name.redshift_cluster_non_default_database_name import ( + redshift_cluster_non_default_database_name, + ) + + check = redshift_cluster_non_default_database_name() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].status_extended == ( + f"Redshift Cluster {CLUSTER_ID} does not have the default database name." + ) + assert result[0].resource_id == CLUSTER_ID + assert result[0].resource_arn == CLUSTER_ARN + assert result[0].region == AWS_REGION_EU_WEST_1 + assert result[0].resource_tags == [{"Key": "test", "Value": "test"}] diff --git a/tests/providers/aws/services/redshift/redshift_cluster_non_default_username/redshift_cluster_non_default_username_test.py b/tests/providers/aws/services/redshift/redshift_cluster_non_default_username/redshift_cluster_non_default_username_test.py new file mode 100644 index 0000000000..446f90a5fe --- /dev/null +++ b/tests/providers/aws/services/redshift/redshift_cluster_non_default_username/redshift_cluster_non_default_username_test.py @@ -0,0 +1,132 @@ +from unittest import mock +from uuid import uuid4 + +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, +) + +CLUSTER_ID = str(uuid4()) +CLUSTER_ARN = ( + f"arn:aws:redshift:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:cluster:{CLUSTER_ID}" +) + + +class Test_redshift_cluster_non_default_username: + def test_no_clusters(self): + from prowler.providers.aws.services.redshift.redshift_service import Redshift + + 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.redshift.redshift_cluster_non_default_username.redshift_cluster_non_default_username.redshift_client", + new=Redshift(aws_provider), + ): + from prowler.providers.aws.services.redshift.redshift_cluster_non_default_username.redshift_cluster_non_default_username import ( + redshift_cluster_non_default_username, + ) + + check = redshift_cluster_non_default_username() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_cluster_default_username(self): + redshift_client = client("redshift", region_name=AWS_REGION_EU_WEST_1) + redshift_client.create_cluster( + DBName="test", + ClusterIdentifier=CLUSTER_ID, + ClusterType="single-node", + NodeType="ds2.xlarge", + MasterUsername="awsuser", + MasterUserPassword="password", + PubliclyAccessible=True, + Tags=[ + {"Key": "test", "Value": "test"}, + ], + Port=9439, + Encrypted=False, + ) + from prowler.providers.aws.services.redshift.redshift_service import Redshift + + 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.redshift.redshift_cluster_non_default_username.redshift_cluster_non_default_username.redshift_client", + new=Redshift(aws_provider), + ): + from prowler.providers.aws.services.redshift.redshift_cluster_non_default_username.redshift_cluster_non_default_username import ( + redshift_cluster_non_default_username, + ) + + check = redshift_cluster_non_default_username() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].status_extended == ( + f"Redshift Cluster {CLUSTER_ID} has the default Admin username." + ) + assert result[0].resource_id == CLUSTER_ID + assert result[0].resource_arn == CLUSTER_ARN + assert result[0].region == AWS_REGION_EU_WEST_1 + assert result[0].resource_tags == [{"Key": "test", "Value": "test"}] + + @mock_aws + def test_cluster_non_default_username(self): + redshift_client = client("redshift", region_name=AWS_REGION_EU_WEST_1) + redshift_client.create_cluster( + DBName="test", + ClusterIdentifier=CLUSTER_ID, + ClusterType="single-node", + NodeType="ds2.xlarge", + MasterUsername="user", + MasterUserPassword="password", + PubliclyAccessible=True, + Tags=[ + {"Key": "test", "Value": "test"}, + ], + Port=9439, + Encrypted=True, + ) + from prowler.providers.aws.services.redshift.redshift_service import Redshift + + 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.redshift.redshift_cluster_non_default_username.redshift_cluster_non_default_username.redshift_client", + new=Redshift(aws_provider), + ): + from prowler.providers.aws.services.redshift.redshift_cluster_non_default_username.redshift_cluster_non_default_username import ( + redshift_cluster_non_default_username, + ) + + check = redshift_cluster_non_default_username() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].status_extended == ( + f"Redshift Cluster {CLUSTER_ID} does not have the default Admin username." + ) + assert result[0].resource_id == CLUSTER_ID + assert result[0].resource_arn == CLUSTER_ARN + assert result[0].region == AWS_REGION_EU_WEST_1 + assert result[0].resource_tags == [{"Key": "test", "Value": "test"}] diff --git a/tests/providers/aws/services/redshift/redshift_cluster_public_access/redshift_cluster_public_access_test.py b/tests/providers/aws/services/redshift/redshift_cluster_public_access/redshift_cluster_public_access_test.py index e7250f9a13..d6e4070e0e 100644 --- a/tests/providers/aws/services/redshift/redshift_cluster_public_access/redshift_cluster_public_access_test.py +++ b/tests/providers/aws/services/redshift/redshift_cluster_public_access/redshift_cluster_public_access_test.py @@ -17,6 +17,9 @@ class Test_redshift_cluster_public_access: with mock.patch( "prowler.providers.aws.services.redshift.redshift_service.Redshift", redshift_client, + ), mock.patch( + "prowler.providers.aws.services.redshift.redshift_client.redshift_client", + redshift_client, ): from prowler.providers.aws.services.redshift.redshift_cluster_public_access.redshift_cluster_public_access import ( redshift_cluster_public_access, @@ -41,6 +44,9 @@ class Test_redshift_cluster_public_access: with mock.patch( "prowler.providers.aws.services.redshift.redshift_service.Redshift", redshift_client, + ), mock.patch( + "prowler.providers.aws.services.redshift.redshift_client.redshift_client", + redshift_client, ): from prowler.providers.aws.services.redshift.redshift_cluster_public_access.redshift_cluster_public_access import ( redshift_cluster_public_access, @@ -71,6 +77,9 @@ class Test_redshift_cluster_public_access: with mock.patch( "prowler.providers.aws.services.redshift.redshift_service.Redshift", redshift_client, + ), mock.patch( + "prowler.providers.aws.services.redshift.redshift_client.redshift_client", + redshift_client, ): from prowler.providers.aws.services.redshift.redshift_cluster_public_access.redshift_cluster_public_access import ( redshift_cluster_public_access, @@ -100,6 +109,9 @@ class Test_redshift_cluster_public_access: with mock.patch( "prowler.providers.aws.services.redshift.redshift_service.Redshift", redshift_client, + ), mock.patch( + "prowler.providers.aws.services.redshift.redshift_client.redshift_client", + redshift_client, ): from prowler.providers.aws.services.redshift.redshift_cluster_public_access.redshift_cluster_public_access import ( redshift_cluster_public_access, diff --git a/tests/providers/aws/services/redshift/redshift_service_test.py b/tests/providers/aws/services/redshift/redshift_service_test.py index 133a0a35e7..3e294a3ce9 100644 --- a/tests/providers/aws/services/redshift/redshift_service_test.py +++ b/tests/providers/aws/services/redshift/redshift_service_test.py @@ -43,6 +43,22 @@ def mock_make_api_call(self, operation_name, kwarg): }, ] } + if operation_name == "DescribeClusterParameters": + return { + "Parameters": [ + { + "ParameterName": "require_ssl", + "ParameterValue": "true", + "Description": "Require SSL for connections", + "Source": "user", + "DataType": "boolean", + "AllowedValues": "true, false", + "IsModifiable": True, + "MinimumEngineVersion": "1.0", + }, + ] + } + return make_api_call(self, operation_name, kwarg) @@ -90,9 +106,11 @@ class Test_Redshift_Service: MasterUsername="user", MasterUserPassword="password", PubliclyAccessible=True, + Encrypted=True, Tags=[ {"Key": "test", "Value": "test"}, ], + ClusterParameterGroupName="default.redshift-1.0", ) aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) redshift = Redshift(aws_provider) @@ -112,6 +130,10 @@ class Test_Redshift_Service: assert redshift.clusters[0].tags == [ {"Key": "test", "Value": "test"}, ] + assert redshift.clusters[0].parameter_group_name == "default.redshift-1.0" + assert redshift.clusters[0].encrypted + assert redshift.clusters[0].master_username == "user" + assert redshift.clusters[0].database_name == "test" @mock_aws def test_describe_logging_status(self): @@ -173,3 +195,39 @@ class Test_Redshift_Service: assert redshift.clusters[0].logging_enabled assert redshift.clusters[0].bucket == test_bucket_name assert redshift.clusters[0].cluster_snapshots + + @mock_aws + def test_describe_cluster_parameter_groups(self): + redshift_client = client("redshift", region_name=AWS_REGION_EU_WEST_1) + response = redshift_client.create_cluster( + DBName="test", + ClusterIdentifier=cluster_id, + ClusterType="single-node", + NodeType="ds2.xlarge", + MasterUsername="user", + MasterUserPassword="password", + PubliclyAccessible=True, + Tags=[ + {"Key": "test", "Value": "test"}, + ], + ) + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + redshift = Redshift(aws_provider) + + assert len(redshift.clusters) == 1 + assert redshift.clusters[0].id == cluster_id + assert redshift.clusters[0].region == AWS_REGION_EU_WEST_1 + assert redshift.clusters[0].public_access + assert ( + redshift.clusters[0].endpoint_address + == response["Cluster"]["Endpoint"]["Address"] + ) + assert ( + redshift.clusters[0].allow_version_upgrade + == response["Cluster"]["AllowVersionUpgrade"] + ) + assert redshift.clusters[0].tags == [ + {"Key": "test", "Value": "test"}, + ] + assert redshift.clusters[0].parameter_group_name == "default.redshift-1.0" + assert redshift.clusters[0].require_ssl is True diff --git a/tests/providers/aws/services/ssm/ssm_managed_compliant_patching/ssm_managed_compliant_patching_test.py b/tests/providers/aws/services/ssm/ssm_managed_compliant_patching/ssm_managed_compliant_patching_test.py index 9c9ec9ff52..dda2bce64e 100644 --- a/tests/providers/aws/services/ssm/ssm_managed_compliant_patching/ssm_managed_compliant_patching_test.py +++ b/tests/providers/aws/services/ssm/ssm_managed_compliant_patching/ssm_managed_compliant_patching_test.py @@ -15,9 +15,20 @@ class Test_ssm_managed_compliant_patching: def test_no_compliance_resources(self): ssm_client = mock.MagicMock ssm_client.compliance_resources = {} + ec2_client = mock.MagicMock + with mock.patch( "prowler.providers.aws.services.ssm.ssm_service.SSM", new=ssm_client, + ), mock.patch( + "prowler.providers.aws.services.ssm.ssm_client.ssm_client", + new=ssm_client, + ), mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider([AWS_REGION_US_EAST_1]), + ), mock.patch( + "prowler.providers.aws.services.ssm.ssm_managed_compliant_patching.ssm_managed_compliant_patching.ec2_client", + new=ec2_client, ): # Test Check from prowler.providers.aws.services.ssm.ssm_managed_compliant_patching.ssm_managed_compliant_patching import ( @@ -55,6 +66,9 @@ class Test_ssm_managed_compliant_patching: with mock.patch( "prowler.providers.aws.services.ssm.ssm_service.SSM", new=ssm_client, + ), mock.patch( + "prowler.providers.aws.services.ssm.ssm_client.ssm_client", + new=ssm_client, ), mock.patch( "prowler.providers.common.provider.Provider.get_global_provider", return_value=set_mocked_aws_provider([AWS_REGION_US_EAST_1]), @@ -113,6 +127,9 @@ class Test_ssm_managed_compliant_patching: with mock.patch( "prowler.providers.aws.services.ssm.ssm_service.SSM", new=ssm_client, + ), mock.patch( + "prowler.providers.aws.services.ssm.ssm_client.ssm_client", + new=ssm_client, ), mock.patch( "prowler.providers.common.provider.Provider.get_global_provider", return_value=set_mocked_aws_provider([AWS_REGION_US_EAST_1]), diff --git a/tests/providers/aws/services/wafv2/wafv2_service_test.py b/tests/providers/aws/services/wafv2/wafv2_service_test.py index c649ac3ec9..394678f855 100644 --- a/tests/providers/aws/services/wafv2/wafv2_service_test.py +++ b/tests/providers/aws/services/wafv2/wafv2_service_test.py @@ -45,14 +45,15 @@ class Test_WAFv2_Service: "MetricName": "idk", }, )["Summary"] + waf_arn = waf["ARN"] # WAFv2 client for this test class aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) wafv2 = WAFv2(aws_provider) assert len(wafv2.web_acls) == 1 - assert wafv2.web_acls[0].name == waf["Name"] - assert wafv2.web_acls[0].region == AWS_REGION_EU_WEST_1 - assert wafv2.web_acls[0].arn == waf["ARN"] - assert wafv2.web_acls[0].id == waf["Id"] + assert wafv2.web_acls[waf_arn].name == waf["Name"] + assert wafv2.web_acls[waf_arn].region == AWS_REGION_EU_WEST_1 + assert wafv2.web_acls[waf_arn].arn == waf["ARN"] + assert wafv2.web_acls[waf_arn].id == waf["Id"] # Test WAFv2 Describe Web ACLs Resources @mock_aws @@ -70,6 +71,7 @@ class Test_WAFv2_Service: "MetricName": "idk", }, )["Summary"] + waf_arn = waf["ARN"] security_group = ec2.create_security_group( GroupName="a-security-group", Description="First One" ) @@ -97,10 +99,10 @@ class Test_WAFv2_Service: # WAFv2 client for this test class aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) wafv2 = WAFv2(aws_provider) - wafv2.web_acls[0].albs.append(lb["LoadBalancerArn"]) + wafv2.web_acls[waf_arn].albs.append(lb["LoadBalancerArn"]) assert len(wafv2.web_acls) == 1 - assert len(wafv2.web_acls[0].albs) == 1 - assert lb["LoadBalancerArn"] in wafv2.web_acls[0].albs + assert len(wafv2.web_acls[waf_arn].albs) == 1 + assert lb["LoadBalancerArn"] in wafv2.web_acls[waf_arn].albs # Test WAFv2 describe Web user pools @mock_aws @@ -117,15 +119,16 @@ class Test_WAFv2_Service: "MetricName": "idk", }, )["Summary"] + waf_arn = waf["ARN"] user_pool = cognito.create_user_pool(PoolName="my-user-pool")["UserPool"] wafv2.associate_web_acl(WebACLArn=waf["ARN"], ResourceArn=user_pool["Arn"]) # WAFv2 client for this test class aws = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) wafv2 = WAFv2(aws) - wafv2.web_acls[0].user_pools.append(user_pool["Arn"]) + wafv2.web_acls[waf_arn].user_pools.append(user_pool["Arn"]) assert len(wafv2.web_acls) == 1 - assert len(wafv2.web_acls[0].user_pools) == 1 - assert user_pool["Arn"] in wafv2.web_acls[0].user_pools + assert len(wafv2.web_acls[waf_arn].user_pools) == 1 + assert user_pool["Arn"] in wafv2.web_acls[waf_arn].user_pools @mock_aws def test_list_tags(self): @@ -143,10 +146,11 @@ class Test_WAFv2_Service: wafv2.tag_resource( ResourceARN=waf["ARN"], Tags=[{"Key": "Name", "Value": "my-web-acl"}] ) + waf_arn = waf["ARN"] # WAFv2 client for this test class aws = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) wafv2 = WAFv2(aws) assert len(wafv2.web_acls) == 1 - assert len(wafv2.web_acls[0].tags) == 1 - assert wafv2.web_acls[0].tags[0]["Key"] == "Name" - assert wafv2.web_acls[0].tags[0]["Value"] == "my-web-acl" + assert len(wafv2.web_acls[waf_arn].tags) == 1 + assert wafv2.web_acls[waf_arn].tags[0]["Key"] == "Name" + assert wafv2.web_acls[waf_arn].tags[0]["Value"] == "my-web-acl" diff --git a/tests/providers/aws/services/wafv2/wafv2_webacl_logging_enabled/wafv2_webacl_logging_enabled_test.py b/tests/providers/aws/services/wafv2/wafv2_webacl_logging_enabled/wafv2_webacl_logging_enabled_test.py index 8e0f02b7fa..fcb2aaa810 100644 --- a/tests/providers/aws/services/wafv2/wafv2_webacl_logging_enabled/wafv2_webacl_logging_enabled_test.py +++ b/tests/providers/aws/services/wafv2/wafv2_webacl_logging_enabled/wafv2_webacl_logging_enabled_test.py @@ -12,7 +12,7 @@ waf_arn = f"arn:aws:wafv2:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:regional/w class Test_wafv2_webacl_logging_enabled: def test_no_web_acls(self): wafv2_client = mock.MagicMock - wafv2_client.web_acls = [] + wafv2_client.web_acls = {} with mock.patch( "prowler.providers.aws.services.wafv2.wafv2_service.WAFv2", new=wafv2_client, @@ -30,10 +30,9 @@ class Test_wafv2_webacl_logging_enabled: def test_wafv2_wb_acl_with_logging(self): wafv2_client = mock.MagicMock - wafv2_client.web_acls = [] wafv2_client.enabled = True - wafv2_client.web_acls.append( - WebAclv2( + wafv2_client.web_acls = { + waf_arn: WebAclv2( arn=waf_arn, name=waf_name, id=waf_id, @@ -43,7 +42,7 @@ class Test_wafv2_webacl_logging_enabled: logging_enabled=True, tags=[{"Key": "Name", "Value": waf_name}], ) - ) + } with mock.patch( "prowler.providers.aws.services.wafv2.wafv2_service.WAFv2", new=wafv2_client, @@ -70,10 +69,10 @@ class Test_wafv2_webacl_logging_enabled: def test_wafv2_wb_acl_without_logging(self): wafv2_client = mock.MagicMock - wafv2_client.web_acls = [] + wafv2_client.web_acls = {} wafv2_client.enabled = True - wafv2_client.web_acls.append( - WebAclv2( + wafv2_client.web_acls = { + waf_arn: WebAclv2( arn=waf_arn, name=waf_name, id=waf_id, @@ -83,7 +82,7 @@ class Test_wafv2_webacl_logging_enabled: logging_enabled=False, tags=[{"Key": "Name", "Value": waf_name}], ) - ) + } with mock.patch( "prowler.providers.aws.services.wafv2.wafv2_service.WAFv2", new=wafv2_client, diff --git a/tests/providers/azure/services/aks/aks_service_test.py b/tests/providers/azure/services/aks/aks_service_test.py index 14e39949f3..13f5538574 100644 --- a/tests/providers/azure/services/aks/aks_service_test.py +++ b/tests/providers/azure/services/aks/aks_service_test.py @@ -27,7 +27,7 @@ def mock_aks_get_clusters(_): "prowler.providers.azure.services.aks.aks_service.AKS._get_clusters", new=mock_aks_get_clusters, ) -class Test_AppInsights_Service: +class Test_AKS_Service: def test_get_client(self): aks = AKS(set_mocked_azure_provider()) assert ( diff --git a/tests/providers/azure/services/vm/vm_service_test.py b/tests/providers/azure/services/vm/vm_service_test.py index 5ebb3cd618..2d876f7308 100644 --- a/tests/providers/azure/services/vm/vm_service_test.py +++ b/tests/providers/azure/services/vm/vm_service_test.py @@ -62,7 +62,7 @@ def mock_vm_get_disks(_): "prowler.providers.azure.services.vm.vm_service.VirtualMachines._get_disks", new=mock_vm_get_disks, ) -class Test_AppInsights_Service: +class Test_VirtualMachines_Service: def test_get_client(self): app_insights = VirtualMachines(set_mocked_azure_provider()) assert (