diff --git a/checkov/common/runners/runner_registry.py b/checkov/common/runners/runner_registry.py index e2823b2f8f6..57018108f3a 100644 --- a/checkov/common/runners/runner_registry.py +++ b/checkov/common/runners/runner_registry.py @@ -215,6 +215,14 @@ def print_reports( cyclonedx_reports = [] csv_sbom_report = CSVSBOM() + try: + if config.skip_resources_without_violations: + for report in scan_reports: + report.extra_resources = set() + except AttributeError: + # config attribute wasn't set, defaults to False and print extra resources to report + pass + data_outputs: dict[str, str] = defaultdict(str) for report in scan_reports: if not report.is_empty(): diff --git a/checkov/main.py b/checkov/main.py index 2fec84e4302..e15c54eb7e6 100755 --- a/checkov/main.py +++ b/checkov/main.py @@ -585,6 +585,10 @@ def add_parser_args(parser: ArgumentParser) -> None: parser.add('--summary-position', default='top', choices=SUMMARY_POSITIONS, help='Chose whether the summary will be appended on top (before the checks results) or on bottom ' '(after check results), default is on top.') + parser.add('--skip-resources-without-violations', + help="exclude extra resources (resources without violations) from report output", + action='store_true', + env_var='CKV_SKIP_RESOURCES_WITHOUT_VIOLATIONS') def get_external_checks_dir(config: Any) -> Any: diff --git a/tests/common/runner_registry/example_bicep_with_empty_resources/playground.bicep b/tests/common/runner_registry/example_bicep_with_empty_resources/playground.bicep new file mode 100644 index 00000000000..cb05a36a92f --- /dev/null +++ b/tests/common/runner_registry/example_bicep_with_empty_resources/playground.bicep @@ -0,0 +1,204 @@ +@description('Virtual machine size (has to be at least the size of Standard_A3 to support 2 NICs)') +param virtualMachineSize string = 'Standard_DS1_v2' + +@description('Default Admin username') +param adminUsername string + +@description('Default Admin password') +@secure() +param adminPassword string + +@description('Storage Account type for the VM and VM diagnostic storage') +@allowed([ + 'Standard_LRS' + 'Premium_LRS' +]) +param storageAccountType string = 'Standard_LRS' + +@description('Location for all resources.') +param location string = resourceGroup().location + +var virtualMachineName = 'VM-MultiNic' +var nic1Name = 'nic-1' +var nic2Name = 'nic-2' +var virtualNetworkName = 'virtualNetwork' +var subnet1Name = 'subnet-1' +var subnet2Name = 'subnet-2' +var publicIPAddressName = 'publicIp' +var diagStorageAccountName = 'diags${uniqueString(resourceGroup().id)}' +var networkSecurityGroupName = 'NSG' +var networkSecurityGroupName2 = '${subnet2Name}-nsg' + +// This is the virtual machine that you're building. +resource vm 'Microsoft.Compute/virtualMachines@2020-06-01' = { + name: virtualMachineName + location: location + properties: { + osProfile: { + computerName: virtualMachineName + adminUsername: adminUsername + adminPassword: adminPassword + windowsConfiguration: { + provisionVMAgent: true + } + } + hardwareProfile: { + vmSize: virtualMachineSize + } + storageProfile: { + imageReference: { + publisher: 'MicrosoftWindowsServer' + offer: 'WindowsServer' + sku: '2019-Datacenter' + version: 'latest' + } + osDisk: { + createOption: 'FromImage' + } + } + networkProfile: { + networkInterfaces: [ + { + properties: { + primary: true + } + id: nic1.id + } + { + properties: { + primary: false + } + id: nic2.id + } + ] + } + diagnosticsProfile: { + bootDiagnostics: { + enabled: true + storageUri: diagsAccount.properties.primaryEndpoints.blob + } + } + } +} + +resource diagsAccount 'Microsoft.Storage/storageAccounts@2019-06-01' = { + // checkov:skip=CKV_AZURE_35: just skip it + name: diagStorageAccountName + location: location + sku: { + name: storageAccountType + } + kind: 'StorageV2' +} + +// Simple Network Security Group for subnet2 +resource nsg2 'Microsoft.Network/networkSecurityGroups@2020-06-01' = { + name: networkSecurityGroupName2 + location: location +} + +// This will build a Virtual Network. +resource vnet 'Microsoft.Network/virtualNetworks@2020-06-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + '10.0.0.0/16' + ] + } + subnets: [ + { + name: subnet1Name + properties: { + addressPrefix: '10.0.0.0/24' + } + } + { + name: subnet2Name + properties: { + addressPrefix: '10.0.1.0/24' + networkSecurityGroup: { + id: nsg2.id + } + } + } + ] + } +} + +// This will be your Primary NIC +resource nic1 'Microsoft.Network/networkInterfaces@2020-06-01' = { + name: nic1Name + location: location + properties: { + ipConfigurations: [ + { + name: 'ipconfig1' + properties: { + subnet: { + id: resourceId('Microsoft.Network/virtualNetworks/subnets', vnet.name, subnet1Name) + } + privateIPAllocationMethod: 'Dynamic' + publicIPAddress: { + id: pip.id + } + } + } + ] + networkSecurityGroup: { + id: nsg.id + } + } +} + +// This will be your Secondary NIC +resource nic2 'Microsoft.Network/networkInterfaces@2020-06-01' = { + name: nic2Name + location: location + properties: { + ipConfigurations: [ + { + name: 'ipconfig1' + properties: { + subnet: { + id: resourceId('Microsoft.Network/virtualNetworks/subnets', vnet.name, subnet2Name) + } + privateIPAllocationMethod: 'Dynamic' + } + } + ] + } +} + +// Public IP for your Primary NIC +resource pip 'Microsoft.Network/publicIPAddresses@2020-06-01' = { + name: publicIPAddressName + location: location + properties: { + publicIPAllocationMethod: 'Dynamic' + } +} + +// Network Security Group (NSG) for your Primary NIC +resource nsg 'Microsoft.Network/networkSecurityGroups@2020-06-01' = { + name: networkSecurityGroupName + location: location + properties: { + securityRules: [ + { + name: 'default-allow-rdp' + properties: { + priority: 1000 + sourceAddressPrefix: '*' + protocol: 'Tcp' + destinationPortRange: '3389' + access: 'Allow' + direction: 'Inbound' + sourcePortRange: '*' + destinationAddressPrefix: '*' + } + } + ] + } +} diff --git a/tests/common/runner_registry/test_runner_registry.py b/tests/common/runner_registry/test_runner_registry.py index 1c1171cbebf..f5adcd83741 100644 --- a/tests/common/runner_registry/test_runner_registry.py +++ b/tests/common/runner_registry/test_runner_registry.py @@ -15,6 +15,7 @@ from checkov.main import DEFAULT_RUNNERS from checkov.runner_filter import RunnerFilter from checkov.terraform.runner import Runner as tf_runner +from checkov.bicep.runner import Runner as bicep_runner import re @@ -296,5 +297,64 @@ def test_non_compact_json_output(capsys): assert 'code_block' in captured.out +def test_extra_resources_in_report(capsys): + # given + test_files_dir = os.path.dirname(os.path.realpath(__file__)) + "/example_bicep_with_empty_resources" + runner_filter = RunnerFilter(framework=None, checks=None, skip_checks=None) + runner_registry = RunnerRegistry( + banner, runner_filter, bicep_runner() + ) + reports = runner_registry.run(root_folder=test_files_dir) + + config = argparse.Namespace( + file=['./example_bicep_with_empty_resources/playground.bicep'], + compact=False, + output=['json'], + quiet=False, + soft_fail=False, + soft_fail_on=None, + hard_fail_on=None, + output_file_path=None, + use_enforcement_rules=None + ) + + # when + runner_registry.print_reports(scan_reports=reports, config=config) + + # then + for report in reports: + assert len(report.extra_resources) > 0 + + +def test_extra_resources_removed_from_report(capsys): + # given + test_files_dir = os.path.dirname(os.path.realpath(__file__)) + "/example_bicep_with_empty_resources" + runner_filter = RunnerFilter(framework=None, checks=None, skip_checks=None) + runner_registry = RunnerRegistry( + banner, runner_filter, bicep_runner() + ) + reports = runner_registry.run(root_folder=test_files_dir) + + config = argparse.Namespace( + file=['./example_bicep_with_empty_resources/playground.bicep'], + compact=False, + output=['json'], + quiet=False, + soft_fail=False, + soft_fail_on=None, + hard_fail_on=None, + output_file_path=None, + use_enforcement_rules=None, + skip_resources_without_violations=True + ) + + # when + runner_registry.print_reports(scan_reports=reports, config=config) + + # then + for report in reports: + assert len(report.extra_resources) == 0 + + if __name__ == "__main__": unittest.main()