mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 12:31:54 +00:00
479 lines
16 KiB
Python
479 lines
16 KiB
Python
from os import path, remove
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
import boto3
|
|
import botocore
|
|
import pytest
|
|
from moto import mock_aws
|
|
|
|
from prowler.lib.outputs.compliance.iso27001.iso27001_aws import AWSISO27001
|
|
from prowler.lib.outputs.csv.csv import CSV
|
|
from prowler.lib.outputs.html.html import HTML
|
|
from prowler.lib.outputs.ocsf.ocsf import OCSF
|
|
from prowler.providers.aws.lib.s3.exceptions.exceptions import (
|
|
S3InvalidBucketNameError,
|
|
S3InvalidBucketRegionError,
|
|
)
|
|
from prowler.providers.aws.lib.s3.s3 import S3
|
|
from prowler.providers.common.models import Connection
|
|
from tests.lib.outputs.compliance.fixtures import ISO27001_2013_AWS
|
|
from tests.lib.outputs.fixtures.fixtures import generate_finding_output
|
|
from tests.providers.aws.utils import AWS_REGION_US_EAST_1
|
|
|
|
CURRENT_DIRECTORY = str(Path(path.dirname(path.realpath(__file__))))
|
|
S3_BUCKET_NAME = "test_bucket"
|
|
S3_BUCKET_ARN = f"arn:aws:s3:::{S3_BUCKET_NAME}"
|
|
OUTPUT_MODE_CSV = "csv"
|
|
OUTPUT_MODE_JSON_OCSF = "json-ocsf"
|
|
OUTPUT_MODE_JSON_ASFF = "json-asff"
|
|
OUTPUT_MODE_HTML = "html"
|
|
OUTPUT_MODE_CIS_1_4_AWS = "cis_1.4_aws"
|
|
FINDING = generate_finding_output(
|
|
status="PASS",
|
|
status_extended="status-extended",
|
|
resource_uid="resource-123",
|
|
resource_name="Example Resource",
|
|
resource_details="Detailed information about the resource",
|
|
resource_tags={"key1": "tag1", "key2": "tag2"},
|
|
partition="aws",
|
|
description="Description of the finding",
|
|
risk="High",
|
|
related_url="http://example.com",
|
|
remediation_recommendation_text="Recommendation text",
|
|
remediation_recommendation_url="http://example.com/remediation",
|
|
remediation_code_nativeiac="native-iac-code",
|
|
remediation_code_terraform="terraform-code",
|
|
remediation_code_other="other-code",
|
|
remediation_code_cli="cli-code",
|
|
compliance={"compliance_key": "compliance_value"},
|
|
categories=["categorya", "categoryb"],
|
|
depends_on=["dependency"],
|
|
related_to=["related"],
|
|
notes="Notes about the finding",
|
|
)
|
|
make_api_call = botocore.client.BaseClient._make_api_call
|
|
|
|
|
|
class TestS3:
|
|
@mock_aws
|
|
def test_send_no_outputs(self):
|
|
current_session = boto3.session.Session(region_name=AWS_REGION_US_EAST_1)
|
|
s3 = S3(
|
|
session=current_session,
|
|
bucket_name=S3_BUCKET_NAME,
|
|
output_directory=CURRENT_DIRECTORY,
|
|
)
|
|
assert s3.send_to_bucket({}) == {"success": {}, "failure": {}}
|
|
|
|
@mock_aws
|
|
def test_send_to_s3_bucket_csv(self):
|
|
# Create bucket
|
|
current_session = boto3.session.Session(region_name=AWS_REGION_US_EAST_1)
|
|
client = current_session.client("s3")
|
|
client.create_bucket(Bucket=S3_BUCKET_NAME)
|
|
|
|
s3 = S3(
|
|
session=current_session,
|
|
bucket_name=S3_BUCKET_NAME,
|
|
output_directory=CURRENT_DIRECTORY,
|
|
)
|
|
|
|
extension = ".csv"
|
|
csv = CSV(
|
|
findings=[FINDING],
|
|
file_extension=extension,
|
|
)
|
|
|
|
s3_send_result = s3.send_to_bucket(outputs={"regular": [csv]})
|
|
|
|
assert "failure" in s3_send_result
|
|
assert s3_send_result["failure"] == {}
|
|
|
|
assert "success" in s3_send_result
|
|
assert extension in s3_send_result["success"]
|
|
assert len(s3_send_result["success"][extension]) == 1
|
|
|
|
uploaded_object_name = s3_send_result["success"][extension][0]
|
|
|
|
assert (
|
|
client.get_object(
|
|
Bucket=S3_BUCKET_NAME,
|
|
Key=uploaded_object_name,
|
|
)["ContentType"]
|
|
== "text/csv"
|
|
)
|
|
|
|
@mock_aws
|
|
def test_send_to_s3_bucket_csv_with_file_descriptor(self):
|
|
# Create bucket
|
|
current_session = boto3.session.Session(region_name=AWS_REGION_US_EAST_1)
|
|
client = current_session.client("s3")
|
|
client.create_bucket(Bucket=S3_BUCKET_NAME)
|
|
|
|
s3 = S3(
|
|
session=current_session,
|
|
bucket_name=S3_BUCKET_NAME,
|
|
output_directory=CURRENT_DIRECTORY,
|
|
)
|
|
|
|
extension = ".csv"
|
|
csv_file = f"test{extension}"
|
|
csv = CSV(
|
|
findings=[FINDING],
|
|
file_path=f"{CURRENT_DIRECTORY}/{csv_file}",
|
|
)
|
|
|
|
s3_send_result = s3.send_to_bucket(outputs={"regular": [csv]})
|
|
|
|
assert "failure" in s3_send_result
|
|
assert s3_send_result["failure"] == {}
|
|
|
|
assert "success" in s3_send_result
|
|
assert extension in s3_send_result["success"]
|
|
assert len(s3_send_result["success"][extension]) == 1
|
|
|
|
uploaded_object_name = s3_send_result["success"][extension][0]
|
|
|
|
assert (
|
|
client.get_object(
|
|
Bucket=S3_BUCKET_NAME,
|
|
Key=uploaded_object_name,
|
|
)["ContentType"]
|
|
== "text/csv"
|
|
)
|
|
|
|
remove(f"{CURRENT_DIRECTORY}/{csv_file}")
|
|
|
|
@mock_aws
|
|
def test_send_to_s3_bucket_ocsf(self):
|
|
# Create bucket
|
|
current_session = boto3.session.Session(region_name=AWS_REGION_US_EAST_1)
|
|
client = current_session.client("s3")
|
|
client.create_bucket(Bucket=S3_BUCKET_NAME)
|
|
|
|
s3 = S3(
|
|
session=current_session,
|
|
bucket_name=S3_BUCKET_NAME,
|
|
output_directory=CURRENT_DIRECTORY,
|
|
)
|
|
extension = ".ocsf.json"
|
|
csv = OCSF(
|
|
findings=[FINDING],
|
|
file_extension=extension,
|
|
)
|
|
|
|
s3_send_result = s3.send_to_bucket(outputs={"regular": [csv]})
|
|
|
|
assert "failure" in s3_send_result
|
|
assert s3_send_result["failure"] == {}
|
|
|
|
assert "success" in s3_send_result
|
|
assert extension in s3_send_result["success"]
|
|
assert len(s3_send_result["success"][extension]) == 1
|
|
|
|
uploaded_object_name = s3_send_result["success"][extension][0]
|
|
|
|
assert (
|
|
client.get_object(
|
|
Bucket=S3_BUCKET_NAME,
|
|
Key=uploaded_object_name,
|
|
)["ContentType"]
|
|
== "application/json"
|
|
)
|
|
|
|
@mock_aws
|
|
def test_send_to_s3_bucket_html(self):
|
|
# Create bucket
|
|
current_session = boto3.session.Session(region_name=AWS_REGION_US_EAST_1)
|
|
client = current_session.client("s3")
|
|
client.create_bucket(Bucket=S3_BUCKET_NAME)
|
|
|
|
s3 = S3(
|
|
session=current_session,
|
|
bucket_name=S3_BUCKET_NAME,
|
|
output_directory=CURRENT_DIRECTORY,
|
|
)
|
|
|
|
extension = ".html"
|
|
csv = HTML(
|
|
findings=[FINDING],
|
|
file_extension=extension,
|
|
)
|
|
|
|
s3_send_result = s3.send_to_bucket(outputs={"regular": [csv]})
|
|
|
|
assert "failure" in s3_send_result
|
|
assert s3_send_result["failure"] == {}
|
|
|
|
assert "success" in s3_send_result
|
|
assert extension in s3_send_result["success"]
|
|
assert len(s3_send_result["success"][extension]) == 1
|
|
|
|
uploaded_object_name = s3_send_result["success"][extension][0]
|
|
|
|
assert (
|
|
client.get_object(
|
|
Bucket=S3_BUCKET_NAME,
|
|
Key=uploaded_object_name,
|
|
)["ContentType"]
|
|
== "text/html"
|
|
)
|
|
|
|
@mock_aws
|
|
def test_send_to_s3_non_existent_bucket(self):
|
|
# Create bucket
|
|
current_session = boto3.session.Session(region_name=AWS_REGION_US_EAST_1)
|
|
|
|
s3 = S3(
|
|
session=current_session,
|
|
bucket_name=S3_BUCKET_NAME,
|
|
output_directory=CURRENT_DIRECTORY,
|
|
)
|
|
|
|
extension = ".csv"
|
|
csv = CSV(
|
|
findings=[FINDING],
|
|
file_extension=extension,
|
|
)
|
|
|
|
s3_send_result = s3.send_to_bucket(outputs={"regular": [csv]})
|
|
|
|
assert "success" in s3_send_result
|
|
assert s3_send_result["success"] == {}
|
|
|
|
assert "failure" in s3_send_result
|
|
assert extension in s3_send_result["failure"]
|
|
assert len(s3_send_result["failure"][extension])
|
|
|
|
assert isinstance(s3_send_result["failure"][extension], list)
|
|
assert len(s3_send_result["failure"][extension]) == 1
|
|
|
|
assert isinstance(s3_send_result["failure"][extension][0], tuple)
|
|
|
|
# Object name
|
|
assert isinstance(s3_send_result["failure"][extension][0][0], str)
|
|
assert (
|
|
f"tests/providers/aws/lib/s3/csv/{path.basename(csv.file_descriptor.name)}"
|
|
in s3_send_result["failure"][extension][0][0]
|
|
)
|
|
# Error
|
|
assert isinstance(s3_send_result["failure"][extension][0][1], Exception)
|
|
assert (
|
|
"An error occurred (NoSuchBucket) when calling the PutObject operation: The specified bucket does not exist"
|
|
in str(s3_send_result["failure"][extension][0][1])
|
|
)
|
|
|
|
@mock_aws
|
|
def test_send_to_s3_bucket_compliance_iso_27001(self):
|
|
# Create bucket
|
|
current_session = boto3.session.Session(region_name=AWS_REGION_US_EAST_1)
|
|
client = current_session.client("s3")
|
|
client.create_bucket(Bucket=S3_BUCKET_NAME)
|
|
|
|
s3 = S3(
|
|
session=current_session,
|
|
bucket_name=S3_BUCKET_NAME,
|
|
output_directory=CURRENT_DIRECTORY,
|
|
)
|
|
|
|
extension = ".csv"
|
|
compliance = AWSISO27001(
|
|
findings=[FINDING], compliance=ISO27001_2013_AWS, file_extension=extension
|
|
)
|
|
|
|
s3_send_result = s3.send_to_bucket(outputs={"compliance": [compliance]})
|
|
|
|
assert "failure" in s3_send_result
|
|
assert s3_send_result["failure"] == {}
|
|
|
|
assert "success" in s3_send_result
|
|
assert extension in s3_send_result["success"]
|
|
assert len(s3_send_result["success"][extension]) == 1
|
|
|
|
uploaded_object_name = s3_send_result["success"][extension][0]
|
|
|
|
assert (
|
|
client.get_object(
|
|
Bucket=S3_BUCKET_NAME,
|
|
Key=uploaded_object_name,
|
|
)["ContentType"]
|
|
== "text/csv"
|
|
)
|
|
|
|
def test_get_get_object_path_with_prowler(self):
|
|
output_directory = "/Users/admin/prowler/"
|
|
assert (
|
|
S3.get_object_path(output_directory)
|
|
== output_directory.partition("prowler/")[-1]
|
|
)
|
|
|
|
def test_get_get_object_path_without_prowler(self):
|
|
output_directory = "/Users/admin/"
|
|
assert S3.get_object_path(output_directory) == output_directory
|
|
|
|
def test_generate_subfolder_name_by_extension_csv(self):
|
|
assert S3.generate_subfolder_name_by_extension(".csv") == "csv"
|
|
|
|
def test_generate_subfolder_name_by_extension_html(self):
|
|
assert S3.generate_subfolder_name_by_extension(".html") == "html"
|
|
|
|
def test_generate_subfolder_name_by_extension_json_asff(self):
|
|
assert S3.generate_subfolder_name_by_extension(".asff.json") == "json-asff"
|
|
|
|
def test_generate_subfolder_name_by_extension_json_ocsf(self):
|
|
assert S3.generate_subfolder_name_by_extension(".ocsf.json") == "json-ocsf"
|
|
|
|
@mock_aws
|
|
def test_test_connection_S3(self):
|
|
# Create a mock IAM user
|
|
iam_client = boto3.client("iam", region_name=AWS_REGION_US_EAST_1)
|
|
iam_user = iam_client.create_user(UserName="test-user")["User"]
|
|
# Create a mock IAM access keys
|
|
access_key = iam_client.create_access_key(UserName=iam_user["UserName"])[
|
|
"AccessKey"
|
|
]
|
|
|
|
# Create bucket
|
|
current_session = boto3.session.Session(
|
|
aws_access_key_id=access_key["AccessKeyId"],
|
|
aws_secret_access_key=access_key["SecretAccessKey"],
|
|
region_name=AWS_REGION_US_EAST_1,
|
|
)
|
|
s3_client = current_session.client("s3")
|
|
s3_client.create_bucket(Bucket=S3_BUCKET_NAME)
|
|
|
|
connection = S3.test_connection(
|
|
aws_region=AWS_REGION_US_EAST_1,
|
|
bucket_name=S3_BUCKET_NAME,
|
|
aws_access_key_id=access_key["AccessKeyId"],
|
|
aws_secret_access_key=access_key["SecretAccessKey"],
|
|
)
|
|
assert isinstance(connection, Connection)
|
|
assert connection.is_connected is True
|
|
assert connection.error is None
|
|
|
|
@mock_aws
|
|
def test_test_connection_S3_bucket_invalid_name(self):
|
|
# Create a mock IAM user
|
|
iam_client = boto3.client("iam", region_name=AWS_REGION_US_EAST_1)
|
|
iam_user = iam_client.create_user(UserName="test-user")["User"]
|
|
# Create a mock IAM access keys
|
|
access_key = iam_client.create_access_key(UserName=iam_user["UserName"])[
|
|
"AccessKey"
|
|
]
|
|
|
|
# Create bucket (with valid name)
|
|
current_session = boto3.session.Session(
|
|
aws_access_key_id=access_key["AccessKeyId"],
|
|
aws_secret_access_key=access_key["SecretAccessKey"],
|
|
region_name=AWS_REGION_US_EAST_1,
|
|
)
|
|
s3_client = current_session.client("s3")
|
|
s3_client.create_bucket(Bucket=S3_BUCKET_NAME)
|
|
|
|
with pytest.raises(S3InvalidBucketNameError):
|
|
S3.test_connection(
|
|
aws_region=AWS_REGION_US_EAST_1,
|
|
bucket_name="invalid_bucket",
|
|
aws_access_key_id=access_key["AccessKeyId"],
|
|
aws_secret_access_key=access_key["SecretAccessKey"],
|
|
)
|
|
|
|
def mock_make_api_head_bucket(self, operation_name, kwarg):
|
|
if operation_name == "HeadBucket":
|
|
if kwarg["Bucket"] == "bucket_without_region":
|
|
return {"BucketArn": S3_BUCKET_ARN}
|
|
else:
|
|
return {
|
|
"BucketArn": S3_BUCKET_ARN,
|
|
"BucketRegion": AWS_REGION_US_EAST_1,
|
|
}
|
|
|
|
@mock_aws
|
|
def test_test_connection_S3_bucket_invalid_region_raise_on_exception(self):
|
|
with mock.patch(
|
|
"botocore.client.BaseClient._make_api_call",
|
|
new=self.mock_make_api_head_bucket,
|
|
):
|
|
|
|
with pytest.raises(S3InvalidBucketRegionError):
|
|
S3.test_connection(
|
|
aws_region=AWS_REGION_US_EAST_1,
|
|
# Bucket without region to force exception
|
|
bucket_name="bucket_without_region",
|
|
# aws_access_key_id=access_key["AccessKeyId"],
|
|
# aws_secret_access_key=access_key["SecretAccessKey"],
|
|
raise_on_exception=True,
|
|
)
|
|
|
|
def test_test_connection_S3_bucket_invalid_region_no_raise_on_exception(self):
|
|
with mock.patch(
|
|
"botocore.client.BaseClient._make_api_call",
|
|
new=self.mock_make_api_head_bucket,
|
|
):
|
|
connection = S3.test_connection(
|
|
aws_region=AWS_REGION_US_EAST_1,
|
|
bucket_name="bucket_without_region",
|
|
raise_on_exception=False,
|
|
)
|
|
assert connection.is_connected is False
|
|
assert (
|
|
str(connection.error)
|
|
== "S3InvalidBucketRegionError[6005]: The specified bucket region is invalid."
|
|
)
|
|
|
|
@mock_aws
|
|
def test_init_without_session(self):
|
|
with pytest.raises(ValueError) as e:
|
|
S3(
|
|
session=None,
|
|
bucket_name=S3_BUCKET_NAME,
|
|
output_directory=CURRENT_DIRECTORY,
|
|
)
|
|
|
|
assert (
|
|
str(e.value)
|
|
== "If no role ARN is provided, a profile, an AWS access key ID, or an AWS secret access key is required."
|
|
)
|
|
|
|
@mock_aws
|
|
def test_init_without_session_but_role_arn(self):
|
|
with pytest.raises(ValueError) as e:
|
|
S3(
|
|
session=None,
|
|
bucket_name=S3_BUCKET_NAME,
|
|
output_directory=CURRENT_DIRECTORY,
|
|
role_arn="arn:aws:iam::123456789012:role/role_name",
|
|
)
|
|
|
|
assert (
|
|
str(e.value)
|
|
== "If a role ARN is provided, a session duration, an external ID, and a role session name are required."
|
|
)
|
|
|
|
@mock_aws
|
|
def test_init_without_session_and_role_arn_but_session_duration(self):
|
|
with pytest.raises(ValueError) as e:
|
|
S3(
|
|
session=None,
|
|
bucket_name=S3_BUCKET_NAME,
|
|
output_directory=CURRENT_DIRECTORY,
|
|
session_duration=3600,
|
|
)
|
|
assert (
|
|
str(e.value)
|
|
== "If no role ARN is provided, a profile, an AWS access key ID, or an AWS secret access key is required."
|
|
)
|
|
|
|
@mock_aws
|
|
def test_init_without_session_and_role_arn_but_profile(self):
|
|
with pytest.raises(ValueError) as e:
|
|
S3(
|
|
session=None,
|
|
bucket_name=S3_BUCKET_NAME,
|
|
output_directory=CURRENT_DIRECTORY,
|
|
external_id="1234567890",
|
|
)
|
|
assert str(e.value) == "If an external ID is provided, a role ARN is required."
|