mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-01-25 02:08:11 +00:00
Compare commits
11 Commits
chore/ui-e
...
4.3.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a35fbec7ff | ||
|
|
11ca3b59bc | ||
|
|
cfd2165b26 | ||
|
|
6acf8d6404 | ||
|
|
ece220a71d | ||
|
|
8adc72ad57 | ||
|
|
9addf86aa5 | ||
|
|
2913d50a52 | ||
|
|
c6c06b3354 | ||
|
|
8242fa883e | ||
|
|
6646bae26c |
13
.github/workflows/build-lint-push-containers.yml
vendored
13
.github/workflows/build-lint-push-containers.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
prowler_version_major: ${{ steps.get-prowler-version.outputs.PROWLER_VERSION_MAJOR }}
|
||||
prowler_version: ${{ steps.update-prowler-version.outputs.PROWLER_VERSION }}
|
||||
prowler_version: ${{ steps.get-prowler-version.outputs.PROWLER_VERSION }}
|
||||
env:
|
||||
POETRY_VIRTUALENVS_CREATE: "false"
|
||||
|
||||
@@ -65,6 +65,8 @@ jobs:
|
||||
id: get-prowler-version
|
||||
run: |
|
||||
PROWLER_VERSION="$(poetry version -s 2>/dev/null)"
|
||||
echo "PROWLER_VERSION=${PROWLER_VERSION}" >> "${GITHUB_ENV}"
|
||||
echo "PROWLER_VERSION=${PROWLER_VERSION}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
# Store prowler version major just for the release
|
||||
PROWLER_VERSION_MAJOR="${PROWLER_VERSION%%.*}"
|
||||
@@ -89,15 +91,6 @@ jobs:
|
||||
;;
|
||||
esac
|
||||
|
||||
- name: Update Prowler version (release)
|
||||
id: update-prowler-version
|
||||
if: github.event_name == 'release'
|
||||
run: |
|
||||
PROWLER_VERSION="${{ github.event.release.tag_name }}"
|
||||
poetry version "${PROWLER_VERSION}"
|
||||
echo "PROWLER_VERSION=${PROWLER_VERSION}" >> "${GITHUB_ENV}"
|
||||
echo "PROWLER_VERSION=${PROWLER_VERSION}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
|
||||
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -13,10 +13,10 @@ name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master", "v3" ]
|
||||
branches: [ "master", "v3", "v4.*" ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ "master", "v3" ]
|
||||
branches: [ "master", "v3", "v4.*" ]
|
||||
schedule:
|
||||
- cron: '00 12 * * *'
|
||||
|
||||
|
||||
2
.github/workflows/pull-request.yml
vendored
2
.github/workflows/pull-request.yml
vendored
@@ -5,10 +5,12 @@ on:
|
||||
branches:
|
||||
- "master"
|
||||
- "v3"
|
||||
- "v4.*"
|
||||
pull_request:
|
||||
branches:
|
||||
- "master"
|
||||
- "v3"
|
||||
- "v4.*"
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
21
.github/workflows/pypi-release.yml
vendored
21
.github/workflows/pypi-release.yml
vendored
@@ -40,7 +40,6 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pipx install poetry
|
||||
pipx inject poetry poetry-bumpversion
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
@@ -48,10 +47,6 @@ jobs:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
cache: ${{ env.CACHE }}
|
||||
|
||||
- name: Update Poetry and config version
|
||||
run: |
|
||||
poetry version ${{ env.RELEASE_TAG }}
|
||||
|
||||
- name: Import GPG key
|
||||
uses: crazy-max/ghaction-import-gpg@v6
|
||||
with:
|
||||
@@ -60,22 +55,6 @@ jobs:
|
||||
git_user_signingkey: true
|
||||
git_commit_gpgsign: true
|
||||
|
||||
- name: Push updated version to the release tag
|
||||
run: |
|
||||
# Configure Git
|
||||
git config user.name "github-actions"
|
||||
git config user.email "${{ env.GIT_COMMITTER_EMAIL }}"
|
||||
|
||||
# Add the files with the version changed
|
||||
git add prowler/config/config.py pyproject.toml
|
||||
git commit -m "chore(release): ${{ env.RELEASE_TAG }}" --no-verify -S
|
||||
|
||||
# Replace the tag with the version updated
|
||||
git tag -fa ${{ env.RELEASE_TAG }} -m "chore(release): ${{ env.RELEASE_TAG }}" --sign
|
||||
|
||||
# Push the tag
|
||||
git push -f origin ${{ env.RELEASE_TAG }}
|
||||
|
||||
- name: Build Prowler package
|
||||
run: |
|
||||
poetry build
|
||||
|
||||
@@ -7,97 +7,121 @@ Mutelist option works along with other options and will modify the output in the
|
||||
- CSV: `muted` is `True`. The field `status` will keep the original status, `MANUAL`, `PASS` or `FAIL`, of the finding.
|
||||
|
||||
|
||||
You can use `-w`/`--mutelist-file` with the path of your mutelist yaml file:
|
||||
## How the Mutelist Works
|
||||
|
||||
The Mutelist uses an "ANDed" and "ORed" logic to determine which resources, checks, regions, and tags should be muted. For each check, the Mutelist checks if the account, region, and resource match the specified criteria, using an "ANDed" logic. If tags are specified, the mutelist uses and "ORed" logic to see if at least one tag is present in the resource.
|
||||
|
||||
If any of the criteria do not match, the check is not muted.
|
||||
|
||||
## Mutelist Specification
|
||||
|
||||
???+ note
|
||||
- For Azure provider, the Account ID is the Subscription Name and the Region is the Location.
|
||||
- For GCP provider, the Account ID is the Project ID and the Region is the Zone.
|
||||
- For Kubernetes provider, the Account ID is the Cluster Name and the Region is the Namespace.
|
||||
|
||||
The Mutelist file uses the [YAML](https://en.wikipedia.org/wiki/YAML) format with the following syntax:
|
||||
|
||||
```yaml
|
||||
### Account, Check and/or Region can be * to apply for all the cases.
|
||||
### Resources and tags are lists that can have either Regex or Keywords.
|
||||
### Tags is an optional list that matches on tuples of 'key=value' and are "ANDed" together.
|
||||
### Use an alternation Regex to match one of multiple tags with "ORed" logic.
|
||||
### For each check you can except Accounts, Regions, Resources and/or Tags.
|
||||
########################### MUTELIST EXAMPLE ###########################
|
||||
Mutelist:
|
||||
Accounts:
|
||||
"123456789012":
|
||||
Checks:
|
||||
"iam_user_hardware_mfa_enabled":
|
||||
Regions:
|
||||
- "us-east-1"
|
||||
Resources:
|
||||
- "user-1" # Will ignore user-1 in check iam_user_hardware_mfa_enabled
|
||||
- "user-2" # Will ignore user-2 in check iam_user_hardware_mfa_enabled
|
||||
"ec2_*":
|
||||
Regions:
|
||||
- "*"
|
||||
Resources:
|
||||
- "*" # Will ignore every EC2 check in every account and region
|
||||
"*":
|
||||
Regions:
|
||||
- "*"
|
||||
Resources:
|
||||
- "test"
|
||||
Tags:
|
||||
- "test=test" # Will ignore every resource containing the string "test" and the tags 'test=test' and
|
||||
- "project=test|project=stage" # either of ('project=test' OR project=stage) in account 123456789012 and every region
|
||||
|
||||
"*":
|
||||
Checks:
|
||||
"s3_bucket_object_versioning":
|
||||
Regions:
|
||||
- "eu-west-1"
|
||||
- "us-east-1"
|
||||
Resources:
|
||||
- "ci-logs" # Will ignore bucket "ci-logs" AND ALSO bucket "ci-logs-replica" in specified check and regions
|
||||
- "logs" # Will ignore EVERY BUCKET containing the string "logs" in specified check and regions
|
||||
- ".+-logs" # Will ignore all buckets containing the terms ci-logs, qa-logs, etc. in specified check and regions
|
||||
"ecs_task_definitions_no_environment_secrets":
|
||||
Regions:
|
||||
- "*"
|
||||
Resources:
|
||||
- "*"
|
||||
Exceptions:
|
||||
Accounts:
|
||||
- "0123456789012"
|
||||
Regions:
|
||||
- "eu-west-1"
|
||||
- "eu-south-2" # Will ignore every resource in check ecs_task_definitions_no_environment_secrets except the ones in account 0123456789012 located in eu-south-2 or eu-west-1
|
||||
"*":
|
||||
Regions:
|
||||
- "*"
|
||||
Resources:
|
||||
- "*"
|
||||
Tags:
|
||||
- "environment=dev" # Will ignore every resource containing the tag 'environment=dev' in every account and region
|
||||
|
||||
"123456789012":
|
||||
Checks:
|
||||
"*":
|
||||
Regions:
|
||||
- "*"
|
||||
Resources:
|
||||
- "*"
|
||||
Exceptions:
|
||||
Resources:
|
||||
- "test"
|
||||
Tags:
|
||||
- "environment=prod" # Will ignore every resource except in account 123456789012 except the ones containing the string "test" and tag environment=prod
|
||||
```
|
||||
|
||||
### Account, Check, Region, Resource, and Tag
|
||||
|
||||
| Field | Description | Logic |
|
||||
|----------|----------|----------|
|
||||
| `<account_id>` | Use `*` to apply the mutelist to all accounts. | `ANDed` |
|
||||
| `<check_name>` | The name of the Prowler check. Use `*` to apply the mutelist to all checks. | `ANDed` |
|
||||
| `<region>` | The region identifier. Use `*` to apply the mutelist to all regions. | `ANDed` |
|
||||
| `<resource>` | The resource identifier. Use `*` to apply the mutelist to all resources. | `ANDed` |
|
||||
| `<tag>` | The tag value. | `ORed` |
|
||||
|
||||
|
||||
## How to Use the Mutelist
|
||||
|
||||
To use the Mutelist, you need to specify the path to the Mutelist YAML file using the `-w` or `--mutelist-file` option when running Prowler:
|
||||
|
||||
```
|
||||
prowler <provider> -w mutelist.yaml
|
||||
```
|
||||
|
||||
## Mutelist YAML File Syntax
|
||||
Replace `<provider>` with the appropriate provider name.
|
||||
|
||||
???+ note
|
||||
For Azure provider, the Account ID is the Subscription Name and the Region is the Location.
|
||||
## Considerations
|
||||
|
||||
???+ note
|
||||
For GCP provider, the Account ID is the Project ID and the Region is the Zone.
|
||||
- The Mutelist can be used in combination with other Prowler options, such as the `--service` or `--checks` option, to further customize the scanning process.
|
||||
- Make sure to review and update the Mutelist regularly to ensure it reflects the desired exclusions and remains up to date with your infrastructure.
|
||||
|
||||
???+ note
|
||||
For Kubernetes provider, the Account ID is the Cluster Name and the Region is the Namespace.
|
||||
|
||||
The Mutelist file is a YAML file with the following syntax:
|
||||
|
||||
```yaml
|
||||
### Account, Check and/or Region can be * to apply for all the cases.
|
||||
### Resources and tags are lists that can have either Regex or Keywords.
|
||||
### Tags is an optional list that matches on tuples of 'key=value' and are "ANDed" together.
|
||||
### Use an alternation Regex to match one of multiple tags with "ORed" logic.
|
||||
### For each check you can except Accounts, Regions, Resources and/or Tags.
|
||||
########################### MUTELIST EXAMPLE ###########################
|
||||
Mutelist:
|
||||
Accounts:
|
||||
"123456789012":
|
||||
Checks:
|
||||
"iam_user_hardware_mfa_enabled":
|
||||
Regions:
|
||||
- "us-east-1"
|
||||
Resources:
|
||||
- "user-1" # Will ignore user-1 in check iam_user_hardware_mfa_enabled
|
||||
- "user-2" # Will ignore user-2 in check iam_user_hardware_mfa_enabled
|
||||
"ec2_*":
|
||||
Regions:
|
||||
- "*"
|
||||
Resources:
|
||||
- "*" # Will ignore every EC2 check in every account and region
|
||||
"*":
|
||||
Regions:
|
||||
- "*"
|
||||
Resources:
|
||||
- "test"
|
||||
Tags:
|
||||
- "test=test" # Will ignore every resource containing the string "test" and the tags 'test=test' and
|
||||
- "project=test|project=stage" # either of ('project=test' OR project=stage) in account 123456789012 and every region
|
||||
|
||||
"*":
|
||||
Checks:
|
||||
"s3_bucket_object_versioning":
|
||||
Regions:
|
||||
- "eu-west-1"
|
||||
- "us-east-1"
|
||||
Resources:
|
||||
- "ci-logs" # Will ignore bucket "ci-logs" AND ALSO bucket "ci-logs-replica" in specified check and regions
|
||||
- "logs" # Will ignore EVERY BUCKET containing the string "logs" in specified check and regions
|
||||
- ".+-logs" # Will ignore all buckets containing the terms ci-logs, qa-logs, etc. in specified check and regions
|
||||
"ecs_task_definitions_no_environment_secrets":
|
||||
Regions:
|
||||
- "*"
|
||||
Resources:
|
||||
- "*"
|
||||
Exceptions:
|
||||
Accounts:
|
||||
- "0123456789012"
|
||||
Regions:
|
||||
- "eu-west-1"
|
||||
- "eu-south-2" # Will ignore every resource in check ecs_task_definitions_no_environment_secrets except the ones in account 0123456789012 located in eu-south-2 or eu-west-1
|
||||
"*":
|
||||
Regions:
|
||||
- "*"
|
||||
Resources:
|
||||
- "*"
|
||||
Tags:
|
||||
- "environment=dev" # Will ignore every resource containing the tag 'environment=dev' in every account and region
|
||||
|
||||
"123456789012":
|
||||
Checks:
|
||||
"*":
|
||||
Regions:
|
||||
- "*"
|
||||
Resources:
|
||||
- "*"
|
||||
Exceptions:
|
||||
Resources:
|
||||
- "test"
|
||||
Tags:
|
||||
- "environment=prod" # Will ignore every resource except in account 123456789012 except the ones containing the string "test" and tag environment=prod
|
||||
```
|
||||
|
||||
## AWS Mutelist
|
||||
### Mute specific AWS regions
|
||||
|
||||
@@ -10,7 +10,7 @@ from prowler.lib.logger import logger
|
||||
|
||||
timestamp = datetime.today()
|
||||
timestamp_utc = datetime.now(timezone.utc).replace(tzinfo=timezone.utc)
|
||||
prowler_version = "4.3.1"
|
||||
prowler_version = "4.3.3"
|
||||
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"
|
||||
|
||||
@@ -211,9 +211,7 @@ class Mutelist(ABC):
|
||||
muted_in_resource = self.is_item_matched(
|
||||
muted_resources, finding_resource
|
||||
)
|
||||
muted_in_tags = self.is_item_matched(
|
||||
muted_tags, finding_tags, tag=True
|
||||
)
|
||||
muted_in_tags = self.is_item_matched(muted_tags, finding_tags)
|
||||
|
||||
# For a finding to be muted requires the following set to True:
|
||||
# - muted_in_check -> True
|
||||
@@ -281,9 +279,7 @@ class Mutelist(ABC):
|
||||
)
|
||||
|
||||
excepted_tags = exceptions.get("Tags", [])
|
||||
is_tag_excepted = self.is_item_matched(
|
||||
excepted_tags, finding_tags, tag=True
|
||||
)
|
||||
is_tag_excepted = self.is_item_matched(excepted_tags, finding_tags)
|
||||
|
||||
if (
|
||||
not is_account_excepted
|
||||
@@ -307,7 +303,7 @@ class Mutelist(ABC):
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def is_item_matched(matched_items, finding_items, tag=False):
|
||||
def is_item_matched(matched_items, finding_items):
|
||||
"""
|
||||
Check if any of the items in matched_items are present in finding_items.
|
||||
|
||||
@@ -321,15 +317,10 @@ class Mutelist(ABC):
|
||||
try:
|
||||
is_item_matched = False
|
||||
if matched_items and (finding_items or finding_items == ""):
|
||||
# If we use tags, we need to use re.search instead of re.match because we need to match the tags in the format key1=value1 | key2=value2
|
||||
if tag:
|
||||
operation = re.search
|
||||
else:
|
||||
operation = re.match
|
||||
for item in matched_items:
|
||||
if item.startswith("*"):
|
||||
item = ".*" + item[1:]
|
||||
if operation(item, finding_items):
|
||||
if re.search(item, finding_items):
|
||||
is_item_matched = True
|
||||
break
|
||||
return is_item_matched
|
||||
|
||||
@@ -25,7 +25,6 @@ class ASFF(Output):
|
||||
- transform(findings: list[Finding]) -> None: Transforms a list of findings into ASFF format.
|
||||
- batch_write_data_to_file() -> None: Writes the findings data to a file in JSON ASFF format.
|
||||
- generate_status(status: str, muted: bool = False) -> str: Generates the ASFF status based on the provided status and muted flag.
|
||||
- format_resource_tags(tags: str) -> dict: Transforms a string of tags into a dictionary format.
|
||||
|
||||
References:
|
||||
- AWS Security Hub API Reference: https://docs.aws.amazon.com/securityhub/1.0/APIReference/API_Compliance.html
|
||||
@@ -62,7 +61,6 @@ class ASFF(Output):
|
||||
if finding.status == "MANUAL":
|
||||
continue
|
||||
timestamp = timestamp_utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
resource_tags = ASFF.format_resource_tags(finding.resource_tags)
|
||||
|
||||
associated_standards, compliance_summary = ASFF.format_compliance(
|
||||
finding.compliance
|
||||
@@ -70,7 +68,6 @@ class ASFF(Output):
|
||||
|
||||
# Ensures finding_status matches allowed values in ASFF
|
||||
finding_status = ASFF.generate_status(finding.status, finding.muted)
|
||||
|
||||
self._data.append(
|
||||
AWSSecurityFindingFormat(
|
||||
# The following line cannot be changed because it is the format we use to generate unique findings for AWS Security Hub
|
||||
@@ -99,7 +96,7 @@ class ASFF(Output):
|
||||
Type=finding.resource_type,
|
||||
Partition=finding.partition,
|
||||
Region=finding.region,
|
||||
Tags=resource_tags,
|
||||
Tags=finding.resource_tags,
|
||||
)
|
||||
],
|
||||
Compliance=Compliance(
|
||||
@@ -195,42 +192,6 @@ class ASFF(Output):
|
||||
|
||||
return json_asff_status
|
||||
|
||||
@staticmethod
|
||||
def format_resource_tags(tags: str) -> dict:
|
||||
"""
|
||||
Transforms a string of tags into a dictionary format.
|
||||
|
||||
Parameters:
|
||||
- tags (str): A string containing tags separated by ' | ' and key-value pairs separated by '='.
|
||||
|
||||
Returns:
|
||||
- dict: A dictionary where keys are tag names and values are tag values.
|
||||
|
||||
Notes:
|
||||
- If the input string is empty or None, it returns None.
|
||||
- Each tag in the input string should be in the format 'key=value'.
|
||||
- If the input string is not formatted correctly, it logs an error and returns None.
|
||||
"""
|
||||
try:
|
||||
tags_dict = None
|
||||
if tags:
|
||||
tags = tags.split(" | ")
|
||||
tags_dict = {}
|
||||
for tag in tags:
|
||||
value = tag.split("=")
|
||||
tags_dict[value[0]] = value[1]
|
||||
return tags_dict
|
||||
except IndexError as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
return None
|
||||
except AttributeError as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def format_compliance(compliance: dict) -> tuple[list[dict], list[str]]:
|
||||
"""
|
||||
@@ -316,6 +277,12 @@ class Resource(BaseModel):
|
||||
Region: str
|
||||
Tags: Optional[dict]
|
||||
|
||||
@validator("Tags", pre=True, always=True)
|
||||
def tags_cannot_be_empty_dict(tags):
|
||||
if not tags:
|
||||
return None
|
||||
return tags
|
||||
|
||||
|
||||
class Compliance(BaseModel):
|
||||
"""
|
||||
|
||||
@@ -3,7 +3,7 @@ from csv import DictWriter
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.lib.outputs.finding import Finding
|
||||
from prowler.lib.outputs.output import Output
|
||||
from prowler.lib.outputs.utils import unroll_dict, unroll_list
|
||||
from prowler.lib.outputs.utils import unroll_dict
|
||||
|
||||
|
||||
class CSV(Output):
|
||||
@@ -17,8 +17,13 @@ class CSV(Output):
|
||||
try:
|
||||
for finding in findings:
|
||||
finding_dict = {k.upper(): v for k, v in finding.dict().items()}
|
||||
finding_dict["COMPLIANCE"] = unroll_dict(finding.compliance)
|
||||
finding_dict["ACCOUNT_TAGS"] = unroll_list(finding.account_tags)
|
||||
finding_dict["RESOURCE_TAGS"] = unroll_dict(finding.resource_tags)
|
||||
finding_dict["COMPLIANCE"] = unroll_dict(
|
||||
finding.compliance, separator=": "
|
||||
)
|
||||
finding_dict["ACCOUNT_TAGS"] = unroll_dict(
|
||||
finding.account_tags, separator=":"
|
||||
)
|
||||
finding_dict["STATUS"] = finding.status.value
|
||||
finding_dict["SEVERITY"] = finding.severity.value
|
||||
self._data.append(finding_dict)
|
||||
|
||||
@@ -50,7 +50,7 @@ class Finding(BaseModel):
|
||||
# Optional since it depends on permissions
|
||||
account_organization_name: Optional[str]
|
||||
# Optional since it depends on permissions
|
||||
account_tags: Optional[list[str]]
|
||||
account_tags: dict = {}
|
||||
finding_uid: str
|
||||
provider: str
|
||||
check_id: str
|
||||
@@ -66,7 +66,7 @@ class Finding(BaseModel):
|
||||
resource_uid: str
|
||||
resource_name: str
|
||||
resource_details: str
|
||||
resource_tags: str
|
||||
resource_tags: dict = {}
|
||||
# Only present for AWS and Azure
|
||||
partition: Optional[str]
|
||||
region: str
|
||||
|
||||
@@ -45,11 +45,11 @@ class HTML(Output):
|
||||
<td>{finding.check_id.replace("_", "<wbr />_")}</td>
|
||||
<td>{finding.check_title}</td>
|
||||
<td>{finding.resource_uid.replace("<", "<").replace(">", ">").replace("_", "<wbr />_")}</td>
|
||||
<td>{parse_html_string(finding.resource_tags)}</td>
|
||||
<td>{parse_html_string(unroll_dict(finding.resource_tags))}</td>
|
||||
<td>{finding.status_extended.replace("<", "<").replace(">", ">").replace("_", "<wbr />_")}</td>
|
||||
<td><p class="show-read-more">{html.escape(finding.risk)}</p></td>
|
||||
<td><p class="show-read-more">{html.escape(finding.remediation_recommendation_text)}</p> <a class="read-more" href="{finding.remediation_recommendation_url}"><i class="fas fa-external-link-alt"></i></a></td>
|
||||
<td><p class="show-read-more">{parse_html_string(unroll_dict(finding.compliance))}</p></td>
|
||||
<td><p class="show-read-more">{parse_html_string(unroll_dict(finding.compliance, separator=": "))}</p></td>
|
||||
</tr>
|
||||
"""
|
||||
)
|
||||
|
||||
@@ -20,6 +20,7 @@ from py_ocsf_models.objects.resource_details import ResourceDetails
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.lib.outputs.finding import Finding
|
||||
from prowler.lib.outputs.output import Output
|
||||
from prowler.lib.outputs.utils import unroll_dict_to_list
|
||||
|
||||
|
||||
class OCSF(Output):
|
||||
@@ -97,12 +98,7 @@ class OCSF(Output):
|
||||
risk_details=finding.risk,
|
||||
resources=[
|
||||
ResourceDetails(
|
||||
# TODO: Check labels for other providers
|
||||
labels=(
|
||||
finding.resource_tags.split(",")
|
||||
if finding.resource_tags
|
||||
else []
|
||||
),
|
||||
labels=unroll_dict_to_list(finding.resource_tags),
|
||||
name=finding.resource_name,
|
||||
uid=finding.resource_uid,
|
||||
group=Group(name=finding.service_name),
|
||||
@@ -148,7 +144,7 @@ class OCSF(Output):
|
||||
type_id=cloud_account_type.value,
|
||||
type=cloud_account_type.name,
|
||||
uid=finding.account_uid,
|
||||
labels=finding.account_tags,
|
||||
labels=unroll_dict_to_list(finding.account_tags),
|
||||
),
|
||||
org=Organization(
|
||||
uid=finding.account_organization_uid,
|
||||
|
||||
@@ -1,4 +1,24 @@
|
||||
def unroll_list(listed_items: list, separator: str = "|"):
|
||||
def unroll_list(listed_items: list, separator: str = "|") -> str:
|
||||
"""
|
||||
Unrolls a list of items into a single string, separated by a specified separator.
|
||||
|
||||
Args:
|
||||
listed_items (list): The list of items to be unrolled.
|
||||
separator (str, optional): The separator to be used between the items. Defaults to "|".
|
||||
|
||||
Returns:
|
||||
str: The unrolled string.
|
||||
|
||||
Examples:
|
||||
>>> unroll_list(['apple', 'banana', 'orange'])
|
||||
'apple | banana | orange'
|
||||
|
||||
>>> unroll_list(['apple', 'banana', 'orange'], separator=',')
|
||||
'apple, banana, orange'
|
||||
|
||||
>>> unroll_list([])
|
||||
''
|
||||
"""
|
||||
unrolled_items = ""
|
||||
if listed_items:
|
||||
for item in listed_items:
|
||||
@@ -13,70 +33,124 @@ def unroll_list(listed_items: list, separator: str = "|"):
|
||||
return unrolled_items
|
||||
|
||||
|
||||
def unroll_tags(tags: list):
|
||||
unrolled_items = ""
|
||||
separator = "|"
|
||||
def unroll_tags(tags: list) -> dict:
|
||||
"""
|
||||
Unrolls a list of tags into a dictionary.
|
||||
|
||||
Args:
|
||||
tags (list): A list of tags.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the unrolled tags.
|
||||
|
||||
Examples:
|
||||
>>> tags = [{"key": "name", "value": "John"}, {"key": "age", "value": "30"}]
|
||||
>>> unroll_tags(tags)
|
||||
{'name': 'John', 'age': '30'}
|
||||
|
||||
>>> tags = [{"Key": "name", "Value": "John"}, {"Key": "age", "Value": "30"}]
|
||||
>>> unroll_tags(tags)
|
||||
{'name': 'John', 'age': '30'}
|
||||
|
||||
>>> tags = [{"name": "John", "age": "30"}]
|
||||
>>> unroll_tags(tags)
|
||||
{'name': 'John', 'age': '30'}
|
||||
|
||||
>>> tags = []
|
||||
>>> unroll_tags(tags)
|
||||
{}
|
||||
|
||||
>>> tags = {"name": "John", "age": "30"}
|
||||
>>> unroll_tags(tags)
|
||||
{'name': 'John', 'age': '30'}
|
||||
"""
|
||||
if tags and tags != [{}] and tags != [None]:
|
||||
for item in tags:
|
||||
# Check if there are tags in list
|
||||
if isinstance(item, dict):
|
||||
for key, value in item.items():
|
||||
if not unrolled_items:
|
||||
# Check the pattern of tags (Key:Value or Key:key/Value:value)
|
||||
if "Key" != key and "Value" != key:
|
||||
unrolled_items = f"{key}={value}"
|
||||
else:
|
||||
if "Key" == key:
|
||||
unrolled_items = f"{value}="
|
||||
else:
|
||||
unrolled_items = f"{value}"
|
||||
else:
|
||||
if "Key" != key and "Value" != key:
|
||||
unrolled_items = (
|
||||
f"{unrolled_items} {separator} {key}={value}"
|
||||
)
|
||||
else:
|
||||
if "Key" == key:
|
||||
unrolled_items = (
|
||||
f"{unrolled_items} {separator} {value}="
|
||||
)
|
||||
else:
|
||||
unrolled_items = f"{unrolled_items}{value}"
|
||||
elif not unrolled_items:
|
||||
unrolled_items = f"{item}"
|
||||
else:
|
||||
unrolled_items = f"{unrolled_items} {separator} {item}"
|
||||
|
||||
return unrolled_items
|
||||
if isinstance(tags, dict):
|
||||
return tags
|
||||
if "key" in tags[0]:
|
||||
return {item["key"]: item["value"] for item in tags}
|
||||
elif "Key" in tags[0]:
|
||||
return {item["Key"]: item["Value"] for item in tags}
|
||||
else:
|
||||
return {key: value for d in tags for key, value in d.items()}
|
||||
return {}
|
||||
|
||||
|
||||
def unroll_dict(dict: dict):
|
||||
def unroll_dict(dict: dict, separator: str = "=") -> str:
|
||||
"""
|
||||
Unrolls a dictionary into a string representation.
|
||||
|
||||
Args:
|
||||
dict (dict): The dictionary to be unrolled.
|
||||
|
||||
Returns:
|
||||
str: The unrolled string representation of the dictionary.
|
||||
|
||||
Examples:
|
||||
>>> my_dict = {'name': 'John', 'age': 30, 'hobbies': ['reading', 'coding']}
|
||||
>>> unroll_dict(my_dict)
|
||||
'name: John | age: 30 | hobbies: reading, coding'
|
||||
"""
|
||||
|
||||
unrolled_items = ""
|
||||
separator = "|"
|
||||
for key, value in dict.items():
|
||||
if isinstance(value, list):
|
||||
value = ", ".join(value)
|
||||
if not unrolled_items:
|
||||
unrolled_items = f"{key}: {value}"
|
||||
unrolled_items = f"{key}{separator}{value}"
|
||||
else:
|
||||
unrolled_items = f"{unrolled_items} {separator} {key}: {value}"
|
||||
unrolled_items = f"{unrolled_items} | {key}{separator}{value}"
|
||||
|
||||
return unrolled_items
|
||||
|
||||
|
||||
def unroll_dict_to_list(dict: dict):
|
||||
def unroll_dict_to_list(dict: dict) -> list:
|
||||
"""
|
||||
Unrolls a dictionary into a list of key-value pairs.
|
||||
|
||||
Args:
|
||||
dict (dict): The dictionary to be unrolled.
|
||||
|
||||
Returns:
|
||||
list: A list of key-value pairs, where each pair is represented as a string.
|
||||
|
||||
Examples:
|
||||
>>> my_dict = {'name': 'John', 'age': 30, 'hobbies': ['reading', 'coding']}
|
||||
>>> unroll_dict_to_list(my_dict)
|
||||
['name: John', 'age: 30', 'hobbies: reading, coding']
|
||||
"""
|
||||
|
||||
dict_list = []
|
||||
for key, value in dict.items():
|
||||
if isinstance(value, list):
|
||||
value = ", ".join(value)
|
||||
dict_list.append(f"{key}: {value}")
|
||||
dict_list.append(f"{key}:{value}")
|
||||
else:
|
||||
dict_list.append(f"{key}: {value}")
|
||||
dict_list.append(f"{key}:{value}")
|
||||
|
||||
return dict_list
|
||||
|
||||
|
||||
def parse_json_tags(tags: list):
|
||||
def parse_json_tags(tags: list) -> dict[str, str]:
|
||||
"""
|
||||
Parses a list of JSON tags and returns a dictionary of key-value pairs.
|
||||
|
||||
Args:
|
||||
tags (list): A list of JSON tags.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the parsed key-value pairs from the tags.
|
||||
|
||||
Examples:
|
||||
>>> tags = [
|
||||
... {"Key": "Name", "Value": "John"},
|
||||
... {"Key": "Age", "Value": "30"},
|
||||
... {"Key": "City", "Value": "New York"}
|
||||
... ]
|
||||
>>> parse_json_tags(tags)
|
||||
{'Name': 'John', 'Age': '30', 'City': 'New York'}
|
||||
"""
|
||||
|
||||
dict_tags = {}
|
||||
if tags and tags != [{}] and tags != [None]:
|
||||
for tag in tags:
|
||||
@@ -88,7 +162,23 @@ def parse_json_tags(tags: list):
|
||||
return dict_tags
|
||||
|
||||
|
||||
def parse_html_string(str: str):
|
||||
def parse_html_string(str: str) -> str:
|
||||
"""
|
||||
Parses a string and returns a formatted HTML string.
|
||||
|
||||
This function takes an input string and splits it using the delimiter " | ".
|
||||
It then formats each element of the split string as a bullet point in HTML format.
|
||||
|
||||
Args:
|
||||
str (str): The input string to be parsed.
|
||||
|
||||
Returns:
|
||||
str: The formatted HTML string.
|
||||
|
||||
Example:
|
||||
>>> parse_html_string("item1 | item2 | item3")
|
||||
'\n•item1\n\n•item2\n\n•item3\n'
|
||||
"""
|
||||
string = ""
|
||||
for elem in str.split(" | "):
|
||||
if elem:
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
from boto3 import Session
|
||||
from boto3.dynamodb.conditions import Attr
|
||||
|
||||
from prowler.lib.check.models import Check_Report_AWS
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.lib.mutelist.mutelist import Mutelist
|
||||
from prowler.lib.outputs.utils import unroll_tags
|
||||
from prowler.lib.outputs.utils import unroll_dict, unroll_tags
|
||||
|
||||
|
||||
class AWSMutelist(Mutelist):
|
||||
@@ -45,7 +45,7 @@ class AWSMutelist(Mutelist):
|
||||
|
||||
def is_finding_muted(
|
||||
self,
|
||||
finding: Any,
|
||||
finding: Check_Report_AWS,
|
||||
aws_account_id: str,
|
||||
) -> bool:
|
||||
return self.is_muted(
|
||||
@@ -53,7 +53,7 @@ class AWSMutelist(Mutelist):
|
||||
finding.check_metadata.CheckID,
|
||||
finding.region,
|
||||
finding.resource_id,
|
||||
unroll_tags(finding.resource_tags),
|
||||
unroll_dict(unroll_tags(finding.resource_tags)),
|
||||
)
|
||||
|
||||
def get_mutelist_file_from_s3(self, aws_session: Session = None):
|
||||
|
||||
@@ -30,9 +30,9 @@ def get_organizations_metadata(
|
||||
def parse_organizations_metadata(metadata: dict, tags: dict) -> AWSOrganizationsInfo:
|
||||
try:
|
||||
# Convert Tags dictionary to String
|
||||
account_details_tags = []
|
||||
account_details_tags = {}
|
||||
for tag in tags.get("Tags", {}):
|
||||
account_details_tags.append(f"{tag['Key']}:{tag['Value']}")
|
||||
account_details_tags[tag["Key"]] = tag["Value"]
|
||||
|
||||
account_details = metadata.get("Account", {})
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
def is_condition_block_restrictive(
|
||||
condition_statement: dict, source_account: str, is_cross_account_allowed=False
|
||||
condition_statement: dict,
|
||||
source_account: str,
|
||||
is_cross_account_allowed=False,
|
||||
):
|
||||
"""
|
||||
is_condition_block_restrictive parses the IAM Condition policy block and, by default, returns True if the source_account passed as argument is within, False if not.
|
||||
@@ -15,6 +17,9 @@ def is_condition_block_restrictive(
|
||||
}
|
||||
|
||||
@param source_account: str with a 12-digit AWS Account number, e.g.: 111122223333
|
||||
|
||||
@param is_cross_account_allowed: bool to allow cross-account access, e.g.: True
|
||||
|
||||
"""
|
||||
is_condition_valid = False
|
||||
|
||||
@@ -90,3 +95,63 @@ def is_condition_block_restrictive(
|
||||
is_condition_valid = True
|
||||
|
||||
return is_condition_valid
|
||||
|
||||
|
||||
def is_condition_block_restrictive_organization(
|
||||
condition_statement: dict,
|
||||
):
|
||||
"""
|
||||
is_condition_block_restrictive_organization parses the IAM Condition policy block and returns True if the condition_statement is restrictive for the organization, False if not.
|
||||
|
||||
@param condition_statement: dict with an IAM Condition block, e.g.:
|
||||
{
|
||||
"StringLike": {
|
||||
"AWS:PrincipalOrgID": "o-111122223333"
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
is_condition_valid = False
|
||||
|
||||
# The conditions must be defined in lowercase since the context key names are not case-sensitive.
|
||||
# For example, including the aws:PrincipalOrgID context key is equivalent to testing for AWS:PrincipalOrgID
|
||||
# https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition.html
|
||||
valid_condition_options = {
|
||||
"StringEquals": [
|
||||
"aws:principalorgid",
|
||||
],
|
||||
"StringLike": [
|
||||
"aws:principalorgid",
|
||||
],
|
||||
}
|
||||
|
||||
for condition_operator, condition_operator_key in valid_condition_options.items():
|
||||
if condition_operator in condition_statement:
|
||||
for value in condition_operator_key:
|
||||
# We need to transform the condition_statement into lowercase
|
||||
condition_statement[condition_operator] = {
|
||||
k.lower(): v
|
||||
for k, v in condition_statement[condition_operator].items()
|
||||
}
|
||||
|
||||
if value in condition_statement[condition_operator]:
|
||||
# values are a list
|
||||
if isinstance(
|
||||
condition_statement[condition_operator][value],
|
||||
list,
|
||||
):
|
||||
is_condition_valid = True
|
||||
for item in condition_statement[condition_operator][value]:
|
||||
if item == "*":
|
||||
is_condition_valid = False
|
||||
break
|
||||
|
||||
# value is a string
|
||||
elif isinstance(
|
||||
condition_statement[condition_operator][value],
|
||||
str,
|
||||
):
|
||||
if "*" not in condition_statement[condition_operator][value]:
|
||||
is_condition_valid = True
|
||||
|
||||
return is_condition_valid
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_AWS
|
||||
from prowler.providers.aws.lib.policy_condition_parser.policy_condition_parser import (
|
||||
is_condition_block_restrictive,
|
||||
is_condition_block_restrictive_organization,
|
||||
)
|
||||
from prowler.providers.aws.services.sns.sns_client import sns_client
|
||||
|
||||
@@ -33,13 +34,30 @@ class sns_topics_not_publicly_accessible(Check):
|
||||
and "*" in statement["Principal"]["CanonicalUser"]
|
||||
)
|
||||
):
|
||||
condition_account = False
|
||||
condition_org = False
|
||||
if (
|
||||
"Condition" in statement
|
||||
and is_condition_block_restrictive(
|
||||
statement["Condition"], sns_client.audited_account
|
||||
statement["Condition"],
|
||||
sns_client.audited_account,
|
||||
)
|
||||
):
|
||||
report.status_extended = f"SNS topic {topic.name} is not public because its policy only allows access from the same account."
|
||||
condition_account = True
|
||||
if (
|
||||
"Condition" in statement
|
||||
and is_condition_block_restrictive_organization(
|
||||
statement["Condition"],
|
||||
)
|
||||
):
|
||||
condition_org = True
|
||||
|
||||
if condition_account and condition_org:
|
||||
report.status_extended = f"SNS topic {topic.name} is not public because its policy only allows access from the account {sns_client.audited_account} and an organization."
|
||||
elif condition_account:
|
||||
report.status_extended = f"SNS topic {topic.name} is not public because its policy only allows access from the account {sns_client.audited_account}."
|
||||
elif condition_org:
|
||||
report.status_extended = f"SNS topic {topic.name} is not public because its policy only allows access from an organization."
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"SNS topic {topic.name} is public because its policy allows public access."
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
from typing import Any
|
||||
|
||||
from prowler.lib.check.models import Check_Report_Azure
|
||||
from prowler.lib.mutelist.mutelist import Mutelist
|
||||
from prowler.lib.outputs.utils import unroll_tags
|
||||
from prowler.lib.outputs.utils import unroll_dict, unroll_tags
|
||||
|
||||
|
||||
class AzureMutelist(Mutelist):
|
||||
def is_finding_muted(
|
||||
self,
|
||||
finding: Any,
|
||||
finding: Check_Report_Azure,
|
||||
) -> bool:
|
||||
return self.is_muted(
|
||||
finding.subscription,
|
||||
finding.check_metadata.CheckID,
|
||||
finding.location,
|
||||
finding.resource_name,
|
||||
unroll_tags(finding.resource_tags),
|
||||
unroll_dict(unroll_tags(finding.resource_tags)),
|
||||
)
|
||||
|
||||
@@ -279,9 +279,9 @@ class GcpProvider(Provider):
|
||||
response = request.execute()
|
||||
|
||||
for project in response.get("projects", []):
|
||||
labels = []
|
||||
labels = {}
|
||||
for key, value in project.get("labels", {}).items():
|
||||
labels.append(f"{key}:{value}")
|
||||
labels[key] = value
|
||||
|
||||
project_id = project["projectId"]
|
||||
gcp_project = GCPProject(
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
from typing import Any
|
||||
|
||||
from prowler.lib.check.models import Check_Report_GCP
|
||||
from prowler.lib.mutelist.mutelist import Mutelist
|
||||
from prowler.lib.outputs.utils import unroll_tags
|
||||
from prowler.lib.outputs.utils import unroll_dict, unroll_tags
|
||||
|
||||
|
||||
class GCPMutelist(Mutelist):
|
||||
def is_finding_muted(
|
||||
self,
|
||||
finding: Any,
|
||||
finding: Check_Report_GCP,
|
||||
) -> bool:
|
||||
return self.is_muted(
|
||||
finding.project_id,
|
||||
finding.check_metadata.CheckID,
|
||||
finding.location,
|
||||
finding.resource_name,
|
||||
unroll_tags(finding.resource_tags),
|
||||
unroll_dict(unroll_tags(finding.resource_tags)),
|
||||
)
|
||||
|
||||
@@ -22,7 +22,7 @@ class GCPProject(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
organization: Optional[GCPOrganization]
|
||||
labels: list[str]
|
||||
labels: dict
|
||||
lifecycle_state: str
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ class cloudsql_instance_ssl_connections(Check):
|
||||
report.status_extended = (
|
||||
f"Database Instance {instance.name} requires SSL connections."
|
||||
)
|
||||
if not instance.ssl:
|
||||
if not instance.require_ssl or instance.ssl_mode != "ENCRYPTED_ONLY":
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Database Instance {instance.name} does not require SSL connections."
|
||||
findings.append(report)
|
||||
|
||||
@@ -31,9 +31,12 @@ class CloudSQL(GCPService):
|
||||
region=instance["region"],
|
||||
ip_addresses=instance.get("ipAddresses", []),
|
||||
public_ip=public_ip,
|
||||
ssl=instance["settings"]["ipConfiguration"].get(
|
||||
require_ssl=instance["settings"]["ipConfiguration"].get(
|
||||
"requireSsl", False
|
||||
),
|
||||
ssl_mode=instance["settings"]["ipConfiguration"].get(
|
||||
"sslMode", "ALLOW_UNENCRYPTED_AND_ENCRYPTED"
|
||||
),
|
||||
automated_backups=instance["settings"][
|
||||
"backupConfiguration"
|
||||
]["enabled"],
|
||||
@@ -61,7 +64,8 @@ class Instance(BaseModel):
|
||||
region: str
|
||||
public_ip: bool
|
||||
authorized_networks: list
|
||||
ssl: bool
|
||||
require_ssl: bool
|
||||
ssl_mode: str
|
||||
automated_backups: bool
|
||||
flags: list
|
||||
project_id: str
|
||||
|
||||
@@ -8,7 +8,7 @@ class kms_key_not_publicly_accessible(Check):
|
||||
for key in kms_client.crypto_keys:
|
||||
report = Check_Report_GCP(self.metadata())
|
||||
report.project_id = key.project_id
|
||||
report.resource_id = key.name
|
||||
report.resource_id = key.id
|
||||
report.resource_name = key.name
|
||||
report.location = key.location
|
||||
report.status = "PASS"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import datetime
|
||||
|
||||
from prowler.lib.check.models import Check, Check_Report_GCP
|
||||
from prowler.providers.gcp.services.kms.kms_client import kms_client
|
||||
|
||||
@@ -8,21 +10,34 @@ class kms_key_rotation_enabled(Check):
|
||||
for key in kms_client.crypto_keys:
|
||||
report = Check_Report_GCP(self.metadata())
|
||||
report.project_id = key.project_id
|
||||
report.resource_id = key.name
|
||||
report.resource_id = key.id
|
||||
report.resource_name = key.name
|
||||
report.location = key.location
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"Key {key.name} is not rotated every 90 days or less."
|
||||
)
|
||||
now = datetime.datetime.now()
|
||||
condition_next_rotation_time = False
|
||||
if key.next_rotation_time:
|
||||
next_rotation_time = datetime.datetime.strptime(
|
||||
key.next_rotation_time, "%Y-%m-%dT%H:%M:%SZ"
|
||||
)
|
||||
condition_next_rotation_time = (
|
||||
abs((next_rotation_time - now).days) <= 90
|
||||
)
|
||||
condition_rotation_period = False
|
||||
if key.rotation_period:
|
||||
if (
|
||||
condition_rotation_period = (
|
||||
int(key.rotation_period[:-1]) // (24 * 3600) <= 90
|
||||
): # Convert seconds to days and check if less or equal than 90
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"Key {key.name} is rotated every 90 days or less."
|
||||
)
|
||||
)
|
||||
if condition_rotation_period and condition_next_rotation_time:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Key {key.name} is rotated every 90 days or less and the next rotation time is in less than 90 days."
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
if condition_rotation_period:
|
||||
report.status_extended = f"Key {key.name} is rotated every 90 days or less but the next rotation time is in more than 90 days."
|
||||
elif condition_next_rotation_time:
|
||||
report.status_extended = f"Key {key.name} is not rotated every 90 days or less but the next rotation time is in less than 90 days."
|
||||
else:
|
||||
report.status_extended = f"Key {key.name} is not rotated every 90 days or less and the next rotation time is in more than 90 days."
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
|
||||
@@ -88,9 +88,11 @@ class KMS(GCPService):
|
||||
for key in response.get("cryptoKeys", []):
|
||||
self.crypto_keys.append(
|
||||
CriptoKey(
|
||||
id=key["name"],
|
||||
name=key["name"].split("/")[-1],
|
||||
location=key["name"].split("/")[3],
|
||||
rotation_period=key.get("rotationPeriod"),
|
||||
next_rotation_time=key.get("nextRotationTime"),
|
||||
key_ring=ring.name,
|
||||
project_id=ring.project_id,
|
||||
)
|
||||
@@ -139,9 +141,11 @@ class KeyRing(BaseModel):
|
||||
|
||||
|
||||
class CriptoKey(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
location: str
|
||||
rotation_period: Optional[str]
|
||||
next_rotation_time: Optional[str]
|
||||
key_ring: str
|
||||
members: list = []
|
||||
project_id: str
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
from typing import Any
|
||||
|
||||
from prowler.lib.check.models import Check_Report_Kubernetes
|
||||
from prowler.lib.mutelist.mutelist import Mutelist
|
||||
from prowler.lib.outputs.utils import unroll_tags
|
||||
from prowler.lib.outputs.utils import unroll_dict, unroll_tags
|
||||
|
||||
|
||||
class KubernetesMutelist(Mutelist):
|
||||
def is_finding_muted(
|
||||
self,
|
||||
finding: Any,
|
||||
finding: Check_Report_Kubernetes,
|
||||
cluster: str,
|
||||
) -> bool:
|
||||
return self.is_muted(
|
||||
@@ -15,5 +14,5 @@ class KubernetesMutelist(Mutelist):
|
||||
finding.check_metadata.CheckID,
|
||||
finding.namespace,
|
||||
finding.resource_name,
|
||||
unroll_tags(finding.resource_tags),
|
||||
unroll_dict(unroll_tags(finding.resource_tags)),
|
||||
)
|
||||
|
||||
@@ -23,7 +23,7 @@ packages = [
|
||||
{include = "dashboard"}
|
||||
]
|
||||
readme = "README.md"
|
||||
version = "4.3.1"
|
||||
version = "4.3.3"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
alive-progress = "3.1.5"
|
||||
|
||||
@@ -40,7 +40,7 @@ class TestASFF:
|
||||
resource_details="Test resource details",
|
||||
resource_name="test-resource",
|
||||
resource_uid="test-arn",
|
||||
resource_tags="key1=value1",
|
||||
resource_tags={"key1": "value1"},
|
||||
)
|
||||
|
||||
timestamp = timestamp_utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
@@ -70,7 +70,7 @@ class TestASFF:
|
||||
Type=finding.resource_type,
|
||||
Partition=AWS_COMMERCIAL_PARTITION,
|
||||
Region=AWS_REGION_EU_WEST_1,
|
||||
Tags=ASFF.format_resource_tags(finding.resource_tags),
|
||||
Tags={"key1": "value1"},
|
||||
)
|
||||
],
|
||||
Compliance=Compliance(
|
||||
@@ -103,7 +103,7 @@ class TestASFF:
|
||||
resource_details="Test resource details",
|
||||
resource_name="test-resource",
|
||||
resource_uid="test-arn",
|
||||
resource_tags="key1=value1",
|
||||
resource_tags={"key1": "value1"},
|
||||
)
|
||||
finding.remediation_recommendation_url = ""
|
||||
|
||||
@@ -136,7 +136,72 @@ class TestASFF:
|
||||
Type=finding.resource_type,
|
||||
Partition=AWS_COMMERCIAL_PARTITION,
|
||||
Region=AWS_REGION_EU_WEST_1,
|
||||
Tags=ASFF.format_resource_tags(finding.resource_tags),
|
||||
Tags={"key1": "value1"},
|
||||
)
|
||||
],
|
||||
Compliance=Compliance(
|
||||
Status=ASFF.generate_status(status),
|
||||
RelatedRequirements=compliance_summary,
|
||||
AssociatedStandards=associated_standards,
|
||||
),
|
||||
Remediation=Remediation(
|
||||
Recommendation=Recommendation(
|
||||
Text=finding.remediation_recommendation_text,
|
||||
Url="https://docs.aws.amazon.com/securityhub/latest/userguide/what-is-securityhub.html",
|
||||
)
|
||||
),
|
||||
Description=finding.description,
|
||||
)
|
||||
|
||||
asff = ASFF(findings=[finding])
|
||||
|
||||
assert len(asff.data) == 1
|
||||
asff_finding = asff.data[0]
|
||||
|
||||
assert asff_finding == expected
|
||||
|
||||
def test_asff_without_resource_tags(self):
|
||||
status = "PASS"
|
||||
finding = generate_finding_output(
|
||||
status=status,
|
||||
status_extended="This is a test",
|
||||
region=AWS_REGION_EU_WEST_1,
|
||||
resource_details="Test resource details",
|
||||
resource_name="test-resource",
|
||||
resource_uid="test-arn",
|
||||
)
|
||||
finding.remediation_recommendation_url = ""
|
||||
|
||||
timestamp = timestamp_utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
associated_standards, compliance_summary = ASFF.format_compliance(
|
||||
finding.compliance
|
||||
)
|
||||
|
||||
timestamp = timestamp_utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
expected = AWSSecurityFindingFormat(
|
||||
Id=f"prowler-{finding.check_id}-{AWS_ACCOUNT_NUMBER}-{AWS_REGION_EU_WEST_1}-{hash_sha512(finding.resource_uid)}",
|
||||
ProductArn=f"arn:{AWS_COMMERCIAL_PARTITION}:securityhub:{AWS_REGION_EU_WEST_1}::product/prowler/prowler",
|
||||
ProductFields=ProductFields(
|
||||
ProviderVersion=prowler_version,
|
||||
ProwlerResourceName=finding.resource_uid,
|
||||
),
|
||||
GeneratorId="prowler-" + finding.check_id,
|
||||
AwsAccountId=AWS_ACCOUNT_NUMBER,
|
||||
Types=finding.check_type.split(","),
|
||||
FirstObservedAt=timestamp,
|
||||
UpdatedAt=timestamp,
|
||||
CreatedAt=timestamp,
|
||||
Severity=Severity(Label=finding.severity),
|
||||
Title=finding.check_title,
|
||||
Resources=[
|
||||
Resource(
|
||||
Id=finding.resource_uid,
|
||||
Type=finding.resource_type,
|
||||
Partition=AWS_COMMERCIAL_PARTITION,
|
||||
Region=AWS_REGION_EU_WEST_1,
|
||||
Tags=None,
|
||||
)
|
||||
],
|
||||
Compliance=Compliance(
|
||||
@@ -171,7 +236,7 @@ class TestASFF:
|
||||
resource_details="Test resource details",
|
||||
resource_name="test-resource",
|
||||
resource_uid="test-arn",
|
||||
resource_tags="key1=value1",
|
||||
resource_tags={"key1": "value1"},
|
||||
)
|
||||
finding.remediation_recommendation_url = ""
|
||||
finding.remediation_recommendation_text = "x" * 513
|
||||
@@ -205,7 +270,7 @@ class TestASFF:
|
||||
Type=finding.resource_type,
|
||||
Partition=AWS_COMMERCIAL_PARTITION,
|
||||
Region=AWS_REGION_EU_WEST_1,
|
||||
Tags=ASFF.format_resource_tags(finding.resource_tags),
|
||||
Tags={"key1": "value1"},
|
||||
)
|
||||
],
|
||||
Compliance=Compliance(
|
||||
@@ -239,7 +304,7 @@ class TestASFF:
|
||||
resource_details="Test resource details",
|
||||
resource_name="test-resource",
|
||||
resource_uid="test-arn",
|
||||
resource_tags="key1=value1",
|
||||
resource_tags={"key1": "value1"},
|
||||
compliance={
|
||||
"CISA": ["your-systems-3", "your-data-2"],
|
||||
"SOC2": ["cc_2_1", "cc_7_2", "cc_a_1_2"],
|
||||
@@ -412,7 +477,7 @@ class TestASFF:
|
||||
Type=finding.resource_type,
|
||||
Partition=AWS_COMMERCIAL_PARTITION,
|
||||
Region=AWS_REGION_EU_WEST_1,
|
||||
Tags=ASFF.format_resource_tags(finding.resource_tags),
|
||||
Tags={"key1": "value1"},
|
||||
)
|
||||
],
|
||||
Compliance=Compliance(
|
||||
@@ -448,7 +513,7 @@ class TestASFF:
|
||||
resource_details="Test resource details",
|
||||
resource_name="test-resource",
|
||||
resource_uid="test-arn",
|
||||
resource_tags="key1=value1",
|
||||
resource_tags={"key1": "value1"},
|
||||
)
|
||||
finding.remediation_recommendation_url = ""
|
||||
|
||||
@@ -517,14 +582,3 @@ class TestASFF:
|
||||
assert ASFF.generate_status("FAIL") == "FAILED"
|
||||
assert ASFF.generate_status("FAIL", True) == "WARNING"
|
||||
assert ASFF.generate_status("SOMETHING ELSE") == "NOT_AVAILABLE"
|
||||
|
||||
def test_asff_format_resource_tags(self):
|
||||
assert ASFF.format_resource_tags(None) is None
|
||||
assert ASFF.format_resource_tags("") is None
|
||||
assert ASFF.format_resource_tags([]) is None
|
||||
assert ASFF.format_resource_tags([{}]) is None
|
||||
assert ASFF.format_resource_tags("key1=value1") == {"key1": "value1"}
|
||||
assert ASFF.format_resource_tags("key1=value1 | key2=value2") == {
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ class TestCSV:
|
||||
resource_uid="resource-123",
|
||||
resource_name="Example Resource",
|
||||
resource_details="Detailed information about the resource",
|
||||
resource_tags="tag1,tag2",
|
||||
resource_tags={"tag1": "value1", "tag2": "value2"},
|
||||
partition="aws",
|
||||
description="Description of the finding",
|
||||
risk="High",
|
||||
@@ -78,7 +78,7 @@ class TestCSV:
|
||||
assert (
|
||||
output_data["RESOURCE_DETAILS"] == "Detailed information about the resource"
|
||||
)
|
||||
assert output_data["RESOURCE_TAGS"] == "tag1,tag2"
|
||||
assert output_data["RESOURCE_TAGS"] == "tag1=value1 | tag2=value2"
|
||||
assert output_data["PARTITION"] == "aws"
|
||||
assert output_data["REGION"] == AWS_REGION_EU_WEST_1
|
||||
assert output_data["DESCRIPTION"] == "Description of the finding"
|
||||
|
||||
@@ -12,7 +12,7 @@ def mock_get_provider_data_mapping_aws(_):
|
||||
"account_email": "mock_account_email",
|
||||
"account_organization_uid": "mock_account_org_uid",
|
||||
"account_organization_name": "mock_account_org_name",
|
||||
"account_tags": ["tag1", "tag2"],
|
||||
"account_tags": {"tag1": "value1"},
|
||||
"finding_uid": "mock_finding_uid",
|
||||
"provider": "aws",
|
||||
"check_id": "mock_check_id",
|
||||
@@ -28,7 +28,7 @@ def mock_get_provider_data_mapping_aws(_):
|
||||
"resource_uid": "mock_resource_uid",
|
||||
"resource_name": "mock_resource_name",
|
||||
"resource_details": "mock_resource_details",
|
||||
"resource_tags": "mock_resource_tags",
|
||||
"resource_tags": {"tag1": "value1"},
|
||||
"partition": None,
|
||||
"region": "mock_region",
|
||||
"description": "mock_description",
|
||||
@@ -58,7 +58,7 @@ def mock_get_provider_data_mapping_azure(_):
|
||||
"account_email": "mock_account_email",
|
||||
"account_organization_uid": "mock_account_org_uid",
|
||||
"account_organization_name": "mock_account_org_name",
|
||||
"account_tags": ["tag1", "tag2"],
|
||||
"account_tags": {"tag1": "value1"},
|
||||
"finding_uid": "mock_finding_uid",
|
||||
"provider": "azure",
|
||||
"check_id": "mock_check_id",
|
||||
@@ -74,7 +74,7 @@ def mock_get_provider_data_mapping_azure(_):
|
||||
"resource_uid": "mock_resource_uid",
|
||||
"resource_name": "mock_resource_name",
|
||||
"resource_details": "mock_resource_details",
|
||||
"resource_tags": "mock_resource_tags",
|
||||
"resource_tags": {"tag1": "value1"},
|
||||
"partition": None,
|
||||
"description": "mock_description",
|
||||
"risk": "mock_risk",
|
||||
@@ -103,7 +103,7 @@ def mock_get_provider_data_mapping_gcp(_):
|
||||
"account_email": "mock_account_email",
|
||||
"account_organization_uid": "mock_account_org_uid",
|
||||
"account_organization_name": "mock_account_org_name",
|
||||
"account_tags": ["tag1", "tag2"],
|
||||
"account_tags": {"tag1": "value1"},
|
||||
"finding_uid": "mock_finding_uid",
|
||||
"provider": "gcp",
|
||||
"check_id": "mock_check_id",
|
||||
@@ -119,7 +119,7 @@ def mock_get_provider_data_mapping_gcp(_):
|
||||
"resource_uid": "mock_resource_uid",
|
||||
"resource_name": "mock_resource_name",
|
||||
"resource_details": "mock_resource_details",
|
||||
"resource_tags": "mock_resource_tags",
|
||||
"resource_tags": {"tag1": "value1"},
|
||||
"partition": None,
|
||||
"description": "mock_description",
|
||||
"risk": "mock_risk",
|
||||
@@ -148,7 +148,7 @@ def mock_get_provider_data_mapping_kubernetes(_):
|
||||
"account_email": "mock_account_email",
|
||||
"account_organization_uid": "mock_account_org_uid",
|
||||
"account_organization_name": "mock_account_org_name",
|
||||
"account_tags": ["tag1", "tag2"],
|
||||
"account_tags": {"tag1": "value1"},
|
||||
"finding_uid": "mock_finding_uid",
|
||||
"provider": "kubernetes",
|
||||
"check_id": "mock_check_id",
|
||||
@@ -164,7 +164,7 @@ def mock_get_provider_data_mapping_kubernetes(_):
|
||||
"resource_uid": "mock_resource_uid",
|
||||
"resource_name": "mock_resource_name",
|
||||
"resource_details": "mock_resource_details",
|
||||
"resource_tags": "mock_resource_tags",
|
||||
"resource_tags": {"tag1": "value1"},
|
||||
"partition": None,
|
||||
"description": "mock_description",
|
||||
"risk": "mock_risk",
|
||||
@@ -240,7 +240,7 @@ class TestFinding:
|
||||
assert finding_output.subservice_name == "mock_subservice_name"
|
||||
assert finding_output.severity == Severity.high
|
||||
assert finding_output.resource_type == "mock_resource_type"
|
||||
assert finding_output.resource_tags == "mock_resource_tags"
|
||||
assert finding_output.resource_tags == {"tag1": "value1"}
|
||||
assert finding_output.partition is None
|
||||
assert finding_output.description == "mock_description"
|
||||
assert finding_output.risk == "mock_risk"
|
||||
@@ -260,7 +260,7 @@ class TestFinding:
|
||||
assert finding_output.account_email == "mock_account_email"
|
||||
assert finding_output.account_organization_uid == "mock_account_org_uid"
|
||||
assert finding_output.account_organization_name == "mock_account_org_name"
|
||||
assert finding_output.account_tags == ["tag1", "tag2"]
|
||||
assert finding_output.account_tags == {"tag1": "value1"}
|
||||
assert finding_output.prowler_version == "1.0.0"
|
||||
|
||||
@patch(
|
||||
@@ -318,7 +318,7 @@ class TestFinding:
|
||||
assert finding_output.subservice_name == "mock_subservice_name"
|
||||
assert finding_output.severity == Severity.high
|
||||
assert finding_output.resource_type == "mock_resource_type"
|
||||
assert finding_output.resource_tags == "mock_resource_tags"
|
||||
assert finding_output.resource_tags == {"tag1": "value1"}
|
||||
assert finding_output.partition is None
|
||||
assert finding_output.description == "mock_description"
|
||||
assert finding_output.risk == "mock_risk"
|
||||
@@ -353,7 +353,7 @@ class TestFinding:
|
||||
organization.display_name = "mock_organization_name"
|
||||
project.id = "mock_project_id"
|
||||
project.name = "mock_project_name"
|
||||
project.labels = ["label1", "label2"]
|
||||
project.labels = {"tag1": "value1"}
|
||||
project.organization = organization
|
||||
|
||||
provider.projects = {"mock_project_id": project}
|
||||
@@ -388,7 +388,7 @@ class TestFinding:
|
||||
assert finding_output.subservice_name == "mock_subservice_name"
|
||||
assert finding_output.severity == Severity.high
|
||||
assert finding_output.resource_type == "mock_resource_type"
|
||||
assert finding_output.resource_tags == "mock_resource_tags"
|
||||
assert finding_output.resource_tags == {"tag1": "value1"}
|
||||
assert finding_output.partition is None
|
||||
assert finding_output.description == "mock_description"
|
||||
assert finding_output.risk == "mock_risk"
|
||||
@@ -408,7 +408,7 @@ class TestFinding:
|
||||
assert finding_output.account_email == "mock_account_email"
|
||||
assert finding_output.account_organization_uid == "mock_organization_id"
|
||||
assert finding_output.account_organization_name == "mock_account_org_name"
|
||||
assert finding_output.account_tags == ["label1", "label2"]
|
||||
assert finding_output.account_tags == {"tag1": "value1"}
|
||||
assert finding_output.prowler_version == "1.0.0"
|
||||
assert finding_output.timestamp == 1622520000
|
||||
|
||||
@@ -459,7 +459,7 @@ class TestFinding:
|
||||
assert finding_output.subservice_name == "mock_subservice_name"
|
||||
assert finding_output.severity == Severity.high
|
||||
assert finding_output.resource_type == "mock_resource_type"
|
||||
assert finding_output.resource_tags == "mock_resource_tags"
|
||||
assert finding_output.resource_tags == {"tag1": "value1"}
|
||||
assert finding_output.partition is None
|
||||
assert finding_output.description == "mock_description"
|
||||
assert finding_output.risk == "mock_risk"
|
||||
@@ -479,6 +479,6 @@ class TestFinding:
|
||||
assert finding_output.account_email == "mock_account_email"
|
||||
assert finding_output.account_organization_uid == "mock_account_org_uid"
|
||||
assert finding_output.account_organization_name == "mock_account_org_name"
|
||||
assert finding_output.account_tags == ["tag1", "tag2"]
|
||||
assert finding_output.account_tags == {"tag1": "value1"}
|
||||
assert finding_output.prowler_version == "1.0.0"
|
||||
assert finding_output.timestamp == 1622520000
|
||||
|
||||
@@ -16,7 +16,7 @@ def generate_finding_output(
|
||||
resource_details: str = "",
|
||||
resource_uid: str = "",
|
||||
resource_name: str = "",
|
||||
resource_tags: str = "",
|
||||
resource_tags: dict = {},
|
||||
compliance: dict = {"test-compliance": "test-compliance"},
|
||||
timestamp: datetime = None,
|
||||
provider: str = "aws",
|
||||
@@ -34,6 +34,10 @@ def generate_finding_output(
|
||||
depends_on: str = "test-dependency",
|
||||
related_to: str = "test-related-to",
|
||||
notes: str = "test-notes",
|
||||
service_name: str = "test-service",
|
||||
check_id: str = "test-check-id",
|
||||
check_title: str = "test-check-id",
|
||||
check_type: str = "test-type",
|
||||
) -> Finding:
|
||||
return Finding(
|
||||
auth_method="profile: default",
|
||||
@@ -43,16 +47,16 @@ def generate_finding_output(
|
||||
account_email="",
|
||||
account_organization_uid="test-organization-id",
|
||||
account_organization_name="test-organization",
|
||||
account_tags=["test-tag:test-value"],
|
||||
account_tags={"test-tag": "test-value"},
|
||||
finding_uid="test-unique-finding",
|
||||
provider=provider,
|
||||
check_id="test-check-id",
|
||||
check_title="test-check-id",
|
||||
check_type="test-type",
|
||||
check_id=check_id,
|
||||
check_title=check_title,
|
||||
check_type=check_type,
|
||||
status=status,
|
||||
status_extended=status_extended,
|
||||
muted=muted,
|
||||
service_name="test-service",
|
||||
service_name=service_name,
|
||||
subservice_name="",
|
||||
severity=severity,
|
||||
resource_type="test-resource",
|
||||
|
||||
@@ -45,11 +45,15 @@ fail_html_finding = """
|
||||
<td>eu-west-1</td>
|
||||
<td>test-check-id</td>
|
||||
<td>test-check-id</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>test-resource-uid</td>
|
||||
<td>
|
||||
•key1=value1
|
||||
|
||||
•key2=value2
|
||||
</td>
|
||||
<td>test-status-extended</td>
|
||||
<td><p class="show-read-more">test-risk</p></td>
|
||||
<td><p class="show-read-more"></p> <a class="read-more" href=""><i class="fas fa-external-link-alt"></i></a></td>
|
||||
<td><p class="show-read-more">test-remediation-recommendation-text</p> <a class="read-more" href=""><i class="fas fa-external-link-alt"></i></a></td>
|
||||
<td><p class="show-read-more">
|
||||
•test-compliance: test-compliance
|
||||
</p></td>
|
||||
@@ -421,7 +425,23 @@ html_footer = """
|
||||
|
||||
class TestHTML:
|
||||
def test_transform_fail_finding(self):
|
||||
findings = [generate_finding_output(status="FAIL")]
|
||||
findings = [
|
||||
generate_finding_output(
|
||||
status="FAIL",
|
||||
resource_tags={"key1": "value1", "key2": "value2"},
|
||||
severity="high",
|
||||
service_name="test-service",
|
||||
region=AWS_REGION_EU_WEST_1,
|
||||
check_id="test-check-id",
|
||||
check_title="test-check-id",
|
||||
resource_uid="test-resource-uid",
|
||||
status_extended="test-status-extended",
|
||||
risk="test-risk",
|
||||
remediation_recommendation_text="test-remediation-recommendation-text",
|
||||
compliance={"test-compliance": "test-compliance"},
|
||||
)
|
||||
]
|
||||
|
||||
html = HTML(findings)
|
||||
output_data = html.data[0]
|
||||
assert isinstance(output_data, str)
|
||||
|
||||
@@ -30,7 +30,11 @@ class TestOCSF:
|
||||
def test_transform(self):
|
||||
findings = [
|
||||
generate_finding_output(
|
||||
status="FAIL", severity="low", muted=False, region=AWS_REGION_EU_WEST_1
|
||||
status="FAIL",
|
||||
severity="low",
|
||||
muted=False,
|
||||
region=AWS_REGION_EU_WEST_1,
|
||||
resource_tags={"Name": "test", "Environment": "dev"},
|
||||
)
|
||||
]
|
||||
|
||||
@@ -58,7 +62,7 @@ class TestOCSF:
|
||||
assert output_data.status_code == findings[0].status
|
||||
assert output_data.status_detail == findings[0].status_extended
|
||||
assert output_data.risk_details == findings[0].risk
|
||||
assert output_data.resources[0].labels == []
|
||||
assert output_data.resources[0].labels == ["Name:test", "Environment:dev"]
|
||||
assert output_data.resources[0].name == findings[0].resource_name
|
||||
assert output_data.resources[0].uid == findings[0].resource_uid
|
||||
assert output_data.resources[0].type == findings[0].resource_type
|
||||
@@ -190,7 +194,11 @@ class TestOCSF:
|
||||
|
||||
def test_finding_output_cloud_pass_low_muted(self):
|
||||
finding_output = generate_finding_output(
|
||||
status="PASS", severity="low", muted=True, region=AWS_REGION_EU_WEST_1
|
||||
status="PASS",
|
||||
severity="low",
|
||||
muted=True,
|
||||
region=AWS_REGION_EU_WEST_1,
|
||||
resource_tags={"Name": "test", "Environment": "dev"},
|
||||
)
|
||||
|
||||
finding_ocsf = OCSF([finding_output])
|
||||
@@ -248,7 +256,7 @@ class TestOCSF:
|
||||
assert len(resource_details) == 1
|
||||
assert isinstance(resource_details, list)
|
||||
assert isinstance(resource_details[0], ResourceDetails)
|
||||
assert resource_details[0].labels == []
|
||||
assert resource_details[0].labels == ["Name:test", "Environment:dev"]
|
||||
assert resource_details[0].name == finding_output.resource_name
|
||||
assert resource_details[0].uid == finding_output.resource_uid
|
||||
assert resource_details[0].type == finding_output.resource_type
|
||||
@@ -287,7 +295,7 @@ class TestOCSF:
|
||||
assert cloud_account.type_id == TypeID.AWS_Account
|
||||
assert cloud_account.type == TypeID.AWS_Account.name
|
||||
assert cloud_account.uid == finding_output.account_uid
|
||||
assert cloud_account.labels == finding_output.account_tags
|
||||
assert cloud_account.labels == ["test-tag:test-value"]
|
||||
|
||||
cloud_organization = cloud.org
|
||||
assert isinstance(cloud_organization, Organization)
|
||||
|
||||
@@ -98,6 +98,30 @@ class TestOutputs:
|
||||
{"Key": "environment", "Value": "dev"},
|
||||
{"Key": "terraform", "Value": "true"},
|
||||
]
|
||||
|
||||
assert unroll_tags(dict_list) == {
|
||||
"environment": "dev",
|
||||
"name": "test",
|
||||
"project": "prowler",
|
||||
"terraform": "true",
|
||||
}
|
||||
|
||||
def test_unroll_dict_tags(self):
|
||||
tags_dict = {
|
||||
"environment": "dev",
|
||||
"name": "test",
|
||||
"project": "prowler",
|
||||
"terraform": "true",
|
||||
}
|
||||
|
||||
assert unroll_tags(tags_dict) == {
|
||||
"environment": "dev",
|
||||
"name": "test",
|
||||
"project": "prowler",
|
||||
"terraform": "true",
|
||||
}
|
||||
|
||||
def test_unroll_tags_unique(self):
|
||||
unique_dict_list = [
|
||||
{
|
||||
"test1": "value1",
|
||||
@@ -105,14 +129,26 @@ class TestOutputs:
|
||||
"test3": "value3",
|
||||
}
|
||||
]
|
||||
assert (
|
||||
unroll_tags(dict_list)
|
||||
== "name=test | project=prowler | environment=dev | terraform=true"
|
||||
)
|
||||
assert (
|
||||
unroll_tags(unique_dict_list)
|
||||
== "test1=value1 | test2=value2 | test3=value3"
|
||||
)
|
||||
assert unroll_tags(unique_dict_list) == {
|
||||
"test1": "value1",
|
||||
"test2": "value2",
|
||||
"test3": "value3",
|
||||
}
|
||||
|
||||
def test_unroll_tags_lowercase(self):
|
||||
dict_list = [
|
||||
{"key": "name", "value": "test"},
|
||||
{"key": "project", "value": "prowler"},
|
||||
{"key": "environment", "value": "dev"},
|
||||
{"key": "terraform", "value": "true"},
|
||||
]
|
||||
|
||||
assert unroll_tags(dict_list) == {
|
||||
"environment": "dev",
|
||||
"name": "test",
|
||||
"project": "prowler",
|
||||
"terraform": "true",
|
||||
}
|
||||
|
||||
def test_unroll_dict(self):
|
||||
test_compliance_dict = {
|
||||
@@ -156,18 +192,18 @@ class TestOutputs:
|
||||
"FedRAMP-Low-Revision-4": ["sc-13"],
|
||||
}
|
||||
assert (
|
||||
unroll_dict(test_compliance_dict)
|
||||
unroll_dict(test_compliance_dict, separator=": ")
|
||||
== "CISA: your-systems-3, your-data-1, your-data-2 | CIS-1.4: 2.1.1 | CIS-1.5: 2.1.1 | GDPR: article_32 | AWS-Foundational-Security-Best-Practices: s3 | HIPAA: 164_308_a_1_ii_b, 164_308_a_4_ii_a, 164_312_a_2_iv, 164_312_c_1, 164_312_c_2, 164_312_e_2_ii | GxP-21-CFR-Part-11: 11.10-c, 11.30 | GxP-EU-Annex-11: 7.1-data-storage-damage-protection | NIST-800-171-Revision-2: 3_3_8, 3_5_10, 3_13_11, 3_13_16 | NIST-800-53-Revision-4: sc_28 | NIST-800-53-Revision-5: au_9_3, cm_6_a, cm_9_b, cp_9_d, cp_9_8, pm_11_b, sc_8_3, sc_8_4, sc_13_a, sc_16_1, sc_28_1, si_19_4 | ENS-RD2022: mp.si.2.aws.s3.1 | NIST-CSF-1.1: ds_1 | RBI-Cyber-Security-Framework: annex_i_1_3 | FFIEC: d3-pc-am-b-12 | PCI-3.2.1: s3 | FedRamp-Moderate-Revision-4: sc-13, sc-28 | FedRAMP-Low-Revision-4: sc-13"
|
||||
)
|
||||
|
||||
def test_unroll_dict_to_list(self):
|
||||
dict_A = {"A": "B"}
|
||||
list_A = ["A: B"]
|
||||
list_A = ["A:B"]
|
||||
|
||||
assert unroll_dict_to_list(dict_A) == list_A
|
||||
|
||||
dict_B = {"A": ["B", "C"]}
|
||||
list_B = ["A: B, C"]
|
||||
list_B = ["A:B, C"]
|
||||
|
||||
assert unroll_dict_to_list(dict_B) == list_B
|
||||
|
||||
|
||||
@@ -270,7 +270,7 @@ class TestAWSProvider:
|
||||
assert isinstance(aws_provider.organizations_metadata, AWSOrganizationsInfo)
|
||||
assert aws_provider.organizations_metadata.account_email == "master@example.com"
|
||||
assert aws_provider.organizations_metadata.account_name == "master"
|
||||
assert aws_provider.organizations_metadata.account_tags == ["tagged:true"]
|
||||
assert aws_provider.organizations_metadata.account_tags == {"tagged": "true"}
|
||||
assert (
|
||||
aws_provider.organizations_metadata.organization_account_arn
|
||||
== f"arn:aws:organizations::{AWS_ACCOUNT_NUMBER}:account/{organization['Id']}/{AWS_ACCOUNT_NUMBER}"
|
||||
@@ -351,7 +351,7 @@ class TestAWSProvider:
|
||||
assert isinstance(aws_provider.organizations_metadata, AWSOrganizationsInfo)
|
||||
assert aws_provider.organizations_metadata.account_email == "master@example.com"
|
||||
assert aws_provider.organizations_metadata.account_name == "master"
|
||||
assert aws_provider.organizations_metadata.account_tags == ["tagged:true"]
|
||||
assert aws_provider.organizations_metadata.account_tags == {"tagged": "true"}
|
||||
assert (
|
||||
aws_provider.organizations_metadata.organization_account_arn
|
||||
== f"arn:aws:organizations::{AWS_ACCOUNT_NUMBER}:account/{organization['Id']}/{AWS_ACCOUNT_NUMBER}"
|
||||
|
||||
@@ -871,6 +871,46 @@ class TestAWSMutelist:
|
||||
mutelist.is_muted(AWS_ACCOUNT_NUMBER, "check_test", "us-east-2", "test", "")
|
||||
)
|
||||
|
||||
def test_is_muted_search(self):
|
||||
# Mutelist
|
||||
mutelist_content = {
|
||||
"Accounts": {
|
||||
AWS_ACCOUNT_NUMBER: {
|
||||
"Checks": {
|
||||
"check_test": {
|
||||
"Regions": ["*"],
|
||||
"Resources": ["prowler"],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mutelist = AWSMutelist(mutelist_content=mutelist_content)
|
||||
|
||||
assert mutelist.is_muted(
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
"check_test",
|
||||
AWS_REGION_US_EAST_1,
|
||||
"prowler",
|
||||
"",
|
||||
)
|
||||
|
||||
assert mutelist.is_muted(
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
"check_test",
|
||||
AWS_REGION_US_EAST_1,
|
||||
"resource-prowler",
|
||||
"",
|
||||
)
|
||||
|
||||
assert mutelist.is_muted(
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
"check_test",
|
||||
AWS_REGION_US_EAST_1,
|
||||
"prowler-resource",
|
||||
"",
|
||||
)
|
||||
|
||||
def test_is_muted_in_region(self):
|
||||
muted_regions = [AWS_REGION_US_EAST_1, AWS_REGION_EU_WEST_1]
|
||||
finding_region = AWS_REGION_US_EAST_1
|
||||
@@ -1223,49 +1263,43 @@ class TestAWSMutelist:
|
||||
def test_is_muted_in_tags(self):
|
||||
mutelist_tags = ["environment=dev", "project=prowler"]
|
||||
|
||||
assert AWSMutelist.is_item_matched(mutelist_tags, "environment=dev", tag=True)
|
||||
assert AWSMutelist.is_item_matched(mutelist_tags, "environment=dev")
|
||||
|
||||
assert AWSMutelist.is_item_matched(
|
||||
mutelist_tags, "environment=dev | project=prowler", tag=True
|
||||
mutelist_tags, "environment=dev | project=prowler"
|
||||
)
|
||||
|
||||
assert AWSMutelist.is_item_matched(
|
||||
mutelist_tags, "environment=pro | project=prowler", tag=True
|
||||
mutelist_tags, "environment=pro | project=prowler"
|
||||
)
|
||||
|
||||
assert not (
|
||||
AWSMutelist.is_item_matched(mutelist_tags, "environment=pro", tag=True)
|
||||
)
|
||||
assert not (AWSMutelist.is_item_matched(mutelist_tags, "environment=pro"))
|
||||
|
||||
def test_is_muted_in_tags_with_piped_tags(self):
|
||||
mutelist_tags = ["environment=dev|project=prowler"]
|
||||
|
||||
assert AWSMutelist.is_item_matched(mutelist_tags, "environment=dev", tag=True)
|
||||
assert AWSMutelist.is_item_matched(mutelist_tags, "environment=dev")
|
||||
|
||||
assert AWSMutelist.is_item_matched(
|
||||
mutelist_tags, "environment=dev | project=prowler", tag=True
|
||||
mutelist_tags, "environment=dev | project=prowler"
|
||||
)
|
||||
|
||||
assert AWSMutelist.is_item_matched(
|
||||
mutelist_tags, "environment=pro | project=prowler", tag=True
|
||||
mutelist_tags, "environment=pro | project=prowler"
|
||||
)
|
||||
|
||||
assert not (
|
||||
AWSMutelist.is_item_matched(mutelist_tags, "environment=pro", tag=True)
|
||||
)
|
||||
assert not (AWSMutelist.is_item_matched(mutelist_tags, "environment=pro"))
|
||||
|
||||
def test_is_muted_in_tags_regex(self):
|
||||
mutelist_tags = ["environment=(dev|test)", ".*=prowler"]
|
||||
assert AWSMutelist.is_item_matched(
|
||||
mutelist_tags, "environment=test | proj=prowler", tag=True
|
||||
mutelist_tags, "environment=test | proj=prowler"
|
||||
)
|
||||
|
||||
assert AWSMutelist.is_item_matched(
|
||||
mutelist_tags, "env=prod | project=prowler", tag=True
|
||||
)
|
||||
assert AWSMutelist.is_item_matched(mutelist_tags, "env=prod | project=prowler")
|
||||
|
||||
assert not AWSMutelist.is_item_matched(
|
||||
mutelist_tags, "environment=prod | project=myproj", tag=True
|
||||
mutelist_tags, "environment=prod | project=myproj"
|
||||
)
|
||||
|
||||
def test_is_muted_in_tags_with_no_tags_in_finding(self):
|
||||
|
||||
@@ -42,7 +42,7 @@ class Test_AWS_Organizations:
|
||||
== f"arn:aws:organizations::{AWS_ACCOUNT_NUMBER}:organization/{org_id}"
|
||||
)
|
||||
assert org.organization_id == org_id
|
||||
assert org.account_tags == ["key:value"]
|
||||
assert org.account_tags == {"key": "value"}
|
||||
|
||||
def test_parse_organizations_metadata(self):
|
||||
tags = {"Tags": [{"Key": "test-key", "Value": "test-value"}]}
|
||||
@@ -70,4 +70,4 @@ class Test_AWS_Organizations:
|
||||
== f"arn:aws:organizations::{AWS_ACCOUNT_NUMBER}:account/{organization_name}/{AWS_ACCOUNT_NUMBER}"
|
||||
)
|
||||
assert org.organization_arn == arn
|
||||
assert org.account_tags == ["test-key:test-value"]
|
||||
assert org.account_tags == {"test-key": "test-value"}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
from prowler.providers.aws.lib.policy_condition_parser.policy_condition_parser import (
|
||||
is_condition_block_restrictive,
|
||||
is_condition_block_restrictive_organization,
|
||||
)
|
||||
|
||||
TRUSTED_AWS_ACCOUNT_NUMBER = "123456789012"
|
||||
NON_TRUSTED_AWS_ACCOUNT_NUMBER = "111222333444"
|
||||
|
||||
TRUSTED_ORGANIZATION_ID = "o-123456789012"
|
||||
NON_TRUSTED_ORGANIZATION_ID = "o-111222333444"
|
||||
|
||||
ALL_ORGS = "*"
|
||||
|
||||
|
||||
class Test_policy_condition_parser:
|
||||
# Test lowercase context key name --> aws
|
||||
@@ -1389,3 +1395,45 @@ class Test_policy_condition_parser:
|
||||
assert is_condition_block_restrictive(
|
||||
condition_statement, TRUSTED_AWS_ACCOUNT_NUMBER, True
|
||||
)
|
||||
|
||||
def test_condition_parser_string_equals_aws_PrincipalOrgID_list(self):
|
||||
condition_statement = {
|
||||
"StringEquals": {"aws:PrincipalOrgID": [TRUSTED_ORGANIZATION_ID]}
|
||||
}
|
||||
assert is_condition_block_restrictive_organization(condition_statement)
|
||||
|
||||
def test_condition_parser_string_equals_aws_PrincipalOrgID_list_multiple_items(
|
||||
self,
|
||||
):
|
||||
condition_statement = {
|
||||
"StringEquals": {
|
||||
"aws:PrincipalOrgID": [
|
||||
TRUSTED_ORGANIZATION_ID,
|
||||
NON_TRUSTED_ORGANIZATION_ID,
|
||||
]
|
||||
}
|
||||
}
|
||||
assert is_condition_block_restrictive_organization(condition_statement)
|
||||
|
||||
def test_condition_parser_string_equals_aws_PrincipalOrgID_str(self):
|
||||
condition_statement = {
|
||||
"StringEquals": {"aws:PrincipalOrgID": TRUSTED_ORGANIZATION_ID}
|
||||
}
|
||||
assert is_condition_block_restrictive_organization(condition_statement)
|
||||
|
||||
def test_condition_parser_string_equals_aws_All_Orgs_list_multiple_items(
|
||||
self,
|
||||
):
|
||||
condition_statement = {
|
||||
"StringEquals": {
|
||||
"aws:PrincipalOrgID": [
|
||||
TRUSTED_ORGANIZATION_ID,
|
||||
ALL_ORGS,
|
||||
]
|
||||
}
|
||||
}
|
||||
assert not is_condition_block_restrictive_organization(condition_statement)
|
||||
|
||||
def test_condition_parser_string_equals_aws_All_Orgs_str(self):
|
||||
condition_statement = {"StringEquals": {"aws:PrincipalOrgID": ALL_ORGS}}
|
||||
assert not is_condition_block_restrictive_organization(condition_statement)
|
||||
|
||||
@@ -26,7 +26,7 @@ FINDING = generate_finding_output(
|
||||
resource_uid="resource-123",
|
||||
resource_name="Example Resource",
|
||||
resource_details="Detailed information about the resource",
|
||||
resource_tags="tag1,tag2",
|
||||
resource_tags={"key1": "tag1", "key2": "tag2"},
|
||||
partition="aws",
|
||||
description="Description of the finding",
|
||||
risk="High",
|
||||
|
||||
@@ -6,6 +6,7 @@ from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER, AWS_REGION_EU_WEST_1
|
||||
|
||||
kms_key_id = str(uuid4())
|
||||
topic_name = "test-topic"
|
||||
org_id = "o-123456"
|
||||
topic_arn = f"arn:aws:sns:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:{topic_name}"
|
||||
test_policy_restricted = {
|
||||
"Statement": [
|
||||
@@ -53,6 +54,48 @@ test_policy_not_restricted = {
|
||||
]
|
||||
}
|
||||
|
||||
test_policy_restricted_principal_org_id = {
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {"AWS": "*"},
|
||||
"Action": ["sns:Publish"],
|
||||
"Resource": f"arn:aws:sns:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:{topic_name}",
|
||||
"Condition": {"StringEquals": {"aws:PrincipalOrgID": org_id}},
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
test_policy_restricted_all_org = {
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {"AWS": "*"},
|
||||
"Action": ["sns:Publish"],
|
||||
"Resource": f"arn:aws:sns:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:{topic_name}",
|
||||
"Condition": {"StringEquals": {"aws:PrincipalOrgID": "*"}},
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
test_policy_restricted_principal_account_organization = {
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {"AWS": "*"},
|
||||
"Action": ["sns:Publish"],
|
||||
"Resource": f"arn:aws:sns:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:{topic_name}",
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"aws:PrincipalOrgID": org_id,
|
||||
"aws:SourceAccount": AWS_ACCOUNT_NUMBER,
|
||||
}
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
class Test_sns_topics_not_publicly_accessible:
|
||||
def test_no_topics(self):
|
||||
@@ -81,6 +124,7 @@ class Test_sns_topics_not_publicly_accessible:
|
||||
region=AWS_REGION_EU_WEST_1,
|
||||
)
|
||||
)
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.sns.sns_service.SNS",
|
||||
sns_client,
|
||||
@@ -108,6 +152,7 @@ class Test_sns_topics_not_publicly_accessible:
|
||||
sns_client.topics.append(
|
||||
Topic(arn=topic_arn, name=topic_name, region=AWS_REGION_EU_WEST_1)
|
||||
)
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.sns.sns_service.SNS",
|
||||
sns_client,
|
||||
@@ -155,7 +200,7 @@ class Test_sns_topics_not_publicly_accessible:
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"SNS topic {topic_name} is not public because its policy only allows access from the same account."
|
||||
== f"SNS topic {topic_name} is not public because its policy only allows access from the account {AWS_ACCOUNT_NUMBER}."
|
||||
)
|
||||
assert result[0].resource_id == topic_name
|
||||
assert result[0].resource_arn == topic_arn
|
||||
@@ -188,7 +233,7 @@ class Test_sns_topics_not_publicly_accessible:
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"SNS topic {topic_name} is not public because its policy only allows access from the same account."
|
||||
== f"SNS topic {topic_name} is not public because its policy only allows access from the account {AWS_ACCOUNT_NUMBER}."
|
||||
)
|
||||
assert result[0].resource_id == topic_name
|
||||
assert result[0].resource_arn == topic_arn
|
||||
@@ -226,3 +271,111 @@ class Test_sns_topics_not_publicly_accessible:
|
||||
assert result[0].resource_arn == topic_arn
|
||||
assert result[0].region == AWS_REGION_EU_WEST_1
|
||||
assert result[0].resource_tags == []
|
||||
|
||||
def test_topic_public_with_principal_organization(self):
|
||||
sns_client = mock.MagicMock
|
||||
sns_client.audited_account = AWS_ACCOUNT_NUMBER
|
||||
sns_client.topics = []
|
||||
sns_client.topics.append(
|
||||
Topic(
|
||||
arn=topic_arn,
|
||||
name=topic_name,
|
||||
policy=test_policy_restricted_principal_org_id,
|
||||
region=AWS_REGION_EU_WEST_1,
|
||||
)
|
||||
)
|
||||
sns_client.provider = mock.MagicMock()
|
||||
sns_client.provider.organizations_metadata = mock.MagicMock()
|
||||
sns_client.provider.organizations_metadata.organization_id = org_id
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.sns.sns_service.SNS",
|
||||
sns_client,
|
||||
):
|
||||
from prowler.providers.aws.services.sns.sns_topics_not_publicly_accessible.sns_topics_not_publicly_accessible import (
|
||||
sns_topics_not_publicly_accessible,
|
||||
)
|
||||
|
||||
check = sns_topics_not_publicly_accessible()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"SNS topic {topic_name} is not public because its policy only allows access from an organization."
|
||||
)
|
||||
assert result[0].resource_id == topic_name
|
||||
assert result[0].resource_arn == topic_arn
|
||||
assert result[0].region == AWS_REGION_EU_WEST_1
|
||||
assert result[0].resource_tags == []
|
||||
|
||||
def test_topic_public_not_with_principal_organization(self):
|
||||
sns_client = mock.MagicMock
|
||||
sns_client.audited_account = AWS_ACCOUNT_NUMBER
|
||||
sns_client.topics = []
|
||||
sns_client.topics.append(
|
||||
Topic(
|
||||
arn=topic_arn,
|
||||
name=topic_name,
|
||||
policy=test_policy_restricted_all_org,
|
||||
region=AWS_REGION_EU_WEST_1,
|
||||
)
|
||||
)
|
||||
sns_client.provider = mock.MagicMock()
|
||||
sns_client.provider.organizations_metadata = mock.MagicMock()
|
||||
sns_client.provider.organizations_metadata.organization_id = org_id
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.sns.sns_service.SNS",
|
||||
sns_client,
|
||||
):
|
||||
from prowler.providers.aws.services.sns.sns_topics_not_publicly_accessible.sns_topics_not_publicly_accessible import (
|
||||
sns_topics_not_publicly_accessible,
|
||||
)
|
||||
|
||||
check = sns_topics_not_publicly_accessible()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"SNS topic {topic_name} is public because its policy allows public access."
|
||||
)
|
||||
assert result[0].resource_id == topic_name
|
||||
assert result[0].resource_arn == topic_arn
|
||||
assert result[0].region == AWS_REGION_EU_WEST_1
|
||||
assert result[0].resource_tags == []
|
||||
|
||||
def test_topic_public_with_principal_account_and_organization(self):
|
||||
sns_client = mock.MagicMock
|
||||
sns_client.audited_account = AWS_ACCOUNT_NUMBER
|
||||
sns_client.topics = []
|
||||
sns_client.topics.append(
|
||||
Topic(
|
||||
arn=topic_arn,
|
||||
name=topic_name,
|
||||
policy=test_policy_restricted_principal_account_organization,
|
||||
region=AWS_REGION_EU_WEST_1,
|
||||
)
|
||||
)
|
||||
sns_client.provider = mock.MagicMock()
|
||||
sns_client.provider.organizations_metadata = mock.MagicMock()
|
||||
sns_client.provider.organizations_metadata.organization_id = org_id
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.sns.sns_service.SNS",
|
||||
sns_client,
|
||||
):
|
||||
from prowler.providers.aws.services.sns.sns_topics_not_publicly_accessible.sns_topics_not_publicly_accessible import (
|
||||
sns_topics_not_publicly_accessible,
|
||||
)
|
||||
|
||||
check = sns_topics_not_publicly_accessible()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"SNS topic {topic_name} is not public because its policy only allows access from the account {AWS_ACCOUNT_NUMBER} and an organization."
|
||||
)
|
||||
assert result[0].resource_id == topic_name
|
||||
assert result[0].resource_arn == topic_arn
|
||||
assert result[0].region == AWS_REGION_EU_WEST_1
|
||||
assert result[0].resource_tags == []
|
||||
|
||||
@@ -583,6 +583,7 @@ def mock_api_instances_calls(client: MagicMock, service: str):
|
||||
"settings": {
|
||||
"ipConfiguration": {
|
||||
"requireSsl": True,
|
||||
"sslMode": "ENCRYPTED_ONLY",
|
||||
"authorizedNetworks": [{"value": "test"}],
|
||||
},
|
||||
"backupConfiguration": {"enabled": True},
|
||||
@@ -597,6 +598,7 @@ def mock_api_instances_calls(client: MagicMock, service: str):
|
||||
"settings": {
|
||||
"ipConfiguration": {
|
||||
"requireSsl": False,
|
||||
"sslMode": "ALLOW_UNENCRYPTED_AND_ENCRYPTED",
|
||||
"authorizedNetworks": [{"value": "test"}],
|
||||
},
|
||||
"backupConfiguration": {"enabled": False},
|
||||
|
||||
@@ -29,7 +29,7 @@ class TestGCPProvider:
|
||||
number="55555555",
|
||||
id="project/55555555",
|
||||
name="test-project",
|
||||
labels=["test:value"],
|
||||
labels={"test": "value"},
|
||||
lifecycle_state="",
|
||||
)
|
||||
}
|
||||
@@ -75,7 +75,7 @@ class TestGCPProvider:
|
||||
number="55555555",
|
||||
id="project/55555555",
|
||||
name="test-project",
|
||||
labels=["test:value"],
|
||||
labels={"test": "value"},
|
||||
lifecycle_state="",
|
||||
)
|
||||
}
|
||||
@@ -148,7 +148,7 @@ class TestGCPProvider:
|
||||
number="55555555",
|
||||
id="project/55555555",
|
||||
name="test-project",
|
||||
labels=["test:value"],
|
||||
labels={"test": "value"},
|
||||
lifecycle_state="",
|
||||
)
|
||||
}
|
||||
@@ -200,7 +200,7 @@ class TestGCPProvider:
|
||||
number="55555555",
|
||||
id="project/55555555",
|
||||
name="test-project",
|
||||
labels=["test:value"],
|
||||
labels={"test": "value"},
|
||||
lifecycle_state="",
|
||||
)
|
||||
}
|
||||
@@ -242,7 +242,7 @@ class TestGCPProvider:
|
||||
number="55555555",
|
||||
id="project/55555555",
|
||||
name="test-project",
|
||||
labels=["test:value"],
|
||||
labels={"test": "value"},
|
||||
lifecycle_state="",
|
||||
)
|
||||
}
|
||||
@@ -292,7 +292,7 @@ class TestGCPProvider:
|
||||
number="55555555",
|
||||
id="project/55555555",
|
||||
name="test-project",
|
||||
labels=["test:value"],
|
||||
labels={"test": "value"},
|
||||
lifecycle_state="",
|
||||
)
|
||||
}
|
||||
@@ -341,7 +341,7 @@ class TestGCPProvider:
|
||||
number="55555555",
|
||||
id="project/55555555",
|
||||
name="test-project",
|
||||
labels=["test:value"],
|
||||
labels={"test": "value"},
|
||||
lifecycle_state="",
|
||||
)
|
||||
}
|
||||
@@ -390,14 +390,14 @@ class TestGCPProvider:
|
||||
number="55555555",
|
||||
id="project/55555555",
|
||||
name="test-project",
|
||||
labels=["test:value"],
|
||||
labels={"test": "value"},
|
||||
lifecycle_state="",
|
||||
),
|
||||
"test-excluded-project": GCPProject(
|
||||
number="12345678",
|
||||
id="project/12345678",
|
||||
name="test-excluded-project",
|
||||
labels=["test:value"],
|
||||
labels={"test": "value"},
|
||||
lifecycle_state="",
|
||||
),
|
||||
}
|
||||
|
||||
@@ -52,7 +52,8 @@ class Test_cloudsql_instance_automated_backups:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -97,7 +98,8 @@ class Test_cloudsql_instance_automated_backups:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=False,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
|
||||
@@ -52,7 +52,8 @@ class Test_cloudsql_instance_mysql_local_infile_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -88,7 +89,8 @@ class Test_cloudsql_instance_mysql_local_infile_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -133,7 +135,8 @@ class Test_cloudsql_instance_mysql_local_infile_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "local_infile", "value": "off"}],
|
||||
@@ -178,7 +181,8 @@ class Test_cloudsql_instance_mysql_local_infile_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "local_infile", "value": "on"}],
|
||||
|
||||
@@ -52,7 +52,8 @@ class Test_cloudsql_instance_mysql_skip_show_database_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -88,7 +89,8 @@ class Test_cloudsql_instance_mysql_skip_show_database_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -133,7 +135,8 @@ class Test_cloudsql_instance_mysql_skip_show_database_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "skip_show_database", "value": "off"}],
|
||||
@@ -178,7 +181,8 @@ class Test_cloudsql_instance_mysql_skip_show_database_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "skip_show_database", "value": "on"}],
|
||||
|
||||
@@ -52,7 +52,8 @@ class Test_cloudsql_instance_postgres_enable_pgaudit_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -88,7 +89,8 @@ class Test_cloudsql_instance_postgres_enable_pgaudit_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -133,7 +135,8 @@ class Test_cloudsql_instance_postgres_enable_pgaudit_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "cloudsql.enable_pgaudit", "value": "off"}],
|
||||
@@ -178,7 +181,8 @@ class Test_cloudsql_instance_postgres_enable_pgaudit_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "cloudsql.enable_pgaudit", "value": "on"}],
|
||||
|
||||
@@ -52,7 +52,8 @@ class Test_cloudsql_instance_postgres_log_connections_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -88,7 +89,8 @@ class Test_cloudsql_instance_postgres_log_connections_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -133,7 +135,8 @@ class Test_cloudsql_instance_postgres_log_connections_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "log_connections", "value": "off"}],
|
||||
@@ -178,7 +181,8 @@ class Test_cloudsql_instance_postgres_log_connections_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "log_connections", "value": "on"}],
|
||||
|
||||
@@ -52,7 +52,8 @@ class Test_cloudsql_instance_postgres_log_disconnections_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -88,7 +89,8 @@ class Test_cloudsql_instance_postgres_log_disconnections_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -133,7 +135,8 @@ class Test_cloudsql_instance_postgres_log_disconnections_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "log_disconnections", "value": "off"}],
|
||||
@@ -178,7 +181,8 @@ class Test_cloudsql_instance_postgres_log_disconnections_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "log_disconnections", "value": "on"}],
|
||||
|
||||
@@ -52,7 +52,8 @@ class Test_cloudsql_instance_postgres_log_error_verbosity_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -88,7 +89,8 @@ class Test_cloudsql_instance_postgres_log_error_verbosity_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -133,7 +135,8 @@ class Test_cloudsql_instance_postgres_log_error_verbosity_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "log_error_verbosity", "value": "off"}],
|
||||
@@ -178,7 +181,8 @@ class Test_cloudsql_instance_postgres_log_error_verbosity_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "log_error_verbosity", "value": "default"}],
|
||||
|
||||
@@ -52,7 +52,8 @@ class Test_cloudsql_instance_postgres_log_min_duration_statement_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -88,7 +89,8 @@ class Test_cloudsql_instance_postgres_log_min_duration_statement_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -133,7 +135,8 @@ class Test_cloudsql_instance_postgres_log_min_duration_statement_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "log_min_duration_statement", "value": "0"}],
|
||||
@@ -178,7 +181,8 @@ class Test_cloudsql_instance_postgres_log_min_duration_statement_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "log_min_duration_statement", "value": "-1"}],
|
||||
|
||||
@@ -52,7 +52,8 @@ class Test_cloudsql_instance_postgres_log_min_error_statement_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -88,7 +89,8 @@ class Test_cloudsql_instance_postgres_log_min_error_statement_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -133,7 +135,8 @@ class Test_cloudsql_instance_postgres_log_min_error_statement_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "log_min_error_statement", "value": "warning"}],
|
||||
@@ -178,7 +181,8 @@ class Test_cloudsql_instance_postgres_log_min_error_statement_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "log_min_error_statement", "value": "error"}],
|
||||
|
||||
@@ -52,7 +52,8 @@ class Test_cloudsql_instance_postgres_log_min_messages_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -88,7 +89,8 @@ class Test_cloudsql_instance_postgres_log_min_messages_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -133,7 +135,8 @@ class Test_cloudsql_instance_postgres_log_min_messages_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "log_min_messages", "value": "debug"}],
|
||||
@@ -178,7 +181,8 @@ class Test_cloudsql_instance_postgres_log_min_messages_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "log_min_messages", "value": "error"}],
|
||||
|
||||
@@ -52,7 +52,8 @@ class Test_cloudsql_instance_postgres_log_statement_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -88,7 +89,8 @@ class Test_cloudsql_instance_postgres_log_statement_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -133,7 +135,8 @@ class Test_cloudsql_instance_postgres_log_statement_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "log_statement", "value": "all"}],
|
||||
@@ -178,7 +181,8 @@ class Test_cloudsql_instance_postgres_log_statement_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "log_statement", "value": "ddl"}],
|
||||
|
||||
@@ -52,7 +52,8 @@ class Test_cloudsql_instance_public_access:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[{"value": "192.168.1.1/32"}],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
@@ -97,7 +98,8 @@ class Test_cloudsql_instance_public_access:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[{"value": "0.0.0.0/0"}],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
|
||||
@@ -52,7 +52,8 @@ class Test_cloudsql_instance_public_ip:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -97,7 +98,8 @@ class Test_cloudsql_instance_public_ip:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=True,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
|
||||
@@ -52,7 +52,8 @@ class Test_cloudsql_instance_sqlserver_contained_database_authentication_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -88,7 +89,8 @@ class Test_cloudsql_instance_sqlserver_contained_database_authentication_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -133,7 +135,8 @@ class Test_cloudsql_instance_sqlserver_contained_database_authentication_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[
|
||||
@@ -180,7 +183,8 @@ class Test_cloudsql_instance_sqlserver_contained_database_authentication_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[
|
||||
|
||||
@@ -52,7 +52,8 @@ class Test_cloudsql_instance_sqlserver_cross_db_ownership_chaining_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -88,7 +89,8 @@ class Test_cloudsql_instance_sqlserver_cross_db_ownership_chaining_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -133,7 +135,8 @@ class Test_cloudsql_instance_sqlserver_cross_db_ownership_chaining_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "cross db ownership chaining", "value": "on"}],
|
||||
@@ -178,7 +181,8 @@ class Test_cloudsql_instance_sqlserver_cross_db_ownership_chaining_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "cross db ownership chaining", "value": "off"}],
|
||||
|
||||
@@ -52,7 +52,8 @@ class Test_cloudsql_instance_sqlserver_external_scripts_enabled_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -88,7 +89,8 @@ class Test_cloudsql_instance_sqlserver_external_scripts_enabled_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -133,7 +135,8 @@ class Test_cloudsql_instance_sqlserver_external_scripts_enabled_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "external scripts enabled", "value": "on"}],
|
||||
@@ -178,7 +181,8 @@ class Test_cloudsql_instance_sqlserver_external_scripts_enabled_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "external scripts enabled", "value": "off"}],
|
||||
|
||||
@@ -52,7 +52,8 @@ class Test_cloudsql_instance_sqlserver_remote_access_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -88,7 +89,8 @@ class Test_cloudsql_instance_sqlserver_remote_access_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -133,7 +135,8 @@ class Test_cloudsql_instance_sqlserver_remote_access_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "remote access", "value": "on"}],
|
||||
@@ -178,7 +181,8 @@ class Test_cloudsql_instance_sqlserver_remote_access_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "remote access", "value": "off"}],
|
||||
|
||||
@@ -52,7 +52,8 @@ class Test_cloudsql_instance_sqlserver_trace_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -88,7 +89,8 @@ class Test_cloudsql_instance_sqlserver_trace_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -133,7 +135,8 @@ class Test_cloudsql_instance_sqlserver_trace_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "3625", "value": "off"}],
|
||||
@@ -178,7 +181,8 @@ class Test_cloudsql_instance_sqlserver_trace_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "3625", "value": "on"}],
|
||||
|
||||
@@ -52,7 +52,8 @@ class Test_cloudsql_instance_sqlserver_user_connections_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -88,7 +89,8 @@ class Test_cloudsql_instance_sqlserver_user_connections_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -133,7 +135,8 @@ class Test_cloudsql_instance_sqlserver_user_connections_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "user connections", "value": "1"}],
|
||||
@@ -178,7 +181,8 @@ class Test_cloudsql_instance_sqlserver_user_connections_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "user connections", "value": "0"}],
|
||||
|
||||
@@ -52,7 +52,8 @@ class Test_cloudsql_instance_sqlserver_user_options_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -88,7 +89,8 @@ class Test_cloudsql_instance_sqlserver_user_options_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -133,7 +135,8 @@ class Test_cloudsql_instance_sqlserver_user_options_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "user options", "value": ""}],
|
||||
@@ -178,7 +181,8 @@ class Test_cloudsql_instance_sqlserver_user_options_flag:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[{"name": "user options", "value": "0"}],
|
||||
|
||||
@@ -28,7 +28,7 @@ class Test_cloudsql_instance_ssl_connections:
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_cloudsql_instance_ssl_connections_enabled(self):
|
||||
def test_cloudsql_instance_ssl_connections_enabled_and_ssl_mode_encrypted(self):
|
||||
cloudsql_client = mock.MagicMock
|
||||
|
||||
with mock.patch(
|
||||
@@ -52,7 +52,8 @@ class Test_cloudsql_instance_ssl_connections:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=True,
|
||||
require_ssl=True,
|
||||
ssl_mode="ENCRYPTED_ONLY",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
@@ -73,7 +74,7 @@ class Test_cloudsql_instance_ssl_connections:
|
||||
assert result[0].location == GCP_EU1_LOCATION
|
||||
assert result[0].project_id == GCP_PROJECT_ID
|
||||
|
||||
def test_cloudsql_instance_ssl_connections_disabled(self):
|
||||
def test_cloudsql_instance_ssl_connections_enabled_and_ssl_mode_not_encrypted(self):
|
||||
cloudsql_client = mock.MagicMock
|
||||
|
||||
with mock.patch(
|
||||
@@ -97,7 +98,56 @@ class Test_cloudsql_instance_ssl_connections:
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
ssl=False,
|
||||
require_ssl=True,
|
||||
ssl_mode="ALLOW_UNENCRYPTED_AND_ENCRYPTED",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
]
|
||||
|
||||
check = cloudsql_instance_ssl_connections()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Database Instance instance1 does not require SSL connections."
|
||||
)
|
||||
assert result[0].resource_id == "instance1"
|
||||
assert result[0].resource_name == "instance1"
|
||||
assert result[0].location == GCP_EU1_LOCATION
|
||||
assert result[0].project_id == GCP_PROJECT_ID
|
||||
|
||||
def test_cloudsql_instance_ssl_connections_disabled_and_ssl_mode_not_encrypted(
|
||||
self,
|
||||
):
|
||||
cloudsql_client = mock.MagicMock
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.gcp.services.cloudsql.cloudsql_instance_ssl_connections.cloudsql_instance_ssl_connections.cloudsql_client",
|
||||
new=cloudsql_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.cloudsql.cloudsql_instance_ssl_connections.cloudsql_instance_ssl_connections import (
|
||||
cloudsql_instance_ssl_connections,
|
||||
)
|
||||
from prowler.providers.gcp.services.cloudsql.cloudsql_service import (
|
||||
Instance,
|
||||
)
|
||||
|
||||
cloudsql_client.instances = [
|
||||
Instance(
|
||||
name="instance1",
|
||||
version="POSTGRES_15",
|
||||
ip_addresses=[],
|
||||
region=GCP_EU1_LOCATION,
|
||||
public_ip=False,
|
||||
require_ssl=False,
|
||||
ssl_mode="ALLOW_UNENCRYPTED_AND_ENCRYPTED",
|
||||
automated_backups=True,
|
||||
authorized_networks=[],
|
||||
flags=[],
|
||||
|
||||
@@ -33,7 +33,8 @@ class TestCloudSQLService:
|
||||
{"type": "PRIMARY", "ipAddress": "66.66.66.66"}
|
||||
]
|
||||
assert cloudsql_client.instances[0].public_ip
|
||||
assert cloudsql_client.instances[0].ssl
|
||||
assert cloudsql_client.instances[0].require_ssl
|
||||
assert cloudsql_client.instances[0].ssl_mode == "ENCRYPTED_ONLY"
|
||||
assert cloudsql_client.instances[0].automated_backups
|
||||
assert cloudsql_client.instances[0].authorized_networks == [
|
||||
{"value": "test"}
|
||||
@@ -48,7 +49,11 @@ class TestCloudSQLService:
|
||||
{"type": "PRIMARY", "ipAddress": "22.22.22.22"}
|
||||
]
|
||||
assert cloudsql_client.instances[1].public_ip
|
||||
assert not cloudsql_client.instances[1].ssl
|
||||
assert not cloudsql_client.instances[1].require_ssl
|
||||
assert (
|
||||
cloudsql_client.instances[1].ssl_mode
|
||||
== "ALLOW_UNENCRYPTED_AND_ENCRYPTED"
|
||||
)
|
||||
assert not cloudsql_client.instances[1].automated_backups
|
||||
assert cloudsql_client.instances[1].authorized_networks == [
|
||||
{"value": "test"}
|
||||
|
||||
@@ -65,8 +65,10 @@ class Test_kms_key_not_publicly_accessible_gcp:
|
||||
kms_client.crypto_keys = [
|
||||
CriptoKey(
|
||||
name="key1",
|
||||
id="projects/123/locations/us-central1/keyRings/keyring1/cryptoKeys/key1",
|
||||
project_id=GCP_PROJECT_ID,
|
||||
rotation_period="7776000s",
|
||||
next_rotation_time="2021-01-01T00:00:00Z",
|
||||
key_ring=keyring.name,
|
||||
location=keylocation.name,
|
||||
members=["allUsers"],
|
||||
@@ -81,7 +83,7 @@ class Test_kms_key_not_publicly_accessible_gcp:
|
||||
result[0].status_extended
|
||||
== f"Key {kms_client.crypto_keys[0].name} may be publicly accessible."
|
||||
)
|
||||
assert result[0].resource_id == kms_client.crypto_keys[0].name
|
||||
assert result[0].resource_id == kms_client.crypto_keys[0].id
|
||||
assert result[0].resource_name == kms_client.crypto_keys[0].name
|
||||
assert result[0].location == kms_client.crypto_keys[0].location
|
||||
assert result[0].project_id == kms_client.crypto_keys[0].project_id
|
||||
@@ -121,8 +123,10 @@ class Test_kms_key_not_publicly_accessible_gcp:
|
||||
kms_client.crypto_keys = [
|
||||
CriptoKey(
|
||||
name="key1",
|
||||
id="projects/123/locations/us-central1/keyRings/keyring1/cryptoKeys/key1",
|
||||
project_id=GCP_PROJECT_ID,
|
||||
rotation_period="7776000s",
|
||||
next_rotation_time="2021-01-01T00:00:00Z",
|
||||
key_ring=keyring.name,
|
||||
location=keylocation.name,
|
||||
members=["user:jane@example.com"],
|
||||
@@ -137,7 +141,7 @@ class Test_kms_key_not_publicly_accessible_gcp:
|
||||
result[0].status_extended
|
||||
== f"Key {kms_client.crypto_keys[0].name} is not exposed to Public."
|
||||
)
|
||||
assert result[0].resource_id == kms_client.crypto_keys[0].name
|
||||
assert result[0].resource_id == kms_client.crypto_keys[0].id
|
||||
assert result[0].resource_name == kms_client.crypto_keys[0].name
|
||||
assert result[0].location == kms_client.crypto_keys[0].location
|
||||
assert result[0].project_id == kms_client.crypto_keys[0].project_id
|
||||
@@ -177,8 +181,10 @@ class Test_kms_key_not_publicly_accessible_gcp:
|
||||
kms_client.crypto_keys = [
|
||||
CriptoKey(
|
||||
name="key1",
|
||||
id="projects/123/locations/us-central1/keyRings/keyring1/cryptoKeys/key1",
|
||||
project_id=GCP_PROJECT_ID,
|
||||
rotation_period="7776000s",
|
||||
next_rotation_time="2021-01-01T00:00:00Z",
|
||||
key_ring=keyring.name,
|
||||
location=keylocation.name,
|
||||
members=[],
|
||||
@@ -193,7 +199,7 @@ class Test_kms_key_not_publicly_accessible_gcp:
|
||||
result[0].status_extended
|
||||
== f"Key {kms_client.crypto_keys[0].name} is not exposed to Public."
|
||||
)
|
||||
assert result[0].resource_id == kms_client.crypto_keys[0].name
|
||||
assert result[0].resource_id == kms_client.crypto_keys[0].id
|
||||
assert result[0].resource_name == kms_client.crypto_keys[0].name
|
||||
assert result[0].location == kms_client.crypto_keys[0].location
|
||||
assert result[0].project_id == kms_client.crypto_keys[0].project_id
|
||||
|
||||
@@ -30,7 +30,7 @@ class Test_kms_key_rotation_enabled:
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_kms_key_no_rotation_period(self):
|
||||
def test_kms_key_no_next_rotation_time_and_no_rotation_period(self):
|
||||
kms_client = mock.MagicMock
|
||||
|
||||
with mock.patch(
|
||||
@@ -65,6 +65,7 @@ class Test_kms_key_rotation_enabled:
|
||||
kms_client.crypto_keys = [
|
||||
CriptoKey(
|
||||
name="key1",
|
||||
id="projects/123/locations/us-central1/keyRings/keyring1/cryptoKeys/key1",
|
||||
project_id=GCP_PROJECT_ID,
|
||||
key_ring=keyring.name,
|
||||
location=keylocation.name,
|
||||
@@ -78,14 +79,14 @@ class Test_kms_key_rotation_enabled:
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Key {kms_client.crypto_keys[0].name} is not rotated every 90 days or less."
|
||||
== f"Key {kms_client.crypto_keys[0].name} is not rotated every 90 days or less and the next rotation time is in more than 90 days."
|
||||
)
|
||||
assert result[0].resource_id == kms_client.crypto_keys[0].name
|
||||
assert result[0].resource_id == kms_client.crypto_keys[0].id
|
||||
assert result[0].resource_name == kms_client.crypto_keys[0].name
|
||||
assert result[0].location == kms_client.crypto_keys[0].location
|
||||
assert result[0].project_id == kms_client.crypto_keys[0].project_id
|
||||
|
||||
def test_kms_key_rotation_period_greater_90_days(self):
|
||||
def test_kms_key_no_next_rotation_time_and_big_rotation_period(self):
|
||||
kms_client = mock.MagicMock
|
||||
|
||||
with mock.patch(
|
||||
@@ -120,8 +121,238 @@ class Test_kms_key_rotation_enabled:
|
||||
kms_client.crypto_keys = [
|
||||
CriptoKey(
|
||||
name="key1",
|
||||
id="projects/123/locations/us-central1/keyRings/keyring1/cryptoKeys/key1",
|
||||
project_id=GCP_PROJECT_ID,
|
||||
key_ring=keyring.name,
|
||||
location=keylocation.name,
|
||||
rotation_period="8776000s",
|
||||
members=["user:jane@example.com"],
|
||||
)
|
||||
]
|
||||
|
||||
check = kms_key_rotation_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Key {kms_client.crypto_keys[0].name} is not rotated every 90 days or less and the next rotation time is in more than 90 days."
|
||||
)
|
||||
assert result[0].resource_id == kms_client.crypto_keys[0].id
|
||||
assert result[0].resource_name == kms_client.crypto_keys[0].name
|
||||
assert result[0].location == kms_client.crypto_keys[0].location
|
||||
assert result[0].project_id == kms_client.crypto_keys[0].project_id
|
||||
|
||||
def test_kms_key_no_next_rotation_time_and_appropriate_rotation_period(self):
|
||||
kms_client = mock.MagicMock
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.gcp.services.kms.kms_key_rotation_enabled.kms_key_rotation_enabled.kms_client",
|
||||
new=kms_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.kms.kms_key_rotation_enabled.kms_key_rotation_enabled import (
|
||||
kms_key_rotation_enabled,
|
||||
)
|
||||
from prowler.providers.gcp.services.kms.kms_service import (
|
||||
CriptoKey,
|
||||
KeyLocation,
|
||||
KeyRing,
|
||||
)
|
||||
|
||||
kms_client.project_ids = [GCP_PROJECT_ID]
|
||||
kms_client.region = GCP_US_CENTER1_LOCATION
|
||||
|
||||
keyring = KeyRing(
|
||||
name="projects/123/locations/us-central1/keyRings/keyring1",
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
keylocation = KeyLocation(
|
||||
name=GCP_US_CENTER1_LOCATION,
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
kms_client.crypto_keys = [
|
||||
CriptoKey(
|
||||
name="key1",
|
||||
id="projects/123/locations/us-central1/keyRings/keyring1/cryptoKeys/key1",
|
||||
project_id=GCP_PROJECT_ID,
|
||||
key_ring=keyring.name,
|
||||
location=keylocation.name,
|
||||
rotation_period="7776000s",
|
||||
members=["user:jane@example.com"],
|
||||
)
|
||||
]
|
||||
|
||||
check = kms_key_rotation_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Key {kms_client.crypto_keys[0].name} is rotated every 90 days or less but the next rotation time is in more than 90 days."
|
||||
)
|
||||
assert result[0].resource_id == kms_client.crypto_keys[0].id
|
||||
assert result[0].resource_name == kms_client.crypto_keys[0].name
|
||||
assert result[0].location == kms_client.crypto_keys[0].location
|
||||
assert result[0].project_id == kms_client.crypto_keys[0].project_id
|
||||
|
||||
def test_kms_key_no_rotation_period_and_big_next_rotation_time(self):
|
||||
kms_client = mock.MagicMock
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.gcp.services.kms.kms_key_rotation_enabled.kms_key_rotation_enabled.kms_client",
|
||||
new=kms_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.kms.kms_key_rotation_enabled.kms_key_rotation_enabled import (
|
||||
kms_key_rotation_enabled,
|
||||
)
|
||||
from prowler.providers.gcp.services.kms.kms_service import (
|
||||
CriptoKey,
|
||||
KeyLocation,
|
||||
KeyRing,
|
||||
)
|
||||
|
||||
kms_client.project_ids = [GCP_PROJECT_ID]
|
||||
kms_client.region = GCP_US_CENTER1_LOCATION
|
||||
|
||||
keyring = KeyRing(
|
||||
name="projects/123/locations/us-central1/keyRings/keyring1",
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
keylocation = KeyLocation(
|
||||
name=GCP_US_CENTER1_LOCATION,
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
kms_client.crypto_keys = [
|
||||
CriptoKey(
|
||||
name="key1",
|
||||
id="projects/123/locations/us-central1/keyRings/keyring1/cryptoKeys/key1",
|
||||
project_id=GCP_PROJECT_ID,
|
||||
key_ring=keyring.name,
|
||||
location=keylocation.name,
|
||||
next_rotation_time="2025-09-01T00:00:00Z",
|
||||
members=["user:jane@example.com"],
|
||||
)
|
||||
]
|
||||
|
||||
check = kms_key_rotation_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Key {kms_client.crypto_keys[0].name} is not rotated every 90 days or less and the next rotation time is in more than 90 days."
|
||||
)
|
||||
assert result[0].resource_id == kms_client.crypto_keys[0].id
|
||||
assert result[0].resource_name == kms_client.crypto_keys[0].name
|
||||
assert result[0].location == kms_client.crypto_keys[0].location
|
||||
assert result[0].project_id == kms_client.crypto_keys[0].project_id
|
||||
|
||||
def test_kms_key_no_rotation_period_and_appropriate_next_rotation_time(self):
|
||||
kms_client = mock.MagicMock
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.gcp.services.kms.kms_key_rotation_enabled.kms_key_rotation_enabled.kms_client",
|
||||
new=kms_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.kms.kms_key_rotation_enabled.kms_key_rotation_enabled import (
|
||||
kms_key_rotation_enabled,
|
||||
)
|
||||
from prowler.providers.gcp.services.kms.kms_service import (
|
||||
CriptoKey,
|
||||
KeyLocation,
|
||||
KeyRing,
|
||||
)
|
||||
|
||||
kms_client.project_ids = [GCP_PROJECT_ID]
|
||||
kms_client.region = GCP_US_CENTER1_LOCATION
|
||||
|
||||
keyring = KeyRing(
|
||||
name="projects/123/locations/us-central1/keyRings/keyring1",
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
keylocation = KeyLocation(
|
||||
name=GCP_US_CENTER1_LOCATION,
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
kms_client.crypto_keys = [
|
||||
CriptoKey(
|
||||
name="key1",
|
||||
id="projects/123/locations/us-central1/keyRings/keyring1/cryptoKeys/key1",
|
||||
project_id=GCP_PROJECT_ID,
|
||||
key_ring=keyring.name,
|
||||
location=keylocation.name,
|
||||
next_rotation_time="2024-09-01T00:00:00Z",
|
||||
members=["user:jane@example.com"],
|
||||
)
|
||||
]
|
||||
|
||||
check = kms_key_rotation_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Key {kms_client.crypto_keys[0].name} is not rotated every 90 days or less but the next rotation time is in less than 90 days."
|
||||
)
|
||||
assert result[0].resource_id == kms_client.crypto_keys[0].id
|
||||
assert result[0].resource_name == kms_client.crypto_keys[0].name
|
||||
assert result[0].location == kms_client.crypto_keys[0].location
|
||||
assert result[0].project_id == kms_client.crypto_keys[0].project_id
|
||||
|
||||
def test_kms_key_rotation_period_greater_90_days_and_big_next_rotation_time(self):
|
||||
kms_client = mock.MagicMock
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.gcp.services.kms.kms_key_rotation_enabled.kms_key_rotation_enabled.kms_client",
|
||||
new=kms_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.kms.kms_key_rotation_enabled.kms_key_rotation_enabled import (
|
||||
kms_key_rotation_enabled,
|
||||
)
|
||||
from prowler.providers.gcp.services.kms.kms_service import (
|
||||
CriptoKey,
|
||||
KeyLocation,
|
||||
KeyRing,
|
||||
)
|
||||
|
||||
kms_client.project_ids = [GCP_PROJECT_ID]
|
||||
kms_client.region = GCP_US_CENTER1_LOCATION
|
||||
|
||||
keyring = KeyRing(
|
||||
name="projects/123/locations/us-central1/keyRings/keyring1",
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
keylocation = KeyLocation(
|
||||
name=GCP_US_CENTER1_LOCATION,
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
kms_client.crypto_keys = [
|
||||
CriptoKey(
|
||||
name="key1",
|
||||
id="projects/123/locations/us-central1/keyRings/keyring1/cryptoKeys/key1",
|
||||
project_id=GCP_PROJECT_ID,
|
||||
rotation_period="8776000s",
|
||||
next_rotation_time="2025-09-01T00:00:00Z",
|
||||
key_ring=keyring.name,
|
||||
location=keylocation.name,
|
||||
members=["user:jane@example.com"],
|
||||
@@ -134,14 +365,16 @@ class Test_kms_key_rotation_enabled:
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Key {kms_client.crypto_keys[0].name} is not rotated every 90 days or less."
|
||||
== f"Key {kms_client.crypto_keys[0].name} is not rotated every 90 days or less and the next rotation time is in more than 90 days."
|
||||
)
|
||||
assert result[0].resource_id == kms_client.crypto_keys[0].name
|
||||
assert result[0].resource_id == kms_client.crypto_keys[0].id
|
||||
assert result[0].resource_name == kms_client.crypto_keys[0].name
|
||||
assert result[0].location == kms_client.crypto_keys[0].location
|
||||
assert result[0].project_id == kms_client.crypto_keys[0].project_id
|
||||
|
||||
def test_kms_key_rotation_period_less_90_days(self):
|
||||
def test_kms_key_rotation_period_greater_90_days_and_appropriate_next_rotation_time(
|
||||
self,
|
||||
):
|
||||
kms_client = mock.MagicMock
|
||||
|
||||
with mock.patch(
|
||||
@@ -176,8 +409,128 @@ class Test_kms_key_rotation_enabled:
|
||||
kms_client.crypto_keys = [
|
||||
CriptoKey(
|
||||
name="key1",
|
||||
id="projects/123/locations/us-central1/keyRings/keyring1/cryptoKeys/key1",
|
||||
project_id=GCP_PROJECT_ID,
|
||||
rotation_period="8776000s",
|
||||
next_rotation_time="2024-09-01T00:00:00Z",
|
||||
key_ring=keyring.name,
|
||||
location=keylocation.name,
|
||||
members=["user:jane@example.com"],
|
||||
)
|
||||
]
|
||||
|
||||
check = kms_key_rotation_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Key {kms_client.crypto_keys[0].name} is not rotated every 90 days or less but the next rotation time is in less than 90 days."
|
||||
)
|
||||
assert result[0].resource_id == kms_client.crypto_keys[0].id
|
||||
assert result[0].resource_name == kms_client.crypto_keys[0].name
|
||||
assert result[0].location == kms_client.crypto_keys[0].location
|
||||
assert result[0].project_id == kms_client.crypto_keys[0].project_id
|
||||
|
||||
def test_kms_key_rotation_period_less_90_days_and_big_next_rotation_time(self):
|
||||
kms_client = mock.MagicMock
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.gcp.services.kms.kms_key_rotation_enabled.kms_key_rotation_enabled.kms_client",
|
||||
new=kms_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.kms.kms_key_rotation_enabled.kms_key_rotation_enabled import (
|
||||
kms_key_rotation_enabled,
|
||||
)
|
||||
from prowler.providers.gcp.services.kms.kms_service import (
|
||||
CriptoKey,
|
||||
KeyLocation,
|
||||
KeyRing,
|
||||
)
|
||||
|
||||
kms_client.project_ids = [GCP_PROJECT_ID]
|
||||
kms_client.region = GCP_US_CENTER1_LOCATION
|
||||
|
||||
keyring = KeyRing(
|
||||
name="projects/123/locations/us-central1/keyRings/keyring1",
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
keylocation = KeyLocation(
|
||||
name=GCP_US_CENTER1_LOCATION,
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
kms_client.crypto_keys = [
|
||||
CriptoKey(
|
||||
name="key1",
|
||||
id="projects/123/locations/us-central1/keyRings/keyring1/cryptoKeys/key1",
|
||||
project_id=GCP_PROJECT_ID,
|
||||
rotation_period="7776000s",
|
||||
next_rotation_time="2025-09-01T00:00:00Z",
|
||||
key_ring=keyring.name,
|
||||
location=keylocation.name,
|
||||
members=["user:jane@example.com"],
|
||||
)
|
||||
]
|
||||
|
||||
check = kms_key_rotation_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Key {kms_client.crypto_keys[0].name} is rotated every 90 days or less but the next rotation time is in more than 90 days."
|
||||
)
|
||||
assert result[0].resource_id == kms_client.crypto_keys[0].id
|
||||
assert result[0].resource_name == kms_client.crypto_keys[0].name
|
||||
assert result[0].location == kms_client.crypto_keys[0].location
|
||||
assert result[0].project_id == kms_client.crypto_keys[0].project_id
|
||||
|
||||
def test_kms_key_rotation_period_less_90_days_and_appropriate_next_rotation_time(
|
||||
self,
|
||||
):
|
||||
kms_client = mock.MagicMock
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
), mock.patch(
|
||||
"prowler.providers.gcp.services.kms.kms_key_rotation_enabled.kms_key_rotation_enabled.kms_client",
|
||||
new=kms_client,
|
||||
):
|
||||
from prowler.providers.gcp.services.kms.kms_key_rotation_enabled.kms_key_rotation_enabled import (
|
||||
kms_key_rotation_enabled,
|
||||
)
|
||||
from prowler.providers.gcp.services.kms.kms_service import (
|
||||
CriptoKey,
|
||||
KeyLocation,
|
||||
KeyRing,
|
||||
)
|
||||
|
||||
kms_client.project_ids = [GCP_PROJECT_ID]
|
||||
kms_client.region = GCP_US_CENTER1_LOCATION
|
||||
|
||||
keyring = KeyRing(
|
||||
name="projects/123/locations/us-central1/keyRings/keyring1",
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
keylocation = KeyLocation(
|
||||
name=GCP_US_CENTER1_LOCATION,
|
||||
project_id=GCP_PROJECT_ID,
|
||||
)
|
||||
|
||||
kms_client.crypto_keys = [
|
||||
CriptoKey(
|
||||
name="key1",
|
||||
id="projects/123/locations/us-central1/keyRings/keyring1/cryptoKeys/key1",
|
||||
project_id=GCP_PROJECT_ID,
|
||||
rotation_period="7776000s",
|
||||
next_rotation_time="2024-09-01T00:00:00Z",
|
||||
key_ring=keyring.name,
|
||||
location=keylocation.name,
|
||||
members=["user:jane@example.com"],
|
||||
@@ -190,9 +543,9 @@ class Test_kms_key_rotation_enabled:
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Key {kms_client.crypto_keys[0].name} is rotated every 90 days or less."
|
||||
== f"Key {kms_client.crypto_keys[0].name} is rotated every 90 days or less and the next rotation time is in less than 90 days."
|
||||
)
|
||||
assert result[0].resource_id == kms_client.crypto_keys[0].name
|
||||
assert result[0].resource_id == kms_client.crypto_keys[0].id
|
||||
assert result[0].resource_name == kms_client.crypto_keys[0].name
|
||||
assert result[0].location == kms_client.crypto_keys[0].location
|
||||
assert result[0].project_id == kms_client.crypto_keys[0].project_id
|
||||
|
||||
@@ -67,17 +67,17 @@ class TestKubernetesMutelist:
|
||||
|
||||
assert mutelist.is_finding_muted(finding, "cluster_1")
|
||||
|
||||
def test_is_finding_muted_etcd_star_within_check_name(self):
|
||||
def test_is_finding_muted_apiserver_star_within_check_name_with_exception(self):
|
||||
# Mutelist
|
||||
mutelist_content = {
|
||||
"Accounts": {
|
||||
"*": {
|
||||
"Checks": {
|
||||
"etcd_*": {
|
||||
"apiserver_*": {
|
||||
"Regions": ["*"],
|
||||
"Resources": ["*"],
|
||||
"Exceptions": {
|
||||
"Accounts": ["k8s-cluster-2"],
|
||||
"Accounts": ["cluster_1"],
|
||||
"Regions": ["namespace1", "namespace2"],
|
||||
},
|
||||
}
|
||||
@@ -97,3 +97,34 @@ class TestKubernetesMutelist:
|
||||
finding.resource_tags = []
|
||||
|
||||
assert not mutelist.is_finding_muted(finding, "cluster_1")
|
||||
|
||||
def test_is_finding_muted_apiserver_star_within_check_name(self):
|
||||
# Mutelist
|
||||
mutelist_content = {
|
||||
"Accounts": {
|
||||
"*": {
|
||||
"Checks": {
|
||||
"apiserver_*": {
|
||||
"Regions": ["*"],
|
||||
"Resources": ["*"],
|
||||
"Exceptions": {
|
||||
"Accounts": ["k8s-cluster-1"],
|
||||
"Regions": ["namespace1", "namespace2"],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutelist = KubernetesMutelist(mutelist_content=mutelist_content)
|
||||
|
||||
finding = MagicMock
|
||||
finding.check_metadata = MagicMock
|
||||
finding.check_metadata.CheckID = "apiserver_etcd_cafile_set"
|
||||
finding.status = "FAIL"
|
||||
finding.resource_name = "test_resource"
|
||||
finding.namespace = "namespace1"
|
||||
finding.resource_tags = []
|
||||
|
||||
assert mutelist.is_finding_muted(finding, "cluster_1")
|
||||
|
||||
Reference in New Issue
Block a user