Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import random
from typing import List

import pytest
from pytest import mark, param

import slp_tfplan.tests.resources.test_resource_paths as resources
from otm.otm.entity.otm import OTM
from sl_util.sl_util.file_utils import get_byte_data
from slp_base import IacFileNotValidError
from slp_base import IacFileNotValidError, MappingFileNotValidError
from slp_base.slp_base.errors import ErrorCode
from slp_base.slp_base.mapping import MAX_SIZE as MAPPING_MAX_SIZE, MIN_SIZE as MAPPING_MIN_SIZE
from slp_base.tests.util.otm import validate_and_compare
from slp_tfplan import TFPlanProcessor
from slp_tfplan.tests.util.builders import create_artificial_file, MIN_FILE_SIZE, MAX_TFPLAN_FILE_SIZE, \
MAX_TFGRAPH_FILE_SIZE

DEFAULT_MAPPING_FILE = get_byte_data(resources.terraform_iriusrisk_tfplan_aws_mapping)
SECONDARY_DEFAULT_MAPPING_FILE = get_byte_data(resources.terraform_plan_default_mapping)
CONFIG_CLIENT_MAPPING_FILE = get_byte_data(resources.terraform_plan_config_client_mapping)
CONFIG_TRUSTZONE_MAPPING_FILE = get_byte_data(resources.terraform_plan_config_trustzone_mapping)
CONFIG_OVERRIDE_DEFAULT = get_byte_data(resources.terraform_plan_config_override_default)
CONFIG_OVERRIDE_CUSTOM = get_byte_data(resources.terraform_plan_config_override_custom)

SAMPLE_VALID_TFPLAN = get_byte_data(resources.tfplan_elb)
SAMPLE_VALID_TFGRAPH = get_byte_data(resources.tfgraph_elb)
Expand All @@ -24,6 +30,12 @@
TFPLAN_OFFICIAL = get_byte_data(resources.tfplan_official)
TFGRAPH_OFFICIAL = get_byte_data(resources.tfgraph_official)

TFPLAN_AWS_COMPLETE = get_byte_data(resources.tfplan_aws_complete)
TFGRAPH_AWS_COMPLETE = get_byte_data(resources.tfgraph_aws_complete)

TFPLAN_BASE = get_byte_data(resources.tfplan_base)
TFGRAPH_BASE = get_byte_data(resources.tfgraph_base)

SAMPLE_ID = 'id'
SAMPLE_NAME = 'name'
EXCLUDED_REGEX = r"root\[\'dataflows'\]\[.+?\]\['id'\]"
Expand Down Expand Up @@ -57,7 +69,7 @@ def test_tfplan_tfgraph_examples(tfplan: bytes, tfgraph: bytes, expected: str):
param([SAMPLE_VALID_TFPLAN], id='one source'),
param([SAMPLE_VALID_TFPLAN] * random.randint(3, 10), id='more than two sources')
])
def test_wrong_number_of_parameters(sources: List[bytes]):
def test_wrong_number_of_parameters(sources: list[bytes]):
# GIVEN a wrong number of sources

# WHEN TFPlanProcessor::process is invoked
Expand All @@ -75,7 +87,7 @@ def test_wrong_number_of_parameters(sources: List[bytes]):
param([SAMPLE_VALID_TFPLAN, create_artificial_file(MIN_FILE_SIZE - 1)], id='tfgraph too small'),
param([SAMPLE_VALID_TFPLAN, create_artificial_file(MAX_TFGRAPH_FILE_SIZE + 1)], id='tfgraph too big')
])
def test_invalid_size(sources: List[bytes]):
def test_invalid_size(sources: list[bytes]):
# GIVEN a tfplan or tfgraph with an invalid size

# WHEN TFPlanProcessor::process is invoked
Expand All @@ -87,6 +99,30 @@ def test_invalid_size(sources: List[bytes]):
assert error.value.title == 'Terraform Plan file is not valid'
assert error.value.message == 'Provided iac_file is not valid. Invalid size'

@mark.parametrize('mappings', [
param([create_artificial_file(MAPPING_MIN_SIZE - 1), DEFAULT_MAPPING_FILE], id='mapping file too small'),
param([create_artificial_file(MAPPING_MAX_SIZE + 1), DEFAULT_MAPPING_FILE], id='mapping file too big'),
param([DEFAULT_MAPPING_FILE, create_artificial_file(MAPPING_MIN_SIZE - 1)], id='custom mapping file too small'),
param([DEFAULT_MAPPING_FILE, create_artificial_file(MAPPING_MAX_SIZE + 1)], id='custom mapping file too big')
])
def test_invalid_mapping_size(mappings: list[bytes]):
# GIVEN a valid tfplan and tfgraph
tfplan = get_byte_data(resources.tfplan_official)
tfgraph = get_byte_data(resources.tfgraph_official)

# AND a mapping file with an invalid size ('mappings' arg)

# WHEN TFPlanProcessor::process is invoked
# THEN a MappingFileNotValidError is raised
with pytest.raises(MappingFileNotValidError) as error:
TFPlanProcessor(SAMPLE_ID, SAMPLE_NAME, [tfplan, tfgraph], mappings).process()

# AND the error details are correct
assert ErrorCode.MAPPING_FILE_NOT_VALID == error.value.error_code
assert 'Mapping files are not valid' == error.value.title
assert 'Mapping files are not valid. Invalid size' == error.value.detail
assert 'Mapping files are not valid. Invalid size' == error.value.message

def test_two_tfplan():
# GIVEN two valid TFPLANs
sources = [SAMPLE_VALID_TFPLAN, SAMPLE_VALID_TFPLAN]
Expand All @@ -105,7 +141,7 @@ def test_two_tfplan():
param([SAMPLE_VALID_TFPLAN, SAMPLE_INVALID_TFGRAPH], id='invalid tfgraph'),
param([SAMPLE_INVALID_TFPLAN, SAMPLE_INVALID_TFGRAPH], id='both invalid')
])
def test_invalid_sources(sources: List[bytes]):
def test_invalid_sources(sources: list[bytes]):
# GIVEN some invalid tfplan

# WHEN TFPlanProcessor::process is invoked
Expand Down Expand Up @@ -150,3 +186,65 @@ def test_singleton_grouped_by_category():
assert components[1].id == 'aws_cloudwatch_log_group.click_logger_firehose_delivery_stream_log_group'
assert components[1].name == 'CloudWatch'
assert components[1].type == 'cloudwatch'

def test_aws_complete_sample():
# GIVEN a valid tfplan and tfgraph
tfplan = TFPLAN_AWS_COMPLETE
tfgraph = TFGRAPH_AWS_COMPLETE

# AND a mapping file with an invalid size ('mappings' arg)
mapping_file = SECONDARY_DEFAULT_MAPPING_FILE

# WHEN TFPlanProcessor::process is invoked
otm = TFPlanProcessor(SAMPLE_ID, SAMPLE_NAME, [tfplan, tfgraph], [mapping_file]).process()

# AND the details are correct
assert len(otm.representations) == 1
assert len(otm.trustzones) == 2
assert len(otm.components) == 15
assert len(otm.dataflows) == 8

def test_configuration_trustzone_no_client():
# GIVEN two valid TFPLANs
tfplan = TFPLAN_BASE
tfgraph = TFGRAPH_BASE

# WHEN TFPlanProcessor::process is invoked
# THEN a MappingFileNotValidError exception is raised
with pytest.raises(MappingFileNotValidError) as error:
TFPlanProcessor(SAMPLE_ID, SAMPLE_NAME, [tfplan, tfgraph], [CONFIG_TRUSTZONE_MAPPING_FILE]).process()

# AND the message says that no multiple tfplan files can be processed at the same time
assert str(error.value.title) == 'Mapping files are not valid'
assert str(error.value.detail) == 'Mapping file does not comply with the schema'
assert str(error.value.message) == "'client' is a required property"

def test_configuration_client_no_trustzone():
# GIVEN two valid TFPLANs
tfplan = TFPLAN_BASE
tfgraph = TFGRAPH_BASE

# WHEN TFPlanProcessor::process is invoked
# THEN a MappingFileNotValidError exception is raised
with pytest.raises(MappingFileNotValidError) as error:
TFPlanProcessor(SAMPLE_ID, SAMPLE_NAME, [tfplan, tfgraph], [CONFIG_CLIENT_MAPPING_FILE]).process()

# AND the message says that no multiple tfplan files can be processed at the same time
assert str(error.value.title) == 'Mapping files are not valid'
assert str(error.value.detail) == 'Mapping file does not comply with the schema'
assert str(error.value.message) == "'trustzone' is a required property"

def test_configuration_mapping_override():
# GIVEN two valid TFPLANs
tfplan = TFPLAN_BASE
tfgraph = TFGRAPH_BASE

# WHEN TFPlanProcessor::process is invoked
otm = TFPlanProcessor(SAMPLE_ID, SAMPLE_NAME, [tfplan, tfgraph],
[CONFIG_OVERRIDE_DEFAULT, CONFIG_OVERRIDE_CUSTOM]).process()

# AND the details are correct
assert len(otm.representations) == 1
assert len(otm.trustzones) == 2
assert len(otm.components) == 15
assert len(otm.dataflows) == 13
189 changes: 189 additions & 0 deletions slp_tfplan/tests/resources/mapping/default-terraform-plan-mapping.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
trustzones:
- type: b61d6911-338d-46a8-9f39-8dcd24abfe91
name: Public Cloud
risk:
trust_rating: 10
$default: true

- type: f0ba7722-39b6-4c81-8290-a30a248bb8d9
name: Internet
risk:
trust_rating: 1

components:

- label: aws_acm_certificate
type: CD-ACM
$singleton: true

- label: aws_cloudwatch_metric_alarm
type: cloudwatch
$singleton: true

- label: aws_dynamodb_table
type: dynamodb

- label: aws_vpc
type: vpc

- label: aws_instance
type: ec2

- label: aws_subnet
type: empty-component

- label: aws_vpc_endpoint
type: empty-component

- label: aws_internet_gateway
type: empty-component

- label: aws_ecs_service
type: elastic-container-service

- label: aws_ecs_task_definition
type: docker-container

- label: ["aws_lb", "aws_elb", "aws_alb"]
type: load-balancer

- label: aws_kms_key
type: kms
$singleton: true

- label: aws_lambda_function
type: aws-lambda-function

- label: aws_cloudwatch_log_group
type: cloudwatch
$singleton: true

- label: ["aws_db_instance", "aws_rds_cluster"]
type: rds

- label: aws_route53_zone
type: route-53

- label: aws_autoscaling_group
type: CD-EC2-AUTO-SCALING

- label: cloudflare_record
type: empty-component

- label: aws_s3_bucket
type: s3

- label: aws_secretsmanager_secret
type: CD-SECRETS-MANAGER
$singleton: true

- label: aws_sqs_queue
type: sqs-simple-queue-service

- label: [ "azurerm_data_share", "azurerm_data_share_account" ]
type: CD-MICROSOFT-AZURE-DATA-SHARE

- label: azurerm_elastic_cloud_elasticsearch
type: CD-MICROSOFT-AZURE-ELASTICSEARCH

- label: ["azurerm_media_services_account", "azurerm_media_services_account_filter"]
type: CD-MICROSOFT-AZURE-MEDIA-SERVICES

- label: {$regex: ^aws_ssm_\w*$}
type: CD-SYSTEMS-MANAGER
$singleton: true

- label: aws_synthetics_canary
type: empty-component

- label: {$regex: ^aws_api_gateway_\w*$}
type: api-gateway
$singleton: true

- label: {$regex: ^aws_athena_\w*$}
type: athena
$singleton: true

- label: {$regex: ^aws_mq_\w*$}
type: CD-MQ
$singleton: true

- label: {$regex: ^aws_cloudfront_\w*$}
type: cf-cloudfront
$singleton: true

- label: aws_cloudtrail
type: cloudtrail

- label: ["aws_cognito_user_pool", "aws_cognito_identity_pool"]
type: cognito

- label: {$regex: ^aws_config_\w*$}
type: CD-CONFIG
$singleton: true

- label: {$regex: ^aws_ecr_\w*$}
type: elastic-container-registry
$singleton: true

- label: aws_eks_cluster
type: elastic-container-kubernetes

- label: {$regex: ^aws_elasticache_\w*$}
type: elasticache
$singleton: true

- label: {$regex: ^aws_guardduty_\w*$}
type: CD-GUARDDUTY
$singleton: true

- label: {$regex: ^aws_inspector_\w*$}
type: CD-INSPECTOR
$singleton: true

- label: {$regex: ^aws_macie2_\w*$}
type: CD-MACIE
$singleton: true

- label: aws_networkfirewall_firewall
type: CD-AWS-NETWORK-FIREWALL

- label: aws_redshift_cluster
type: redshift

- label: {$regex: ^aws_ses_\w*$}
type: CD-SES
$singleton: true

- label: {$regex: ^aws_sns_\w*$}
type: sns
$singleton: true

- label: {$regex: ^aws_sfn_\w*$}
type: step-functions

- label: {$regex: ^aws_waf_\w*$}
type: CD-WAF
$singleton: true

- label: {$regex: ^aws_kinesis_analytics_\w*$}
type: kinesis-data-analytics
$singleton: true

- label: {$regex: ^aws_kinesis_stream\w*$}
type: kinesis-data-analytics
$singleton: true

- label: {$regex: ^aws_kinesis_firehose_\w*$}
type: kinesis-data-firehose
$singleton: true

configuration:
attack_surface:
client: generic-client
trustzone: f0ba7722-39b6-4c81-8290-a30a248bb8d9

# skip:
# - aws_security_group
# - aws_db_subnet_group
# catch_all: empty-component
Loading