From 3ef79588b4844f39f9dbce6cf8da4d5d5bcb070f Mon Sep 17 00:00:00 2001 From: eeche Date: Wed, 9 Apr 2025 22:13:24 +0900 Subject: [PATCH] feat(NHN): add NHN cloud provider with 6 checks (#6870) Co-authored-by: MrCloudSec --- README.md | 1 + prowler/__main__.py | 35 + prowler/compliance/nhn/__init__.py | 0 prowler/compliance/nhn/iso27001_2022_nhn.json | 1313 +++++++++++++++++ prowler/config/config.py | 1 + prowler/lib/check/models.py | 23 + prowler/lib/cli/parser.py | 5 +- .../compliance/iso27001/iso27001_nhn.py | 87 ++ .../lib/outputs/compliance/iso27001/models.py | 25 + prowler/lib/outputs/finding.py | 15 + prowler/lib/outputs/html/html.py | 45 + prowler/lib/outputs/outputs.py | 2 + prowler/lib/outputs/summary_table.py | 3 + prowler/providers/common/provider.py | 9 + prowler/providers/nhn/__init__.py | 0 .../providers/nhn/exceptions/exceptions.py | 0 prowler/providers/nhn/lib/__init__.py | 0 .../providers/nhn/lib/arguments/__init__.py | 0 .../providers/nhn/lib/arguments/arguments.py | 17 + .../providers/nhn/lib/mutelist/__init__.py | 0 .../providers/nhn/lib/mutelist/mutelist.py | 14 + prowler/providers/nhn/lib/service/__init__.py | 0 prowler/providers/nhn/lib/service/service.py | 1 + prowler/providers/nhn/models.py | 56 + prowler/providers/nhn/nhn_provider.py | 308 ++++ .../nhn/services/compute/__init__.py | 0 .../nhn/services/compute/compute_client.py | 4 + .../compute_instance_login_user/__init__.py | 0 .../compute_instance_login_user.metadata.json | 30 + .../compute_instance_login_user.py | 22 + .../compute_instance_public_ip/__init__.py | 0 .../compute_instance_public_ip.metadata.json | 30 + .../compute_instance_public_ip.py | 22 + .../__init__.py | 0 ...ute_instance_security_groups.metadata.json | 30 + .../compute_instance_security_groups.py | 24 + .../nhn/services/compute/compute_service.py | 95 ++ .../nhn/services/network/__init__.py | 0 .../nhn/services/network/network_client.py | 4 + .../nhn/services/network/network_service.py | 89 ++ .../__init__.py | 0 ..._vpc_has_empty_routingtables.metadata.json | 30 + .../network_vpc_has_empty_routingtables.py | 22 + .../__init__.py | 0 ...twork_vpc_subnet_enable_dhcp.metadata.json | 30 + .../network_vpc_subnet_enable_dhcp.py | 23 + .../__init__.py | 0 ...c_subnet_has_external_router.metadata.json | 30 + .../network_vpc_subnet_has_external_router.py | 21 + tests/lib/cli/parser_test.py | 4 +- .../lib/mutelist/fixtures/nhn_mutelist.yaml | 16 + .../nhn/lib/mutelist/nhn_mutelist_test.py | 100 ++ tests/providers/nhn/nhn_fixtures.py | 29 + tests/providers/nhn/nhn_provider_test.py | 157 ++ ...ompute_instance_login_user_test_for_nhn.py | 110 ++ ...compute_instance_public_ip_test_for_nhn.py | 107 ++ ...e_instance_security_groups_test_for_nhn.py | 108 ++ .../compute/compute_service_test_for_nhn.py | 100 ++ .../network/network_service_test_for_nhn.py | 98 ++ ...pc_has_empty_routingtables_test_for_nhn.py | 107 ++ .../network_vpc_subnet_enable_dhcp_for_nhn.py | 113 ++ ..._vpc_subnet_has_external_router_for_nhn.py | 104 ++ 62 files changed, 3615 insertions(+), 4 deletions(-) create mode 100644 prowler/compliance/nhn/__init__.py create mode 100644 prowler/compliance/nhn/iso27001_2022_nhn.json create mode 100644 prowler/lib/outputs/compliance/iso27001/iso27001_nhn.py create mode 100644 prowler/providers/nhn/__init__.py create mode 100644 prowler/providers/nhn/exceptions/exceptions.py create mode 100644 prowler/providers/nhn/lib/__init__.py create mode 100644 prowler/providers/nhn/lib/arguments/__init__.py create mode 100644 prowler/providers/nhn/lib/arguments/arguments.py create mode 100644 prowler/providers/nhn/lib/mutelist/__init__.py create mode 100644 prowler/providers/nhn/lib/mutelist/mutelist.py create mode 100644 prowler/providers/nhn/lib/service/__init__.py create mode 100644 prowler/providers/nhn/lib/service/service.py create mode 100644 prowler/providers/nhn/models.py create mode 100644 prowler/providers/nhn/nhn_provider.py create mode 100644 prowler/providers/nhn/services/compute/__init__.py create mode 100644 prowler/providers/nhn/services/compute/compute_client.py create mode 100644 prowler/providers/nhn/services/compute/compute_instance_login_user/__init__.py create mode 100644 prowler/providers/nhn/services/compute/compute_instance_login_user/compute_instance_login_user.metadata.json create mode 100644 prowler/providers/nhn/services/compute/compute_instance_login_user/compute_instance_login_user.py create mode 100644 prowler/providers/nhn/services/compute/compute_instance_public_ip/__init__.py create mode 100644 prowler/providers/nhn/services/compute/compute_instance_public_ip/compute_instance_public_ip.metadata.json create mode 100644 prowler/providers/nhn/services/compute/compute_instance_public_ip/compute_instance_public_ip.py create mode 100644 prowler/providers/nhn/services/compute/compute_instance_security_groups/__init__.py create mode 100644 prowler/providers/nhn/services/compute/compute_instance_security_groups/compute_instance_security_groups.metadata.json create mode 100644 prowler/providers/nhn/services/compute/compute_instance_security_groups/compute_instance_security_groups.py create mode 100644 prowler/providers/nhn/services/compute/compute_service.py create mode 100644 prowler/providers/nhn/services/network/__init__.py create mode 100644 prowler/providers/nhn/services/network/network_client.py create mode 100644 prowler/providers/nhn/services/network/network_service.py create mode 100644 prowler/providers/nhn/services/network/network_vpc_has_empty_routingtables/__init__.py create mode 100644 prowler/providers/nhn/services/network/network_vpc_has_empty_routingtables/network_vpc_has_empty_routingtables.metadata.json create mode 100644 prowler/providers/nhn/services/network/network_vpc_has_empty_routingtables/network_vpc_has_empty_routingtables.py create mode 100644 prowler/providers/nhn/services/network/network_vpc_subnet_enable_dhcp/__init__.py create mode 100644 prowler/providers/nhn/services/network/network_vpc_subnet_enable_dhcp/network_vpc_subnet_enable_dhcp.metadata.json create mode 100644 prowler/providers/nhn/services/network/network_vpc_subnet_enable_dhcp/network_vpc_subnet_enable_dhcp.py create mode 100644 prowler/providers/nhn/services/network/network_vpc_subnet_has_external_router/__init__.py create mode 100644 prowler/providers/nhn/services/network/network_vpc_subnet_has_external_router/network_vpc_subnet_has_external_router.metadata.json create mode 100644 prowler/providers/nhn/services/network/network_vpc_subnet_has_external_router/network_vpc_subnet_has_external_router.py create mode 100644 tests/providers/nhn/lib/mutelist/fixtures/nhn_mutelist.yaml create mode 100644 tests/providers/nhn/lib/mutelist/nhn_mutelist_test.py create mode 100644 tests/providers/nhn/nhn_fixtures.py create mode 100644 tests/providers/nhn/nhn_provider_test.py create mode 100644 tests/providers/nhn/services/compute/compute_instance_login_user/compute_instance_login_user_test_for_nhn.py create mode 100644 tests/providers/nhn/services/compute/compute_instance_public_ip/compute_instance_public_ip_test_for_nhn.py create mode 100644 tests/providers/nhn/services/compute/compute_instance_security__groups/compute_instance_security_groups_test_for_nhn.py create mode 100644 tests/providers/nhn/services/compute/compute_service_test_for_nhn.py create mode 100644 tests/providers/nhn/services/network/network_service_test_for_nhn.py create mode 100644 tests/providers/nhn/services/network/network_vpc_has_empty_routingtables/network_vpc_has_empty_routingtables_test_for_nhn.py create mode 100644 tests/providers/nhn/services/network/network_vpc_subnet_enable_dhcp/network_vpc_subnet_enable_dhcp_for_nhn.py create mode 100644 tests/providers/nhn/services/network/network_vpc_subnet_has_external_router/network_vpc_subnet_has_external_router_for_nhn.py diff --git a/README.md b/README.md index 55d84b2d8d..f7592cdf98 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ It contains hundreds of controls covering CIS, NIST 800, NIST CSF, CISA, RBI, Fe | Azure | 140 | 18 | 7 | 3 | | Kubernetes | 83 | 7 | 4 | 7 | | Microsoft365 | 5 | 2 | 1 | 0 | +| NHN (Unofficial) | 6 | 2 | 1 | 0 | > You can list the checks, services, compliance frameworks and categories with `prowler --list-checks`, `prowler --list-services`, `prowler --list-compliance` and `prowler --list-categories`. diff --git a/prowler/__main__.py b/prowler/__main__.py index d27b7da8e9..f7e617ebcc 100644 --- a/prowler/__main__.py +++ b/prowler/__main__.py @@ -63,6 +63,7 @@ from prowler.lib.outputs.compliance.iso27001.iso27001_gcp import GCPISO27001 from prowler.lib.outputs.compliance.iso27001.iso27001_kubernetes import ( KubernetesISO27001, ) +from prowler.lib.outputs.compliance.iso27001.iso27001_nhn import NHNISO27001 from prowler.lib.outputs.compliance.kisa_ismsp.kisa_ismsp_aws import AWSKISAISMSP from prowler.lib.outputs.compliance.mitre_attack.mitre_attack_aws import AWSMitreAttack from prowler.lib.outputs.compliance.mitre_attack.mitre_attack_azure import ( @@ -85,6 +86,7 @@ from prowler.providers.common.quick_inventory import run_provider_quick_inventor from prowler.providers.gcp.models import GCPOutputOptions from prowler.providers.kubernetes.models import KubernetesOutputOptions from prowler.providers.microsoft365.models import Microsoft365OutputOptions +from prowler.providers.nhn.models import NHNOutputOptions def prowler(): @@ -270,6 +272,10 @@ def prowler(): output_options = Microsoft365OutputOptions( args, bulk_checks_metadata, global_provider.identity ) + elif provider == "nhn": + output_options = NHNOutputOptions( + args, bulk_checks_metadata, global_provider.identity + ) # Run the quick inventory for the provider if available if hasattr(args, "quick_inventory") and args.quick_inventory: @@ -688,6 +694,35 @@ def prowler(): generated_outputs["compliance"].append(generic_compliance) generic_compliance.batch_write_data_to_file() + elif provider == "nhn": + for compliance_name in input_compliance_frameworks: + if compliance_name.startswith("iso27001_"): + # Generate ISO27001 Finding Object + filename = ( + f"{output_options.output_directory}/compliance/" + f"{output_options.output_filename}_{compliance_name}.csv" + ) + iso27001 = NHNISO27001( + findings=finding_outputs, + compliance=bulk_compliance_frameworks[compliance_name], + file_path=filename, + ) + generated_outputs["compliance"].append(iso27001) + iso27001.batch_write_data_to_file() + else: + filename = ( + f"{output_options.output_directory}/compliance/" + f"{output_options.output_filename}_{compliance_name}.csv" + ) + generic_compliance = GenericCompliance( + findings=finding_outputs, + compliance=bulk_compliance_frameworks[compliance_name], + create_file_descriptor=True, + file_path=filename, + ) + generated_outputs["compliance"].append(generic_compliance) + generic_compliance.batch_write_data_to_file() + # AWS Security Hub Integration if provider == "aws": # Send output to S3 if needed (-B / -D) for all the output formats diff --git a/prowler/compliance/nhn/__init__.py b/prowler/compliance/nhn/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/compliance/nhn/iso27001_2022_nhn.json b/prowler/compliance/nhn/iso27001_2022_nhn.json new file mode 100644 index 0000000000..d825981469 --- /dev/null +++ b/prowler/compliance/nhn/iso27001_2022_nhn.json @@ -0,0 +1,1313 @@ +{ + "Framework": "ISO27001", + "Version": "2022", + "Provider": "NHN", + "Description": "ISO (the International Organization for Standardization) and IEC (the International Electrotechnical Commission) form the specialized system for worldwide standardization. National bodies that are members of ISO or IEC participate in the development of International Standards through technical committees established by the respective organization to deal with particular fields of technical activity. ISO and IEC technical committees collaborate in fields of mutual interest. Other international organizations, governmental and non-governmental, in liaison with ISO and IEC, also take part in the work.", + "Requirements": [ + { + "Id": "A.5.1", + "Description": "Information security policy and topic-specific policies should be defined, approved by management, published, communicated to and acknowledged by relevant personnel and relevant interested parties, and reviewed at planned intervals and if significant changes occur.", + "Name": "Policies for information security", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.1", + "Objetive_Name": "Policies for information security", + "Check_Summary": "Information security policy and topic-specific policies should be defined, approved by management, published, communicated to and acknowledged by relevant personnel and relevant interested parties, and reviewed at planned intervals and if significant changes occur." + } + ], + "Checks": [] + }, + { + "Id": "A.5.2", + "Description": "Information security roles and responsibilities should be defined and allocated according to the organisation needs.", + "Name": "Roles and Responsibilities", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.2", + "Objetive_Name": "Roles and Responsibilities", + "Check_Summary": "Information security roles and responsibilities should be defined and allocated according to the organisation needs." + } + ], + "Checks": [] + }, + { + "Id": "A.5.3", + "Description": "Conflicting duties and conflicting areas of responsibility should be segregated.", + "Name": "Segregation of Duties", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.3", + "Objetive_Name": "Segregation of Duties", + "Check_Summary": "Conflicting duties and conflicting areas of responsibility should be segregated." + } + ], + "Checks": [] + }, + { + "Id": "A.5.4", + "Description": "Management should require all personnel to apply information security in accordance with the established information security policy, topic-specific policies and procedures of the organization.", + "Name": "Management Responsibilities", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.4", + "Objetive_Name": "Management Responsibilities", + "Check_Summary": "Management should require all personnel to apply information security in accordance with the established information security policy, topic-specific policies and procedures of the organization." + } + ], + "Checks": [] + }, + { + "Id": "A.5.5", + "Description": "The organisation should establish and maintain contact with relevant authorities.", + "Name": "Contact With Authorities", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.5", + "Objetive_Name": "Contact With Authorities", + "Check_Summary": "The organisation should establish and maintain contact with relevant authorities." + } + ], + "Checks": [] + }, + { + "Id": "A.5.6", + "Description": "The organisation should establish and maintain contact with special interest groups or other specialist security forums and professional associations.", + "Name": "Contact With Special Interest Groups", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.6", + "Objetive_Name": "Contact With Special Interest Groups", + "Check_Summary": "The organisation should establish and maintain contact with special interest groups or other specialist security forums and professional associations." + } + ], + "Checks": [] + }, + { + "Id": "A.5.7", + "Description": "Information relating to information security threats should be collected and analysed to produce threat intelligence.", + "Name": "Threat Intelligence", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.7", + "Objetive_Name": "Threat Intelligence", + "Check_Summary": "Information relating to information security threats should be collected and analysed to produce threat intelligence." + } + ], + "Checks": [] + }, + { + "Id": "A.5.8", + "Description": "Information security should be integrated into project management.", + "Name": "Information Security In Project Management", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.8", + "Objetive_Name": "Information Security In Project Management", + "Check_Summary": "Information security should be integrated into project management." + } + ], + "Checks": [] + }, + { + "Id": "A.5.9", + "Description": "An inventory of information and other associated assets, including owners, should be developed and maintained.", + "Name": "Inventory Of Information And Other Associated Assets", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.9", + "Objetive_Name": "Inventory Of Information And Other Associated Assets", + "Check_Summary": "An inventory of information and other associated assets, including owners, should be developed and maintained." + } + ], + "Checks": [] + }, + { + "Id": "A.5.10", + "Description": "Rules for the acceptable use and procedures for handling information and other associated assets should be identified, documented and implemented.", + "Name": "Acceptable Use Of Information And Other Associated Assets", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.10", + "Objetive_Name": "Acceptable Use Of Information And Other Associated Assets", + "Check_Summary": "Rules for the acceptable use and procedures for handling information and other associated assets should be identified, documented and implemented." + } + ], + "Checks": [] + }, + { + "Id": "A.5.11", + "Description": "Personnel and other interested parties as appropriate should return all the organisation’s assets in their possession upon change or termination of their employment, contract or agreement.", + "Name": "Return Of Assets", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.11", + "Objetive_Name": "Return Of Assets", + "Check_Summary": "Personnel and other interested parties as appropriate should return all the organisation’s assets in their possession upon change or termination of their employment, contract or agreement." + } + ], + "Checks": [] + }, + { + "Id": "A.5.12", + "Description": "Information should be classified according to the information security needs of the organisation based on confidentiality, integrity, availability and relevant interested party requirements.", + "Name": "Classification Of Information", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.12", + "Objetive_Name": "Classification Of Information", + "Check_Summary": "Information should be classified according to the information security needs of the organisation based on confidentiality, integrity, availability and relevant interested party requirements." + } + ], + "Checks": [] + }, + { + "Id": "A.5.13", + "Description": "An appropriate set of procedures for information labelling should be developed and implemented in accordance with the information classification scheme adopted by the organisation.", + "Name": "Labelling Of Information", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.13", + "Objetive_Name": "Labelling Of Information", + "Check_Summary": "An appropriate set of procedures for information labelling should be developed and implemented in accordance with the information classification scheme adopted by the organisation." + } + ], + "Checks": [] + }, + { + "Id": "A.5.14", + "Description": "Information transfer rules, procedures, or agreements should be in place for all types of transfer facilities within the organisation and between the organisation and other parties.", + "Name": "Information Transfer", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.14", + "Objetive_Name": "Information Transfer", + "Check_Summary": "Information transfer rules, procedures, or agreements should be in place for all types of transfer facilities within the organisation and between the organisation and other parties." + } + ], + "Checks": [] + }, + { + "Id": "A.5.15", + "Description": "Rules to control physical and logical access to information and other associated assets should be established", + "Name": "Access Control", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.15", + "Objetive_Name": "Access Control", + "Check_Summary": "Rules to control physical and logical access to information and other associated assets should be established" + } + ], + "Checks": [ + "compute_instance_login_user" + ] + }, + { + "Id": "A.5.16", + "Description": "The full lifecycle of identities should be managed.", + "Name": "Identity Management", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.16", + "Objetive_Name": "Identity Management", + "Check_Summary": "The full lifecycle of identities should be managed." + } + ], + "Checks": [] + }, + { + "Id": "A.5.17", + "Description": "Allocation and management of authentication information should be controlled by a management process, including advising personnel on the appropriate handling of authentication information.", + "Name": "Authentication Information", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.17", + "Objetive_Name": "Authentication Information", + "Check_Summary": "Allocation and management of authentication information should be controlled by a management process, including advising personnel on the appropriate handling of authentication information." + } + ], + "Checks": [] + }, + { + "Id": "A.5.18", + "Description": "Access rights to information and other associated assets should be provisioned, reviewed, modified and removed in accordance with the organisation’s topic-specific policy on and rules for access control.", + "Name": "Access Rights", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.18", + "Objetive_Name": "Access Rights", + "Check_Summary": "Access rights to information and other associated assets should be provisioned, reviewed, modified and removed in accordance with the organisation’s topic-specific policy on and rules for access control." + } + ], + "Checks": [ + "compute_instance_login_user" + ] + }, + { + "Id": "A.5.19", + "Description": "Processes and procedures should be defined and implemented to manage the information security risks associated with the use of supplier’s products or services.", + "Name": "Information Security In Supplier Relationships", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.19", + "Objetive_Name": "Information Security In Supplier Relationships", + "Check_Summary": "Processes and procedures should be defined and implemented to manage the information security risks associated with the use of supplier’s products or services." + } + ], + "Checks": [] + }, + { + "Id": "A.5.20", + "Description": "Relevant information security requirements should be established and agreed with each supplier based on the type of supplier relationship.", + "Name": "Addressing Information Security Within Supplier Agreements", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.20", + "Objetive_Name": "Addressing Information Security Within Supplier Agreements", + "Check_Summary": "Relevant information security requirements should be established and agreed with each supplier based on the type of supplier relationship." + } + ], + "Checks": [] + }, + { + "Id": "A.5.21", + "Description": "Processes and procedures should be defined and implemented to manage the information security risks associated with the ICT products and services supply chain.", + "Name": "Managing Information Security In The ICT Supply Chain", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.21", + "Objetive_Name": "Managing Information Security In The ICT Supply Chain", + "Check_Summary": "Processes and procedures should be defined and implemented to manage the information security risks associated with the ICT products and services supply chain." + } + ], + "Checks": [] + }, + { + "Id": "A.5.22", + "Description": "The organisation should regularly monitor, review, evaluate and manage change in supplier information security practices and service delivery.", + "Name": "Monitor, Review And Change Management Of Supplier Services", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.22", + "Objetive_Name": "Monitor, Review And Change Management Of Supplier Services", + "Check_Summary": "The organisation should regularly monitor, review, evaluate and manage change in supplier information security practices and service delivery." + } + ], + "Checks": [] + }, + { + "Id": "A.5.23", + "Description": "Processes for acquisition, use, management and exit from cloud services should be established in accordance with the organisation’s information security requirements.", + "Name": "Information Security For Use Of Cloud Services", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.23", + "Objetive_Name": "Information Security For Use Of Cloud Services", + "Check_Summary": "Processes for acquisition, use, management and exit from cloud services should be established in accordance with the organisation’s information security requirements." + } + ], + "Checks": [] + }, + { + "Id": "A.5.24", + "Description": "The organization should plan and prepare for managing information security incidents by defining, establishing and communicating information security incident management processes, roles and responsibilities.", + "Name": "Information Security Incident Management Planning and Preparation", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.24", + "Objetive_Name": "Information Security Incident Management Planning and Preparation", + "Check_Summary": "The organization should plan and prepare for managing information security incidents by defining, establishing and communicating information security incident management processes, roles and responsibilities." + } + ], + "Checks": [] + }, + { + "Id": "A.5.25", + "Description": "The organisation should assess information security events and decide if they are to be categorised as information security incidents.", + "Name": "Assessment And Decision On Information Security Events", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.25", + "Objetive_Name": "Assessment And Decision On Information Security Events", + "Check_Summary": "The organisation should assess information security events and decide if they are to be categorised as information security incidents." + } + ], + "Checks": [] + }, + { + "Id": "A.5.26", + "Description": "Information security incidents should be responded to in accordance with the documented procedures.", + "Name": "Response To Information Security Incidents", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.26", + "Objetive_Name": "Response To Information Security Incidents", + "Check_Summary": "Information security incidents should be responded to in accordance with the documented procedures." + } + ], + "Checks": [] + }, + { + "Id": "A.5.27", + "Description": "Knowledge gained from information security incidents should be used to strengthen and improve the information security controls.", + "Name": "Learning From Information Security Incidents", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.27", + "Objetive_Name": "Learning From Information Security Incidents", + "Check_Summary": "Knowledge gained from information security incidents should be used to strengthen and improve the information security controls." + } + ], + "Checks": [] + }, + { + "Id": "A.5.28", + "Description": "The organisation should establish and implement procedures for the identification, collection, acquisition and preservation of evidence related to information security events.", + "Name": "Collection Of Evidence", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.28", + "Objetive_Name": "Collection Of Evidence", + "Check_Summary": "The organisation should establish and implement procedures for the identification, collection, acquisition and preservation of evidence related to information security events." + } + ], + "Checks": [] + }, + { + "Id": "A.5.29", + "Description": "The organisation should plan how to maintain information security at an appropriate level during disruption.", + "Name": "Information Security During Disruption", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.29", + "Objetive_Name": "Information Security During Disruption", + "Check_Summary": "The organisation should plan how to maintain information security at an appropriate level during disruption." + } + ], + "Checks": [] + }, + { + "Id": "A.5.30", + "Description": "ICT readiness should be planned, implemented, maintained and tested based on business continuity objectives and ICT continuity requirements. ", + "Name": "Readiness For Business Continuity", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.30", + "Objetive_Name": "Readiness For Business Continuity", + "Check_Summary": "ICT readiness should be planned, implemented, maintained and tested based on business continuity objectives and ICT continuity requirements. " + } + ], + "Checks": [] + }, + { + "Id": "A.5.31", + "Description": "Legal, statutory, regulatory and contractual requirements relevant to information security and the organisations approach to meet these requirements should be identified, documented and kept up to date. ", + "Name": "Legal, statutory, regulatory and contractual requirements", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.31", + "Objetive_Name": "Legal, statutory, regulatory and contractual requirements", + "Check_Summary": "Legal, statutory, regulatory and contractual requirements relevant to information security and the organisations approach to meet these requirements should be identified, documented and kept up to date. " + } + ], + "Checks": [] + }, + { + "Id": "A.5.32", + "Description": "The organisation should implement appropriate procedures to protect intellectual property rights. ", + "Name": "Intellectual Property Rights", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.32", + "Objetive_Name": "Intellectual Property Rights", + "Check_Summary": "The organisation should implement appropriate procedures to protect intellectual property rights. " + } + ], + "Checks": [] + }, + { + "Id": "A.5.33", + "Description": "Records should be protected from loss, destruction, falsification, unauthorised access and unauthorised release.", + "Name": "Protection Of Records", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.33", + "Objetive_Name": "Protection Of Records", + "Check_Summary": "Records should be protected from loss, destruction, falsification, unauthorised access and unauthorised release." + } + ], + "Checks": [] + }, + { + "Id": "A.5.34", + "Description": "The organisation should identify and meet the requirements regarding the preservation of privacy and protection of PII according to applicable laws and regulations and contractual requirements.", + "Name": "Privacy And Protection Of PII", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.34", + "Objetive_Name": "Privacy And Protection Of PII", + "Check_Summary": "The organisation should identify and meet the requirements regarding the preservation of privacy and protection of PII according to applicable laws and regulations and contractual requirements." + } + ], + "Checks": [] + }, + { + "Id": "A.5.35", + "Description": "The organisations approach to managing information security and its implementation including people, processes and technologies should be reviewed independently at planned intervals, or when significant changes occur. ", + "Name": "Independent Review Of Information Security", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.35", + "Objetive_Name": "Independent Review Of Information Security", + "Check_Summary": "The organisations approach to managing information security and its implementation including people, processes and technologies should be reviewed independently at planned intervals, or when significant changes occur. " + } + ], + "Checks": [] + }, + { + "Id": "A.5.36", + "Description": "Compliance with the organisations information security policy, topic-specific policies, rules and standards should be regularly reviewed. ", + "Name": "Compliance With Policies, Rules And Standards For Information Security", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.36", + "Objetive_Name": "Compliance With Policies, Rules And Standards For Information Security", + "Check_Summary": "Compliance with the organisations information security policy, topic-specific policies, rules and standards should be regularly reviewed. " + } + ], + "Checks": [] + }, + { + "Id": "A.5.37", + "Description": "Operating procedures for information processing facilities should be documented and made available to personnel who need them. ", + "Name": "Documented Operating Procedures", + "Attributes": [ + { + "Category": "A.5 Organizational controls", + "Objetive_ID": "A.5.37", + "Objetive_Name": "Documented Operating Procedures", + "Check_Summary": "Operating procedures for information processing facilities should be documented and made available to personnel who need them. " + } + ], + "Checks": [] + }, + { + "Id": "A.6.1", + "Description": "Background verification checks on all candidates to become personnel should be carried out prior to joining the organisation and on an ongoing basis taking into consideration applicable laws, regulations and ethics and be proportional to the business requirements, the classification of the information to be accessed and the perceived risks. ", + "Name": "Screening", + "Attributes": [ + { + "Category": "A.6 People controls", + "Objetive_ID": "A.6.1", + "Objetive_Name": "Screening", + "Check_Summary": "Background verification checks on all candidates to become personnel should be carried out prior to joining the organisation and on an ongoing basis taking into consideration applicable laws, regulations and ethics and be proportional to the business requirements, the classification of the information to be accessed and the perceived risks. " + } + ], + "Checks": [] + }, + { + "Id": "A.6.2", + "Description": "The employment contractual agreements should state the personnel’s and the organisations responsibilities for information security. ", + "Name": "Terms and Conditions Of Employment", + "Attributes": [ + { + "Category": "A.6 People controls", + "Objetive_ID": "A.6.2", + "Objetive_Name": "Terms and Conditions Of Employment", + "Check_Summary": "The employment contractual agreements should state the personnel’s and the organisations responsibilities for information security. " + } + ], + "Checks": [] + }, + { + "Id": "A.6.3", + "Description": "Personnel of the organisation and relevant interested parties should receive appropriate information security awareness, education and training and regular updates of the organisations information security policy, topic-specific policies and procedures, as relevant for their job function.", + "Name": "Information Security Awareness, Education And Training", + "Attributes": [ + { + "Category": "A.6 People controls", + "Objetive_ID": "A.6.3", + "Objetive_Name": "Information Security Awareness, Education And Training", + "Check_Summary": "Personnel of the organisation and relevant interested parties should receive appropriate information security awareness, education and training and regular updates of the organisations information security policy, topic-specific policies and procedures, as relevant for their job function." + } + ], + "Checks": [] + }, + { + "Id": "A.6.4", + "Description": "A disciplinary process should be formalised and communicated to take actions against personnel and other relevant interested parties who have committed an information security policy violation.", + "Name": "Disciplinary Process", + "Attributes": [ + { + "Category": "A.6 People controls", + "Objetive_ID": "A.6.4", + "Objetive_Name": "Disciplinary Process", + "Check_Summary": "A disciplinary process should be formalised and communicated to take actions against personnel and other relevant interested parties who have committed an information security policy violation." + } + ], + "Checks": [] + }, + { + "Id": "A.6.5", + "Description": "Information security responsibilities and duties that remain valid after termination or change of employment should be defined, enforced and communicated to relevant personnel and other interested parties.", + "Name": "Responsibilities After Termination Or Change Of Employment", + "Attributes": [ + { + "Category": "A.6 People controls", + "Objetive_ID": "A.6.5", + "Objetive_Name": "Responsibilities After Termination Or Change Of Employment", + "Check_Summary": "Information security responsibilities and duties that remain valid after termination or change of employment should be defined, enforced and communicated to relevant personnel and other interested parties." + } + ], + "Checks": [] + }, + { + "Id": "A.6.6", + "Description": "Confidentiality or non-disclosure agreements reflecting the organisation’s needs for the protection of information should be identified, documented, regularly reviewed and signed by personnel and other relevant interested parties.", + "Name": "Confidentiality Or Non-Disclosure Agreements", + "Attributes": [ + { + "Category": "A.6 People controls", + "Objetive_ID": "A.6.6", + "Objetive_Name": "Confidentiality Or Non-Disclosure Agreements", + "Check_Summary": "Confidentiality or non-disclosure agreements reflecting the organisation’s needs for the protection of information should be identified, documented, regularly reviewed and signed by personnel and other relevant interested parties." + } + ], + "Checks": [] + }, + { + "Id": "A.6.7", + "Description": "Security measures should be implemented when personnel are working remotely to protect information accessed, processed or stored outside the organisation’s premises.", + "Name": "Remote Working", + "Attributes": [ + { + "Category": "A.6 People controls", + "Objetive_ID": "A.6.7", + "Objetive_Name": "Remote Working", + "Check_Summary": "Security measures should be implemented when personnel are working remotely to protect information accessed, processed or stored outside the organisation’s premises." + } + ], + "Checks": [] + }, + { + "Id": "A.6.8", + "Description": "The organisation should provide a mechanism for personnel to report observed or suspected information security events through appropriate channels in a timely manner.", + "Name": "Information Security Event Reporting", + "Attributes": [ + { + "Category": "A.6 People controls", + "Objetive_ID": "A.6.8", + "Objetive_Name": "Information Security Event Reporting", + "Check_Summary": "The organisation should provide a mechanism for personnel to report observed or suspected information security events through appropriate channels in a timely manner." + } + ], + "Checks": [] + }, + { + "Id": "A.7.1", + "Description": "To prevent unauthorised physical access, damage and interference to the organisations information and other associated assets.", + "Name": "Physical Security Perimeters", + "Attributes": [ + { + "Category": "A.7 Physical controls", + "Objetive_ID": "A.7.1", + "Objetive_Name": "Physical Security Perimeters", + "Check_Summary": "To prevent unauthorised physical access, damage and interference to the organisations information and other associated assets." + } + ], + "Checks": [] + }, + { + "Id": "A.7.2", + "Description": "Secure areas should be protected by appropriate entry controls and access points.", + "Name": "Physical Entry", + "Attributes": [ + { + "Category": "A.7 Physical controls", + "Objetive_ID": "A.7.2", + "Objetive_Name": "Physical Entry", + "Check_Summary": "Secure areas should be protected by appropriate entry controls and access points." + } + ], + "Checks": [] + }, + { + "Id": "A.7.3", + "Description": "Physical security for offices, rooms and facilities should be designed and implemented.", + "Name": "Securing Offices, Rooms And Facilities", + "Attributes": [ + { + "Category": "A.7 Physical controls", + "Objetive_ID": "A.7.3", + "Objetive_Name": "Securing Offices, Rooms And Facilities", + "Check_Summary": "Physical security for offices, rooms and facilities should be designed and implemented." + } + ], + "Checks": [] + }, + { + "Id": "A.7.4", + "Description": "Premises should be continuously monitored for unauthorised physical access.", + "Name": "Physical Security Monitoring", + "Attributes": [ + { + "Category": "A.7 Physical controls", + "Objetive_ID": "A.7.4", + "Objetive_Name": "Physical Security Monitoring", + "Check_Summary": "Premises should be continuously monitored for unauthorised physical access." + } + ], + "Checks": [] + }, + { + "Id": "A.7.5", + "Description": "Protection against physical and environmental threats, such as natural disasters and other intentional or unintentional physical threats to infrastructure should be designed and implemented.", + "Name": "Protecting Against Physical and Environmental Threats", + "Attributes": [ + { + "Category": "A.7 Physical controls", + "Objetive_ID": "A.7.5", + "Objetive_Name": "Protecting Against Physical and Environmental Threats", + "Check_Summary": "Protection against physical and environmental threats, such as natural disasters and other intentional or unintentional physical threats to infrastructure should be designed and implemented." + } + ], + "Checks": [] + }, + { + "Id": "A.7.6", + "Description": "Security measures for working in secure areas should be designed and implemented.", + "Name": "Working In Secure Areas", + "Attributes": [ + { + "Category": "A.7 Physical controls", + "Objetive_ID": "A.7.6", + "Objetive_Name": "Working In Secure Areas", + "Check_Summary": "Security measures for working in secure areas should be designed and implemented." + } + ], + "Checks": [] + }, + { + "Id": "A.7.7", + "Description": "Clear desk rules for papers and removable storage media and clear screen rules for information processing facilities should be defined and appropriately enforced.", + "Name": "Clear Desk And Clear Screen", + "Attributes": [ + { + "Category": "A.7 Physical controls", + "Objetive_ID": "A.7.7", + "Objetive_Name": "Clear Desk And Clear Screen", + "Check_Summary": "Clear desk rules for papers and removable storage media and clear screen rules for information processing facilities should be defined and appropriately enforced." + } + ], + "Checks": [] + }, + { + "Id": "A.7.8", + "Description": "Equipment should be sited securely and protected.", + "Name": "Equipment Siting And Protection", + "Attributes": [ + { + "Category": "A.7 Physical controls", + "Objetive_ID": "A.7.8", + "Objetive_Name": "Equipment Siting And Protection", + "Check_Summary": "Equipment should be sited securely and protected." + } + ], + "Checks": [] + }, + { + "Id": "A.7.9", + "Description": "Off-site assets should be protected.", + "Name": "Security Of Assets Off-Premises", + "Attributes": [ + { + "Category": "A.7 Physical controls", + "Objetive_ID": "A.7.9", + "Objetive_Name": "Security Of Assets Off-Premises", + "Check_Summary": "Off-site assets should be protected." + } + ], + "Checks": [] + }, + { + "Id": "A.7.10", + "Description": "Storage media should be managed through their life cycle of acquisition, use, transportation and disposal in accordance with the organisations classification scheme and handling requirements.", + "Name": "Storage Media", + "Attributes": [ + { + "Category": "A.7 Physical controls", + "Objetive_ID": "A.7.10", + "Objetive_Name": "Storage Media", + "Check_Summary": "Storage media should be managed through their life cycle of acquisition, use, transportation and disposal in accordance with the organisations classification scheme and handling requirements." + } + ], + "Checks": [] + }, + { + "Id": "A.7.11", + "Description": "Information processing facilities should be protected from power failures and other disruptions caused by failures in supporting utilities.", + "Name": "Supporting Utilities", + "Attributes": [ + { + "Category": "A.7 Physical controls", + "Objetive_ID": "A.7.11", + "Objetive_Name": "Supporting Utilities", + "Check_Summary": "Information processing facilities should be protected from power failures and other disruptions caused by failures in supporting utilities." + } + ], + "Checks": [] + }, + { + "Id": "A.7.12", + "Description": "Cables carrying power, data or supporting information services should be protected from interception", + "Name": "Cabling Security", + "Attributes": [ + { + "Category": "A.7 Physical controls", + "Objetive_ID": "A.7.12", + "Objetive_Name": "Cabling Security", + "Check_Summary": "Cables carrying power, data or supporting information services should be protected from interception" + } + ], + "Checks": [] + }, + { + "Id": "A.7.13", + "Description": "Equipment should be maintained correctly to ensure availability, integrity and confidentiality of information.", + "Name": "Equipment Maintenance", + "Attributes": [ + { + "Category": "A.7 Physical controls", + "Objetive_ID": "A.7.13", + "Objetive_Name": "Equipment Maintenance", + "Check_Summary": "Equipment should be maintained correctly to ensure availability, integrity and confidentiality of information." + } + ], + "Checks": [] + }, + { + "Id": "A.7.14", + "Description": "Items of equipment containing storage media should be verified to ensure that any sensitive data and licensed software has been removed or securely overwritten prior to disposal or re-use.", + "Name": "Secure Disposal Or Re-Use Of Equipment", + "Attributes": [ + { + "Category": "A.7 Physical controls", + "Objetive_ID": "A.7.14", + "Objetive_Name": "Secure Disposal Or Re-Use Of Equipment", + "Check_Summary": "Items of equipment containing storage media should be verified to ensure that any sensitive data and licensed software has been removed or securely overwritten prior to disposal or re-use." + } + ], + "Checks": [] + }, + { + "Id": "A.8.1", + "Description": "Information stored on, processed by or accessible via user endpoint devices should be protected.", + "Name": "User Endpoint Devices", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.1", + "Objetive_Name": "User Endpoint Devices", + "Check_Summary": "Information stored on, processed by or accessible via user endpoint devices should be protected." + } + ], + "Checks": [] + }, + { + "Id": "A.8.2", + "Description": "The allocation and use of privileged access rights should be restricted and managed.", + "Name": "Privileged Access Rights", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.2", + "Objetive_Name": "Privileged Access Rights", + "Check_Summary": "The allocation and use of privileged access rights should be restricted and managed." + } + ], + "Checks": [ + "compute_instance_login_user" + ] + }, + { + "Id": "A.8.3", + "Description": "Access to information and other associated assets should be restricted in accordance with the established topic-specific policy on access control.", + "Name": "Information Access Restriction", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.3", + "Objetive_Name": "Information Access Restriction", + "Check_Summary": "Access to information and other associated assets should be restricted in accordance with the established topic-specific policy on access control." + } + ], + "Checks": [] + }, + { + "Id": "A.8.4", + "Description": "Read and write access to source code, development tools and software libraries should be appropriately managed.", + "Name": "Access To Source Code", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.4", + "Objetive_Name": "Access To Source Code", + "Check_Summary": "Read and write access to source code, development tools and software libraries should be appropriately managed." + } + ], + "Checks": [] + }, + { + "Id": "A.8.5", + "Description": "Secure authentication technologies and procedures should be implemented based on information access restrictions and the topic-specific policy on access control.", + "Name": "Secure Authentication", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.5", + "Objetive_Name": "Secure Authentication", + "Check_Summary": "Secure authentication technologies and procedures should be implemented based on information access restrictions and the topic-specific policy on access control." + } + ], + "Checks": [] + }, + { + "Id": "A.8.6", + "Description": "The use of resources should be monitored and adjusted in line with current and expected capacity requirements.", + "Name": "Capacity Management", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.6", + "Objetive_Name": "Capacity Management", + "Check_Summary": "The use of resources should be monitored and adjusted in line with current and expected capacity requirements." + } + ], + "Checks": [] + }, + { + "Id": "A.8.7", + "Description": "Protection against malware should be implemented and supported by appropriate user awareness.", + "Name": "Protection Against Malware", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.7", + "Objetive_Name": "Protection Against Malware", + "Check_Summary": "Protection against malware should be implemented and supported by appropriate user awareness." + } + ], + "Checks": [] + }, + { + "Id": "A.8.8", + "Description": "Information about technical vulnerabilities of information systems in use should be obtained, the organisations exposure to such vulnerabilities should be evaluated and appropriate measures should be taken.", + "Name": "Management of Technical Vulnerabilities", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.8", + "Objetive_Name": "Management of Technical Vulnerabilities", + "Check_Summary": "Information about technical vulnerabilities of information systems in use should be obtained, the organisations exposure to such vulnerabilities should be evaluated and appropriate measures should be taken." + } + ], + "Checks": [] + }, + { + "Id": "A.8.9", + "Description": "Configurations, including security configurations, of hardware, software, services and networks should be established, documented, implemented, monitored and reviewed.", + "Name": "Configuration Management", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.9", + "Objetive_Name": "Configuration Management", + "Check_Summary": "Configurations, including security configurations, of hardware, software, services and networks should be established, documented, implemented, monitored and reviewed." + } + ], + "Checks": [ + "compute_instance_security_groups", + "network_vpc_subnet_enable_dhcp" + ] + }, + { + "Id": "A.8.10", + "Description": "Information stored in information systems, devices or in any other storage media should be deleted when no longer required.", + "Name": "Information Deletion", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.10", + "Objetive_Name": "Information Deletion", + "Check_Summary": "Information stored in information systems, devices or in any other storage media should be deleted when no longer required." + } + ], + "Checks": [] + }, + { + "Id": "A.8.11", + "Description": "Data masking should be used in accordance with the organisation’s topic-specific policy on access control and other related topic-specific policies, and business requirements, taking applicable legislation into consideration.", + "Name": "Data Masking", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.11", + "Objetive_Name": "Data Masking", + "Check_Summary": "Data masking should be used in accordance with the organisation’s topic-specific policy on access control and other related topic-specific policies, and business requirements, taking applicable legislation into consideration." + } + ], + "Checks": [] + }, + { + "Id": "A.8.12", + "Description": "Data leakage prevention measures should be applied to systems, networks and any other devices that process, store or transmit sensitive information.", + "Name": "Data Leakage Prevention", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.12", + "Objetive_Name": "Data Leakage Prevention", + "Check_Summary": "Data leakage prevention measures should be applied to systems, networks and any other devices that process, store or transmit sensitive information." + } + ], + "Checks": [] + }, + { + "Id": "A.8.13", + "Description": "Backup copies of information, software and systems should be maintained and regularly tested in accordance with the agreed topic-specific policy on backup.", + "Name": "Information Backup", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.13", + "Objetive_Name": "Information Backup", + "Check_Summary": "Backup copies of information, software and systems should be maintained and regularly tested in accordance with the agreed topic-specific policy on backup." + } + ], + "Checks": [] + }, + { + "Id": "A.8.14", + "Description": "Information processing facilities should be implemented with redundancy sufficient to meet availability", + "Name": "Redundancy of information processing facilities", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.14", + "Objetive_Name": "Redundancy of information processing facilities", + "Check_Summary": "Information processing facilities should be implemented with redundancy sufficient to meet availability" + } + ], + "Checks": [] + }, + { + "Id": "A.8.15", + "Description": "Logs that record activities, exceptions, faults and other relevant events should be produced, stored, protected and analysed.", + "Name": "Logging", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.15", + "Objetive_Name": "Logging", + "Check_Summary": "Logs that record activities, exceptions, faults and other relevant events should be produced, stored, protected and analysed." + } + ], + "Checks": [] + }, + { + "Id": "A.8.16", + "Description": "Networks, systems and applications should be monitored for anomalous behaviour and appropriate actions taken to evaluate potential information security incidents.", + "Name": "Monitoring Activities", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.16", + "Objetive_Name": "Monitoring Activities", + "Check_Summary": "Networks, systems and applications should be monitored for anomalous behaviour and appropriate actions taken to evaluate potential information security incidents." + } + ], + "Checks": [] + }, + { + "Id": "A.8.17", + "Description": "The clocks of information processing systems used by the organisation should be synchronised to approved time sources.", + "Name": "Clock Synchronisation", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.17", + "Objetive_Name": "Clock Synchronisation", + "Check_Summary": "The clocks of information processing systems used by the organisation should be synchronised to approved time sources." + } + ], + "Checks": [] + }, + { + "Id": "A.8.18", + "Description": "The use of utility programs that can be capable of overriding system and application controls should be restricted and tightly controlled", + "Name": "Use of Privileged Utility Programs", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.18", + "Objetive_Name": "Use of Privileged Utility Programs", + "Check_Summary": "The use of utility programs that can be capable of overriding system and application controls should be restricted and tightly controlled" + } + ], + "Checks": [] + }, + { + "Id": "A.8.19", + "Description": "Procedures and measures should be implemented to securely manage software installation on operational systems.", + "Name": "Installation of Software on Operational Systems", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.19", + "Objetive_Name": "Installation of Software on Operational Systems", + "Check_Summary": "Procedures and measures should be implemented to securely manage software installation on operational systems." + } + ], + "Checks": [] + }, + { + "Id": "A.8.20", + "Description": "Networks and network devices should be secured, managed and controlled to protect information in systems and applications.", + "Name": "Network Security", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.20", + "Objetive_Name": "Network Security", + "Check_Summary": "Networks and network devices should be secured, managed and controlled to protect information in systems and applications." + } + ], + "Checks": [ + "compute_instance_public_ip", + "compute_instance_security_groups", + "network_vpc_has_empty_routingtables", + "network_vpc_subnet_has_external_router" + ] + }, + { + "Id": "A.8.21", + "Description": "Security mechanisms, service levels and service requirements of network services should be identified, implemented and monitored.", + "Name": "Security of Network Services", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.21", + "Objetive_Name": "Security of Network Services", + "Check_Summary": "Security mechanisms, service levels and service requirements of network services should be identified, implemented and monitored." + } + ], + "Checks": [] + }, + { + "Id": "A.8.22", + "Description": "Security mechanisms, service levels and service requirements of network services should be identified, implemented and monitored.", + "Name": "Segregation of Networks", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.22", + "Objetive_Name": "Segregation of Networks", + "Check_Summary": "Security mechanisms, service levels and service requirements of network services should be identified, implemented and monitored." + } + ], + "Checks": [ + "compute_instance_public_ip", + "network_vpc_subnet_has_external_router" + ] + }, + { + "Id": "A.8.23", + "Description": "Access to external websites should be managed to reduce exposure to malicious content.", + "Name": "Web Filtering", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.23", + "Objetive_Name": "Web Filtering", + "Check_Summary": "Access to external websites should be managed to reduce exposure to malicious content." + } + ], + "Checks": [] + }, + { + "Id": "A.8.24", + "Description": "Rules for the effective use of cryptography, including cryptographic key management, should be defined and implemented.", + "Name": "Use of Cryptography", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.24", + "Objetive_Name": "Use of Cryptography", + "Check_Summary": "Rules for the effective use of cryptography, including cryptographic key management, should be defined and implemented." + } + ], + "Checks": [] + }, + { + "Id": "A.8.25", + "Description": "Rules for the secure development of software and systems should be established and applied.", + "Name": "Secure Development Life Cycle", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.25", + "Objetive_Name": "Secure Development Life Cycle", + "Check_Summary": "Rules for the secure development of software and systems should be established and applied." + } + ], + "Checks": [] + }, + { + "Id": "A.8.26", + "Description": "Information security requirements should be identified, specified and approved when developing or acquiring applications.", + "Name": "Application Security Requirements", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.26", + "Objetive_Name": "Application Security Requirements", + "Check_Summary": "Information security requirements should be identified, specified and approved when developing or acquiring applications." + } + ], + "Checks": [] + }, + { + "Id": "A.8.27", + "Description": "Principles for engineering secure systems should be established, documented, maintained and applied to any information system development activities.", + "Name": "Secure Systems Architecture and Engineering Principles", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.27", + "Objetive_Name": "Secure Systems Architecture and Engineering Principles", + "Check_Summary": "Principles for engineering secure systems should be established, documented, maintained and applied to any information system development activities." + } + ], + "Checks": [] + }, + { + "Id": "A.8.29", + "Description": "Security testing processes should be defined and implemented in the development life cycle.", + "Name": "Security Testing in Development and Acceptance", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.29", + "Objetive_Name": "Security Testing in Development and Acceptance", + "Check_Summary": "Security testing processes should be defined and implemented in the development life cycle." + } + ], + "Checks": [] + }, + { + "Id": "A.8.30", + "Description": "The organisation should direct, monitor and review the activities related to outsourced system development.", + "Name": "Outsourced Development", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.30", + "Objetive_Name": "Outsourced Development", + "Check_Summary": "The organisation should direct, monitor and review the activities related to outsourced system development." + } + ], + "Checks": [] + }, + { + "Id": "A.8.31", + "Description": "Development, testing and production environments should be separated and secured.", + "Name": "Separation of Development, Test and Production Environments", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.31", + "Objetive_Name": "Separation of Development, Test and Production Environments", + "Check_Summary": "Development, testing and production environments should be separated and secured." + } + ], + "Checks": [] + }, + { + "Id": "A.8.32", + "Description": "Changes to information processing facilities and information systems should be subject to change management procedures.", + "Name": "Change Management", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.32", + "Objetive_Name": "Change Management", + "Check_Summary": "Changes to information processing facilities and information systems should be subject to change management procedures." + } + ], + "Checks": [] + }, + { + "Id": "A.8.33", + "Description": "Test information should be appropriately selected, protected and managed.", + "Name": "Test Information", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.33", + "Objetive_Name": "Test Information", + "Check_Summary": "Test information should be appropriately selected, protected and managed." + } + ], + "Checks": [] + }, + { + "Id": "A.8.34", + "Description": "Audit tests and other assurance activities involving assessment of operational systems should be planned and agreed between the tester and appropriate management.", + "Name": "Protection of Information Systems During Audit Testing", + "Attributes": [ + { + "Category": "A.8 Technological controls", + "Objetive_ID": "A.8.34", + "Objetive_Name": "Protection of Information Systems During Audit Testing", + "Check_Summary": "Audit tests and other assurance activities involving assessment of operational systems should be planned and agreed between the tester and appropriate management." + } + ], + "Checks": [] + } + ] +} diff --git a/prowler/config/config.py b/prowler/config/config.py index 9c5a4021a6..6089a24514 100644 --- a/prowler/config/config.py +++ b/prowler/config/config.py @@ -29,6 +29,7 @@ class Provider(str, Enum): AZURE = "azure" KUBERNETES = "kubernetes" MICROSOFT365 = "microsoft365" + NHN = "nhn" # Compliance diff --git a/prowler/lib/check/models.py b/prowler/lib/check/models.py index 7dba878217..b76424f495 100644 --- a/prowler/lib/check/models.py +++ b/prowler/lib/check/models.py @@ -573,6 +573,29 @@ class CheckReportMicrosoft365(Check_Report): self.location = resource_location +@dataclass +class CheckReportNHN(Check_Report): + """Contains the NHN Check's finding information.""" + + resource_name: str + resource_id: str + location: str + + def __init__(self, metadata: Dict, resource: Any) -> None: + """Initialize the NHN Check's finding information. + + Args: + metadata: The metadata of the check. + resource: Basic information about the resource. Defaults to None. + """ + super().__init__(metadata, resource) + self.resource_name = getattr( + resource, "name", getattr(resource, "resource_name", "") + ) + self.resource_id = getattr(resource, "id", getattr(resource, "resource_id", "")) + self.location = getattr(resource, "location", "kr1") + + # Testing Pending def load_check_metadata(metadata_file: str) -> CheckMetadata: """ diff --git a/prowler/lib/cli/parser.py b/prowler/lib/cli/parser.py index cff8940134..0a5f63074d 100644 --- a/prowler/lib/cli/parser.py +++ b/prowler/lib/cli/parser.py @@ -26,15 +26,16 @@ class ProwlerArgumentParser: self.parser = argparse.ArgumentParser( prog="prowler", formatter_class=RawTextHelpFormatter, - usage="prowler [-h] [--version] {aws,azure,gcp,kubernetes,microsoft365,dashboard} ...", + usage="prowler [-h] [--version] {aws,azure,gcp,kubernetes,microsoft365,nhn,dashboard} ...", epilog=""" Available Cloud Providers: - {aws,azure,gcp,kubernetes} + {aws,azure,gcp,kubernetes,microsoft365,nhn} aws AWS Provider azure Azure Provider gcp GCP Provider kubernetes Kubernetes Provider microsoft365 Microsoft 365 Provider + nhn NHN Provider (Unofficial) Available components: dashboard Local dashboard diff --git a/prowler/lib/outputs/compliance/iso27001/iso27001_nhn.py b/prowler/lib/outputs/compliance/iso27001/iso27001_nhn.py new file mode 100644 index 0000000000..03bbfa7195 --- /dev/null +++ b/prowler/lib/outputs/compliance/iso27001/iso27001_nhn.py @@ -0,0 +1,87 @@ +from prowler.lib.check.compliance_models import Compliance +from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput +from prowler.lib.outputs.compliance.iso27001.models import NHNISO27001Model +from prowler.lib.outputs.finding import Finding + + +class NHNISO27001(ComplianceOutput): + """ + This class represents the NHN ISO 27001 compliance output. + + Attributes: + - _data (list): A list to store transformed data from findings. + - _file_descriptor (TextIOWrapper): A file descriptor to write data to a file. + + Methods: + - transform: Transforms findings into NHN ISO 27001 compliance format. + """ + + def transform( + self, + findings: list[Finding], + compliance: Compliance, + compliance_name: str, + ) -> None: + """ + Transforms a list of findings into NHN ISO 27001 compliance format. + + Parameters: + - findings (list): A list of findings. + - compliance (Compliance): A compliance model. + - compliance_name (str): The name of the compliance model. + + Returns: + - None + """ + for finding in findings: + finding_requirements = finding.compliance.get(compliance_name, []) + for requirement in compliance.Requirements: + if requirement.Id in finding_requirements: + for attribute in requirement.Attributes: + compliance_row = NHNISO27001Model( + Provider=finding.provider, + Description=compliance.Description, + AccountId=finding.account_uid, + Region=finding.region, + AssessmentDate=str(finding.timestamp), + Requirements_Id=requirement.Id, + Requirements_Description=requirement.Description, + Requirements_Name=requirement.Name, + Requirements_Attributes_Category=attribute.Category, + Requirements_Attributes_Objetive_ID=attribute.Objetive_ID, + Requirements_Attributes_Objetive_Name=attribute.Objetive_Name, + Requirements_Attributes_Check_Summary=attribute.Check_Summary, + Status=finding.status, + StatusExtended=finding.status_extended, + ResourceId=finding.resource_uid, + CheckId=finding.check_id, + Muted=finding.muted, + ResourceName=finding.resource_name, + ) + self._data.append(compliance_row) + + # Add manual requirements to the compliance output + for requirement in compliance.Requirements: + if not requirement.Checks: + for attribute in requirement.Attributes: + compliance_row = NHNISO27001Model( + Provider=compliance.Provider.lower(), + Description=compliance.Description, + AccountId="", + Region="", + AssessmentDate=str(finding.timestamp), + Requirements_Id=requirement.Id, + Requirements_Description=requirement.Description, + Requirements_Name=requirement.Name, + Requirements_Attributes_Category=attribute.Category, + Requirements_Attributes_Objetive_ID=attribute.Objetive_ID, + Requirements_Attributes_Objetive_Name=attribute.Objetive_Name, + Requirements_Attributes_Check_Summary=attribute.Check_Summary, + Status="MANUAL", + StatusExtended="Manual check", + ResourceId="manual_check", + ResourceName="Manual check", + CheckId="manual", + Muted=False, + ) + self._data.append(compliance_row) diff --git a/prowler/lib/outputs/compliance/iso27001/models.py b/prowler/lib/outputs/compliance/iso27001/models.py index 1d84b5c892..16e97a178d 100644 --- a/prowler/lib/outputs/compliance/iso27001/models.py +++ b/prowler/lib/outputs/compliance/iso27001/models.py @@ -99,3 +99,28 @@ class KubernetesISO27001Model(BaseModel): CheckId: str Muted: bool ResourceName: str + + +class NHNISO27001Model(BaseModel): + """ + NHNISO27001Model generates a finding's output in CSV NHN ISO27001 format. + """ + + Provider: str + Description: str + AccountId: str + Region: str + AssessmentDate: str + Requirements_Id: str + Requirements_Name: str + Requirements_Description: str + Requirements_Attributes_Category: str + Requirements_Attributes_Objetive_ID: str + Requirements_Attributes_Objetive_Name: str + Requirements_Attributes_Check_Summary: str + Status: str + StatusExtended: str + ResourceId: str + CheckId: str + Muted: bool + ResourceName: str diff --git a/prowler/lib/outputs/finding.py b/prowler/lib/outputs/finding.py index 58d21487c0..a1e68047fa 100644 --- a/prowler/lib/outputs/finding.py +++ b/prowler/lib/outputs/finding.py @@ -259,6 +259,21 @@ class Finding(BaseModel): output_data["resource_uid"] = check_output.resource_id output_data["region"] = check_output.location + elif provider.type == "nhn": + output_data["auth_method"] = ( + f"passwordCredentials: username={get_nested_attribute(provider, '_identity.username')}, " + f"tenantId={get_nested_attribute(provider, '_identity.tenant_id')}" + ) + output_data["account_uid"] = get_nested_attribute( + provider, "identity.tenant_id" + ) + output_data["account_name"] = get_nested_attribute( + provider, "identity.tenant_domain" + ) + output_data["resource_name"] = check_output.resource_name + output_data["resource_uid"] = check_output.resource_id + output_data["region"] = check_output.location + # check_output Unique ID # TODO: move this to a function # TODO: in Azure, GCP and K8s there are findings without resource_name diff --git a/prowler/lib/outputs/html/html.py b/prowler/lib/outputs/html/html.py index de99168326..8e3c88efd6 100644 --- a/prowler/lib/outputs/html/html.py +++ b/prowler/lib/outputs/html/html.py @@ -591,6 +591,51 @@ class HTML(Output): ) return "" + def get_nhn_assessment_summary(provider: Provider) -> str: + """ + get_nhn_assessment_summary gets the HTML assessment summary for the provider + + Args: + provider (Provider): the provider object + + Returns: + str: the HTML assessment summary + """ + try: + return f""" +
+
+
+ NHN Assessment Summary +
+
    +
  • + NHN Tenant Domain: {provider.identity.tenant_domain} +
  • +
+
+
+
+
+
+ NHN Credentials +
+
    +
  • + NHN Identity Type: {provider.identity.identity_type} +
  • +
  • + NHN Identity ID: {provider.identity.identity_id} +
  • +
+
+
""" + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}" + ) + return "" + @staticmethod def get_assessment_summary(provider: Provider) -> str: """ diff --git a/prowler/lib/outputs/outputs.py b/prowler/lib/outputs/outputs.py index dfd37429dd..0912f986af 100644 --- a/prowler/lib/outputs/outputs.py +++ b/prowler/lib/outputs/outputs.py @@ -18,6 +18,8 @@ def stdout_report(finding, color, verbose, status, fix): details = finding.namespace.lower() if finding.check_metadata.Provider == "microsoft365": details = finding.location + if finding.check_metadata.Provider == "nhn": + details = finding.location if (verbose or fix) and (not status or finding.status in status): if finding.muted: diff --git a/prowler/lib/outputs/summary_table.py b/prowler/lib/outputs/summary_table.py index 63d4480f80..b860285e2e 100644 --- a/prowler/lib/outputs/summary_table.py +++ b/prowler/lib/outputs/summary_table.py @@ -43,6 +43,9 @@ def display_summary_table( elif provider.type == "microsoft365": entity_type = "Tenant Domain" audited_entities = provider.identity.tenant_domain + elif provider.type == "nhn": + entity_type = "Tenant Domain" + audited_entities = provider.identity.tenant_domain # Check if there are findings and that they are not all MANUAL if findings and not all(finding.status == "MANUAL" for finding in findings): diff --git a/prowler/providers/common/provider.py b/prowler/providers/common/provider.py index ec9c5de0db..b8fe46d9e8 100644 --- a/prowler/providers/common/provider.py +++ b/prowler/providers/common/provider.py @@ -222,6 +222,15 @@ class Provider(ABC): tenant_id=arguments.tenant_id, fixer_config=fixer_config, ) + elif "nhn" in provider_class_name.lower(): + provider_class( + username=arguments.nhn_username, + password=arguments.nhn_password, + tenant_id=arguments.nhn_tenant_id, + config_path=arguments.config_file, + mutelist_path=arguments.mutelist_file, + fixer_config=fixer_config, + ) except TypeError as error: logger.critical( diff --git a/prowler/providers/nhn/__init__.py b/prowler/providers/nhn/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/nhn/exceptions/exceptions.py b/prowler/providers/nhn/exceptions/exceptions.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/nhn/lib/__init__.py b/prowler/providers/nhn/lib/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/nhn/lib/arguments/__init__.py b/prowler/providers/nhn/lib/arguments/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/nhn/lib/arguments/arguments.py b/prowler/providers/nhn/lib/arguments/arguments.py new file mode 100644 index 0000000000..d4925090e6 --- /dev/null +++ b/prowler/providers/nhn/lib/arguments/arguments.py @@ -0,0 +1,17 @@ +def init_parser(self): + """Init the NHN Provider CLI parser""" + nhn_parser = self.subparsers.add_parser( + "nhn", parents=[self.common_providers_parser], help="NHN Provider" + ) + + # Authentication + nhn_auth_subparser = nhn_parser.add_argument_group("Authentication") + nhn_auth_subparser.add_argument( + "--nhn-username", nargs="?", default=None, help="NHN API Username" + ) + nhn_auth_subparser.add_argument( + "--nhn-password", nargs="?", default=None, help="NHN API Password" + ) + nhn_auth_subparser.add_argument( + "--nhn-tenant-id", nargs="?", default=None, help="NHN Tenant ID" + ) diff --git a/prowler/providers/nhn/lib/mutelist/__init__.py b/prowler/providers/nhn/lib/mutelist/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/nhn/lib/mutelist/mutelist.py b/prowler/providers/nhn/lib/mutelist/mutelist.py new file mode 100644 index 0000000000..14144d00f8 --- /dev/null +++ b/prowler/providers/nhn/lib/mutelist/mutelist.py @@ -0,0 +1,14 @@ +from prowler.lib.check.models import CheckReportNHN +from prowler.lib.mutelist.mutelist import Mutelist +from prowler.lib.outputs.utils import unroll_dict, unroll_tags + + +class NHNMutelist(Mutelist): + def is_finding_muted(self, finding: CheckReportNHN) -> bool: + return self.is_muted( + finding.resource_id, + finding.check_metadata.CheckID, + finding.location, + finding.resource_name, + unroll_dict(unroll_tags(finding.resource_tags)), + ) diff --git a/prowler/providers/nhn/lib/service/__init__.py b/prowler/providers/nhn/lib/service/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/nhn/lib/service/service.py b/prowler/providers/nhn/lib/service/service.py new file mode 100644 index 0000000000..36781b8895 --- /dev/null +++ b/prowler/providers/nhn/lib/service/service.py @@ -0,0 +1 @@ +# TODO: If more services are added, we need to add common methods here diff --git a/prowler/providers/nhn/models.py b/prowler/providers/nhn/models.py new file mode 100644 index 0000000000..0288e1be5d --- /dev/null +++ b/prowler/providers/nhn/models.py @@ -0,0 +1,56 @@ +from pydantic import BaseModel + +from prowler.config.config import output_file_timestamp +from prowler.providers.common.models import ProviderOutputOptions + + +class NHNIdentityInfo(BaseModel): + """ + NHNIdentityInfo holds basic identity fields for the NHN provider. + + Attributes: + - identity_id (str): An optional identity ID if used by NHN services. + - identity_type (str): The type or role of the identity, if needed. + - tenant_domain (str): The tenant domain if applicable. + (Some NHN services might require a domain or project domain.) + - tenant_id (str): The tenant ID for the NHN Cloud account. + - username (str): The username associated with the account. + """ + + identity_id: str = "" + identity_type: str = "" + tenant_domain: str = "" + tenant_id: str + username: str + + +class NHNOutputOptions(ProviderOutputOptions): + """ + NHNOutputOptions overrides ProviderOutputOptions for NHN-specific output logic. + For example, generating a filename that includes the NHN tenant_id. + + Attributes inherited from ProviderOutputOptions: + - output_filename (str): The base filename used for generated reports. + - output_directory (str): The directory to store the output files. + - ... see ProviderOutputOptions for more details. + + Methods: + - __init__: Customizes the output filename logic for NHN. + """ + + def __init__(self, arguments, bulk_checks_metadata, identity: NHNIdentityInfo): + super().__init__(arguments, bulk_checks_metadata) + + # If --output-filename is not specified, build a default name. + if not getattr(arguments, "output_filename", None): + # If tenant_id exists, include it in the filename (e.g., prowler-output-nhn--20230101) + if identity.tenant_id: + self.output_filename = ( + f"prowler-output-nhn-{identity.tenant_id}-{output_file_timestamp}" + ) + # Otherwise just 'prowler-output-nhn-' + else: + self.output_filename = f"prowler-output-nhn-{output_file_timestamp}" + # If --output-filename was explicitly given, respect that + else: + self.output_filename = arguments.output_filename diff --git a/prowler/providers/nhn/nhn_provider.py b/prowler/providers/nhn/nhn_provider.py new file mode 100644 index 0000000000..3e179d694b --- /dev/null +++ b/prowler/providers/nhn/nhn_provider.py @@ -0,0 +1,308 @@ +import os +from typing import Optional + +import requests +from colorama import Style + +from prowler.config.config import ( + default_config_file_path, + get_default_mute_file_path, + load_and_validate_config_file, +) +from prowler.lib.logger import logger +from prowler.lib.utils.utils import print_boxes +from prowler.providers.common.models import Audit_Metadata, Connection +from prowler.providers.common.provider import Provider +from prowler.providers.nhn.lib.mutelist.mutelist import NHNMutelist +from prowler.providers.nhn.models import NHNIdentityInfo + + +class NhnProvider(Provider): + """ + NHN Provider class to handle the NHN provider + + Attributes: + - _type: str -> The type of the provider, which is set to "nhn". + - _session: requests.Session -> The session object associated with the NHN provider. + - _identity: NHNIdentityInfo -> The identity information for the NHN provider. + - _audit_config: dict -> The audit configuration for the NHN provider. + - _mutelist: NHNMutelist -> The mutelist object associated with the NHN provider. + - audit_metadata: Audit_Metadata -> The audit metadata for the NHN provider. + + Methods: + - __init__: Initializes the NHN provider. + - type: Returns the type of the NHN provider. + - identity: Returns the identity of the NHN provider.(ex: tenant_id, username) + - session: Returns the session object associated with the NHN provider.(ex: Bearer token) + - audit_config: Returns the audit configuration for the NHN provider. + - fixer_config: Returns the fixer configuration. + - mutelist: Returns the mutelist object associated with the NHN provider. + - validate_arguments: Validates the NHN provider arguments.(ex: username, password, tenant_id) + - print_credentials: Prints the NHN credentials information.(ex: username, tenant_id) + - setup_session: Set up the NHN session with the specified authentication method. + - test_connection: tests the provider connection + """ + + _type: str = "nhn" + _session: Optional[requests.Session] + _identity: NHNIdentityInfo + _audit_config: dict + _mutelist: NHNMutelist + # TODO: this is not optional, enforce for all providers + audit_metadata: Audit_Metadata + + def __init__( + self, + username: str = None, + password: str = None, + tenant_id: str = None, + config_path: str = None, + fixer_config: dict = None, + mutelist_path: str = None, + mutelist_content: dict = None, + ): + """ + Initializes the NHN provider. + + Args: + - username: The NHN Cloud client ID + - password: The NHN Cloud client password + - tenant_id: The NHN Cloud Tenant ID + - config_path: The path to the configuration file. + - fixer_config: The fixer configuration. + - mutelist_path: The path to the mutelist file. + - mutelist_content: The mutelist content. + """ + logger.info("Initializing Nhn Provider...") + + # 1) Store argument values + self._username = username or os.getenv("NHN_USERNAME") + self._password = password or os.getenv("NHN_PASSWORD") + self._tenant_id = tenant_id or os.getenv("NHN_TENANT_ID") + + if not all([self._username, self._password, self._tenant_id]): + raise ValueError("NhnProvider requires username, password and tenant_id") + + # 2) Load audit_config, fixer_config, mutelist + self._fixer_config = fixer_config if fixer_config else {} + if not config_path: + config_path = default_config_file_path + self._audit_config = load_and_validate_config_file(self._type, config_path) + + if mutelist_content: + self._mutelist = NHNMutelist(mutelist_content=mutelist_content) + else: + if not mutelist_path: + mutelist_path = get_default_mute_file_path(self._type) + self._mutelist = NHNMutelist(mutelist_path=mutelist_path) + + # 3) Initialize session/token + self._token = None + self._session = None + self.setup_session() + + # 4) Create NHNIdentityInfo object + self._identity = NHNIdentityInfo( + tenant_id=self._tenant_id, + username=self._username, + ) + + Provider.set_global_provider(self) + + @property + def type(self) -> str: + """ + Returns the type of the provider ("nhn"). + """ + return self._type + + @property + def identity(self) -> str: + """ + Returns the NHNIdentityInfo object, which may contain tenant_id, username, etc. + """ + return self._identity + + @property + def session(self) -> str: + """ + Returns the requests.Session object for NHN API calls. + """ + return self._session + + @property + def audit_config(self) -> dict: + """ + Returns the audit configuration loaded from file or default settings. + """ + return self._audit_config + + @property + def fixer_config(self) -> dict: + """ + Returns any fixer configuration provided to the NHN provider. + """ + return self._fixer_config + + @property + def mutelist(self) -> dict: + """ + Returns the NHNMutelist object for handling any muted checks. + """ + return self._mutelist + + @staticmethod + def validate_arguments(username: str, password: str, tenant_id: str) -> None: + """ + Ensures that username, password, and tenant_id are not empty. + """ + if not username or not password or not tenant_id: + raise ValueError("NHN Provider requires username, password and tenant_id.") + + def print_credentials(self) -> None: + """ + Prints the NHN credentials in a simple box format. + """ + report_lines = [ + f" Username: {self._username}", + f" TenantID: {self._tenant_id}", + ] + report_title = ( + f"{Style.BRIGHT}Using the NHN credentials below:{Style.RESET_ALL}" + ) + print_boxes(report_lines, report_title) + + def setup_session(self) -> None: + """ + Implement NHN Cloud Authentication method by calling Keystone v2.0 API(POST /v2.0/tokens). + ex) https://api-identity-infrastructure.nhncloudservice.com/v2.0/tokens + { + "auth": { + "tenantId": "f5073eaa26b64cffbee89411df94ce01", + "passwordCredentials": { + "username": "user@example.com", + "password": "secretsecret" + } + } + } + + On success, it creates a requests.Session and sets the X-Auth-Token header. + """ + url = "https://api-identity-infrastructure.nhncloudservice.com/v2.0/tokens" + data = { + "auth": { + "tenantId": self._tenant_id, + "passwordCredentials": { + "username": self._username, + "password": self._password, + }, + } + } + try: + response = requests.post(url, json=data, timeout=10) + if response.status_code == 200: + resp_json = response.json() + self._token = resp_json["access"]["token"]["id"] + sess = requests.Session() + sess.headers.update( + {"X-Auth-Token": self._token, "Content-Type": "application/json"} + ) + self._session = sess + logger.info("NHN token acquired successfully and session is set up.") + else: + logger.critical( + f"Failed to get token. Status: {response.status_code}, Body: {response.text}" + ) + raise ValueError("Failed to get NHN token") + except Exception as e: + logger.critical(f"[setup_session] Error: {e}") + raise e + + @staticmethod + def test_connection( + username: str, + password: str, + tenant_id: str, + raise_on_exception: bool = True, + ) -> Connection: + """ + Test connection to NHN Cloud by performing: + 1) Keystone token request + 2) (Optional) a small test API call to confirm credentials are valid + + Args: + username (str): NHN Cloud user ID (email) + password (str): NHN Cloud user password + tenant_id (str): NHN Cloud tenant ID + raise_on_exception (bool): If True, raise the caught exception; + if False, return Connection(error=exception). + + Returns: + Connection: + Connection(is_connected=True) if success, + otherwise Connection(error=Exception or custom error). + """ + try: + # 1) Validate arguments (예: username/password/tenant_id) + if not username or not password or not tenant_id: + error_msg = ( + "NHN test_connection error: missing username/password/tenant_id" + ) + logger.error(error_msg) + raise ValueError(error_msg) + + # 2) Request Keystone token + token_url = ( + "https://api-identity-infrastructure.nhncloudservice.com/v2.0/tokens" + ) + data = { + "auth": { + "tenantId": tenant_id, + "passwordCredentials": { + "username": username, + "password": password, + }, + } + } + resp = requests.post(token_url, json=data, timeout=10) + if resp.status_code != 200: + # Fail + error_msg = f"Failed to get token. Status: {resp.status_code}, Body: {resp.text}" + logger.error(error_msg) + if raise_on_exception: + raise Exception(error_msg) + return Connection(error=Exception(error_msg)) + + # Success + token_json = resp.json() + keystone_token = token_json["access"]["token"]["id"] + logger.info("NHN test_connection: Successfully acquired Keystone token.") + + # 3) (Optional) Test API call to confirm credentials are valid + compute_endpoint = f"https://kr1-api-instance.infrastructure.cloud.toast.com/v2/{tenant_id}" + + # Check servers list + headers = { + "X-Auth-Token": keystone_token, + "Content-Type": "application/json", + } + servers_resp = requests.get( + f"{compute_endpoint}/servers", headers=headers, timeout=10 + ) + if servers_resp.status_code == 200: + logger.info( + "NHN test_connection: /servers call success. Credentials valid." + ) + return Connection(is_connected=True) + else: + error_msg = f"/servers call failed. Status: {servers_resp.status_code}, Body: {servers_resp.text}" + logger.error(error_msg) + if raise_on_exception: + raise Exception(error_msg) + return Connection(error=Exception(error_msg)) + + except Exception as e: + logger.critical(f"{e.__class__.__name__}[{e.__traceback__.tb_lineno}]: {e}") + if raise_on_exception: + raise e + return Connection(error=e) diff --git a/prowler/providers/nhn/services/compute/__init__.py b/prowler/providers/nhn/services/compute/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/nhn/services/compute/compute_client.py b/prowler/providers/nhn/services/compute/compute_client.py new file mode 100644 index 0000000000..cf4bc98df0 --- /dev/null +++ b/prowler/providers/nhn/services/compute/compute_client.py @@ -0,0 +1,4 @@ +from prowler.providers.common.provider import Provider +from prowler.providers.nhn.services.compute.compute_service import NHNComputeService + +compute_client = NHNComputeService(Provider.get_global_provider()) diff --git a/prowler/providers/nhn/services/compute/compute_instance_login_user/__init__.py b/prowler/providers/nhn/services/compute/compute_instance_login_user/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/nhn/services/compute/compute_instance_login_user/compute_instance_login_user.metadata.json b/prowler/providers/nhn/services/compute/compute_instance_login_user/compute_instance_login_user.metadata.json new file mode 100644 index 0000000000..535597bf39 --- /dev/null +++ b/prowler/providers/nhn/services/compute/compute_instance_login_user/compute_instance_login_user.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "nhn", + "CheckID": "compute_instance_login_user", + "CheckTitle": "Check for Administrative Login Users in NHN Compute Instances", + "CheckType": [], + "ServiceName": "compute", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "high", + "ResourceType": "VMInstance", + "Description": "Checks if NHN Compute instances have administrative login users.", + "Risk": "", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Review the login users configured for each VM instance.", + "Url": "" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/nhn/services/compute/compute_instance_login_user/compute_instance_login_user.py b/prowler/providers/nhn/services/compute/compute_instance_login_user/compute_instance_login_user.py new file mode 100644 index 0000000000..95f575f83c --- /dev/null +++ b/prowler/providers/nhn/services/compute/compute_instance_login_user/compute_instance_login_user.py @@ -0,0 +1,22 @@ +from prowler.lib.check.models import Check, CheckReportNHN +from prowler.providers.nhn.services.compute.compute_client import compute_client + + +class compute_instance_login_user(Check): + def execute(self): + findings = [] + for instance in compute_client.instances: + report = CheckReportNHN( + metadata=self.metadata(), + resource=instance, + ) + report.status = "PASS" + report.status_extended = ( + f"VM Instance {instance.name} has a appropriate login user." + ) + if instance.login_user: + report.status = "FAIL" + report.status_extended = f"VM Instance {instance.name} has an Administrative(admin/root) login user." + findings.append(report) + + return findings diff --git a/prowler/providers/nhn/services/compute/compute_instance_public_ip/__init__.py b/prowler/providers/nhn/services/compute/compute_instance_public_ip/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/nhn/services/compute/compute_instance_public_ip/compute_instance_public_ip.metadata.json b/prowler/providers/nhn/services/compute/compute_instance_public_ip/compute_instance_public_ip.metadata.json new file mode 100644 index 0000000000..b643fc5c60 --- /dev/null +++ b/prowler/providers/nhn/services/compute/compute_instance_public_ip/compute_instance_public_ip.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "nhn", + "CheckID": "compute_instance_public_ip", + "CheckTitle": "Check for Public IP in NHN Compute Instances", + "CheckType": [], + "ServiceName": "compute", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "low", + "ResourceType": "VMInstance", + "Description": "Check if a floating(public) IP is assigned to an NHN compute instance.", + "Risk": "", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Remove or unassign floating IP if not required to reduce external exposure.", + "Url": "" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/nhn/services/compute/compute_instance_public_ip/compute_instance_public_ip.py b/prowler/providers/nhn/services/compute/compute_instance_public_ip/compute_instance_public_ip.py new file mode 100644 index 0000000000..1a02dee8c8 --- /dev/null +++ b/prowler/providers/nhn/services/compute/compute_instance_public_ip/compute_instance_public_ip.py @@ -0,0 +1,22 @@ +from prowler.lib.check.models import Check, CheckReportNHN +from prowler.providers.nhn.services.compute.compute_client import compute_client + + +class compute_instance_public_ip(Check): + def execute(self): + findings = [] + for instance in compute_client.instances: + report = CheckReportNHN( + metadata=self.metadata(), + resource=instance, + ) + report.status = "PASS" + report.status_extended = ( + f"VM Instance {instance.name} does not have a public IP." + ) + if instance.public_ip: + report.status = "FAIL" + report.status_extended = f"VM Instance {instance.name} has a public IP." + findings.append(report) + + return findings diff --git a/prowler/providers/nhn/services/compute/compute_instance_security_groups/__init__.py b/prowler/providers/nhn/services/compute/compute_instance_security_groups/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/nhn/services/compute/compute_instance_security_groups/compute_instance_security_groups.metadata.json b/prowler/providers/nhn/services/compute/compute_instance_security_groups/compute_instance_security_groups.metadata.json new file mode 100644 index 0000000000..22ec845232 --- /dev/null +++ b/prowler/providers/nhn/services/compute/compute_instance_security_groups/compute_instance_security_groups.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "nhn", + "CheckID": "compute_instance_security_groups", + "CheckTitle": "Check NHN Compute Security Group Configuration", + "CheckType": [], + "ServiceName": "compute", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "medium", + "ResourceType": "VMInstance", + "Description": "Checks if NHN Compute VM instances are using appropriate security group configurations. Using only the default security group can pose a security risk.", + "Risk": "", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Review and modify security group rules for each VM instance.", + "Url": "" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/nhn/services/compute/compute_instance_security_groups/compute_instance_security_groups.py b/prowler/providers/nhn/services/compute/compute_instance_security_groups/compute_instance_security_groups.py new file mode 100644 index 0000000000..a6fb3073f4 --- /dev/null +++ b/prowler/providers/nhn/services/compute/compute_instance_security_groups/compute_instance_security_groups.py @@ -0,0 +1,24 @@ +from prowler.lib.check.models import Check, CheckReportNHN +from prowler.providers.nhn.services.compute.compute_client import compute_client + + +class compute_instance_security_groups(Check): + def execute(self): + findings = [] + for instance in compute_client.instances: + report = CheckReportNHN( + metadata=self.metadata(), + resource=instance, + ) + report.status = "PASS" + report.status_extended = ( + f"VM Instance {instance.name} has a variety of security groups." + ) + if instance.security_groups: + report.status = "FAIL" + report.status_extended = ( + f"VM Instance {instance.name} has only the default security group." + ) + findings.append(report) + + return findings diff --git a/prowler/providers/nhn/services/compute/compute_service.py b/prowler/providers/nhn/services/compute/compute_service.py new file mode 100644 index 0000000000..082c04bc96 --- /dev/null +++ b/prowler/providers/nhn/services/compute/compute_service.py @@ -0,0 +1,95 @@ +from pydantic import BaseModel + +from prowler.lib.logger import logger +from prowler.providers.nhn.nhn_provider import NhnProvider + + +class NHNComputeService: + def __init__(self, provider: NhnProvider): + self.session = provider.session + self.tenant_id = provider._tenant_id + self.endpoint = "https://kr1-api-instance.infrastructure.cloud.toast.com" + + self.instances: list[Instance] = [] + self._get_instances() + + def _list_servers(self) -> list: + url = f"{self.endpoint}/v2/{self.tenant_id}/servers" + try: + response = self.session.get(url, timeout=10) + response.raise_for_status() + data = response.json() + return data.get("servers", []) + except Exception as e: + logger.error(f"Error listing servers: {e}") + return [] + + def _get_server_detail(self, server_id: str) -> dict: + url = f"{self.endpoint}/v2/{self.tenant_id}/servers/{server_id}" + try: + response = self.session.get(url, timeout=10) + response.raise_for_status() + return response.json() + except Exception as e: + logger.error(f"Error getting server detail {server_id}: {e}") + return {} + + def _check_public_ip(self, server_info: dict) -> bool: + addresses = server_info.get("addresses", {}) + for _, ip_list in addresses.items(): + for ip_info in ip_list: + if ip_info.get("OS-EXT-IPS:type") == "floating": + return True + return False + + def _check_security_groups(self, server_info: dict) -> bool: + secruity_groups = server_info.get("security_groups", []) + sg_names = [] + for sg_info in secruity_groups: + name = sg_info.get("name", "") + sg_names.append(name) + + for name in sg_names: + if name != "default": + return False + return True + + def _check_login_user(self, server_info: dict) -> bool: + metadata = server_info.get("metadata", {}) + login_user = metadata.get("login_username", "") + if ( + login_user == "Administrator" + or login_user == "root" + or login_user == "admin" + ): + return True + return False + + def _get_instances(self): + server_list = self._list_servers() + for server in server_list: + server_id = server["id"] + server_name = server["name"] + detail = self._get_server_detail(server_id) + server_info = detail.get("server", {}) + + server_public_ip = self._check_public_ip(server_info) + server_security_groups = self._check_security_groups(server_info) + server_login_user = self._check_login_user(server_info) + + instance = Instance( + id=server_id, + name=server_name, + public_ip=server_public_ip, + security_groups=server_security_groups, + login_user=server_login_user, + ) + self.instances.append(instance) + + +class Instance(BaseModel): + id: str + name: str + public_ip: bool + security_groups: bool + login_user: bool diff --git a/prowler/providers/nhn/services/network/__init__.py b/prowler/providers/nhn/services/network/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/nhn/services/network/network_client.py b/prowler/providers/nhn/services/network/network_client.py new file mode 100644 index 0000000000..9c53cfd3ba --- /dev/null +++ b/prowler/providers/nhn/services/network/network_client.py @@ -0,0 +1,4 @@ +from prowler.providers.common.provider import Provider +from prowler.providers.nhn.services.network.network_service import NHNNetworkService + +network_client = NHNNetworkService(Provider.get_global_provider()) diff --git a/prowler/providers/nhn/services/network/network_service.py b/prowler/providers/nhn/services/network/network_service.py new file mode 100644 index 0000000000..140e1d2815 --- /dev/null +++ b/prowler/providers/nhn/services/network/network_service.py @@ -0,0 +1,89 @@ +from pydantic import BaseModel + +from prowler.lib.logger import logger +from prowler.providers.nhn.nhn_provider import NhnProvider + + +class Subnet(BaseModel): + name: str + external_router: bool + enable_dhcp: bool + + +class Network(BaseModel): + id: str + name: str + empty_routingtables: bool + subnets: list[Subnet] + + +class NHNNetworkService: + def __init__(self, provider: NhnProvider): + self.session = provider.session + self.tenant_id = provider._tenant_id + self.endpoint = "https://kr1-api-network-infrastructure.nhncloudservice.com" + self.networks: list[Network] = [] + self._get_networks() + + def _list_vpcs(self) -> list: + url = f"{self.endpoint}/v2.0/vpcs" + try: + response = self.session.get(url, timeout=10) + response.raise_for_status() + data = response.json() + return data.get("vpcs", []) + except Exception as e: + logger.error(f"Error listing vpcs: {e}") + return [] + + def _get_vpc_detail(self, vpc_id: str) -> dict: + url = f"{self.endpoint}/v2.0/vpcs/{vpc_id}" + try: + response = self.session.get(url, timeout=10) + response.raise_for_status() + return response.json() + except Exception as e: + logger.error(f"Error getting vpc detail {vpc_id}: {e}") + return {} + + def _check_has_empty_routingtables(self, vpc_info: dict) -> bool: + routingtables = vpc_info.get("routingtables", []) + return not routingtables + + def _check_subnet_has_external_router(self, subnet: dict) -> bool: + return subnet.get("router:external", True) + + def _check_subnet_enable_dhcp(self, subnet: dict) -> bool: + return subnet.get("enable_dhcp", True) + + def _get_networks(self): + vpc_list = self._list_vpcs() + for vpc in vpc_list: + vpc_id = vpc["id"] + vpc_name = vpc["name"] + detail = self._get_vpc_detail(vpc_id) + vpc_info = detail.get("vpc", {}) + vpc_empty_routingtables = self._check_has_empty_routingtables(vpc_info) + + network = Network( + id=vpc_id, + name=vpc_name, + empty_routingtables=vpc_empty_routingtables, + subnets=[], + ) + self._get_subnets(vpc_info, network) + self.networks.append(network) + + def _get_subnets(self, vpc_info: dict, network: Network): + subnet_list = vpc_info.get("subnets", []) + # ret_subnet_list = [] + for subnet in subnet_list: + subnet_name = subnet["name"] + subnet_external_router = self._check_subnet_has_external_router(subnet) + subnet_enable_dhcp = self._check_subnet_enable_dhcp(subnet) + subnet_instance = Subnet( + name=subnet_name, + external_router=subnet_external_router, + enable_dhcp=subnet_enable_dhcp, + ) + network.subnets.append(subnet_instance) diff --git a/prowler/providers/nhn/services/network/network_vpc_has_empty_routingtables/__init__.py b/prowler/providers/nhn/services/network/network_vpc_has_empty_routingtables/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/nhn/services/network/network_vpc_has_empty_routingtables/network_vpc_has_empty_routingtables.metadata.json b/prowler/providers/nhn/services/network/network_vpc_has_empty_routingtables/network_vpc_has_empty_routingtables.metadata.json new file mode 100644 index 0000000000..8ded29ecb6 --- /dev/null +++ b/prowler/providers/nhn/services/network/network_vpc_has_empty_routingtables/network_vpc_has_empty_routingtables.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "nhn", + "CheckID": "network_vpc_has_empty_routingtables", + "CheckTitle": "Check if VPC has empty routing tables", + "CheckType": [], + "ServiceName": "network", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "medium", + "ResourceType": "VPC", + "Description": "Check if VPC has empty routing tables. Having empty routing tables may indicate misconfiguration or incomplete network setup.", + "Risk": "", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Ensure that VPC has properly configured routing tables with necessary routes to ensure proper network connectivity. If not needed, delete the empty routing tables.", + "Url": "" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/nhn/services/network/network_vpc_has_empty_routingtables/network_vpc_has_empty_routingtables.py b/prowler/providers/nhn/services/network/network_vpc_has_empty_routingtables/network_vpc_has_empty_routingtables.py new file mode 100644 index 0000000000..fd3051445a --- /dev/null +++ b/prowler/providers/nhn/services/network/network_vpc_has_empty_routingtables/network_vpc_has_empty_routingtables.py @@ -0,0 +1,22 @@ +from prowler.lib.check.models import Check, CheckReportNHN +from prowler.providers.nhn.services.network.network_client import network_client + + +class network_vpc_has_empty_routingtables(Check): + def execute(self): + findings = [] + for network in network_client.networks: + report = CheckReportNHN( + metadata=self.metadata(), + resource=network, + ) + report.status = "PASS" + report.status_extended = ( + f"VPC {network.name} does not have empty routingtables." + ) + if network.empty_routingtables: + report.status = "FAIL" + report.status_extended = f"VPC {network.name} has empty routingtables." + findings.append(report) + + return findings diff --git a/prowler/providers/nhn/services/network/network_vpc_subnet_enable_dhcp/__init__.py b/prowler/providers/nhn/services/network/network_vpc_subnet_enable_dhcp/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/nhn/services/network/network_vpc_subnet_enable_dhcp/network_vpc_subnet_enable_dhcp.metadata.json b/prowler/providers/nhn/services/network/network_vpc_subnet_enable_dhcp/network_vpc_subnet_enable_dhcp.metadata.json new file mode 100644 index 0000000000..c13f16cb25 --- /dev/null +++ b/prowler/providers/nhn/services/network/network_vpc_subnet_enable_dhcp/network_vpc_subnet_enable_dhcp.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "nhn", + "CheckID": "network_vpc_subnet_enable_dhcp", + "CheckTitle": "Check if DHCP is enabled for subnets in VPC", + "CheckType": [], + "ServiceName": "network", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "medium", + "ResourceType": "VPC", + "Description": "Check if DHCP is enabled for the subnets in the VPC.", + "Risk": "", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Ensure that DHCP is enabled for all subnets where automatic IP address allocation is needed.", + "Url": "" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/nhn/services/network/network_vpc_subnet_enable_dhcp/network_vpc_subnet_enable_dhcp.py b/prowler/providers/nhn/services/network/network_vpc_subnet_enable_dhcp/network_vpc_subnet_enable_dhcp.py new file mode 100644 index 0000000000..c90c71b8fd --- /dev/null +++ b/prowler/providers/nhn/services/network/network_vpc_subnet_enable_dhcp/network_vpc_subnet_enable_dhcp.py @@ -0,0 +1,23 @@ +from prowler.lib.check.models import Check, CheckReportNHN +from prowler.providers.nhn.services.network.network_client import network_client + + +class network_vpc_subnet_enable_dhcp(Check): + def execute(self): + findings = [] + for network in network_client.networks: + for subnet in network.subnets: + report = CheckReportNHN( + metadata=self.metadata(), + resource=network, + ) + report.status = "PASS" + report.status_extended = f"VPC {network.name} Subnet {subnet.name} does not have DHCP enabled." + if subnet.enable_dhcp: + report.status = "FAIL" + report.status_extended = ( + f"VPC {network.name} Subnet {subnet.name} has DHCP enabled." + ) + findings.append(report) + + return findings diff --git a/prowler/providers/nhn/services/network/network_vpc_subnet_has_external_router/__init__.py b/prowler/providers/nhn/services/network/network_vpc_subnet_has_external_router/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/nhn/services/network/network_vpc_subnet_has_external_router/network_vpc_subnet_has_external_router.metadata.json b/prowler/providers/nhn/services/network/network_vpc_subnet_has_external_router/network_vpc_subnet_has_external_router.metadata.json new file mode 100644 index 0000000000..4a975d0ed7 --- /dev/null +++ b/prowler/providers/nhn/services/network/network_vpc_subnet_has_external_router/network_vpc_subnet_has_external_router.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "nhn", + "CheckID": "network_vpc_subnet_has_external_router", + "CheckTitle": "Check for External Router in NHN VPC Subnet", + "CheckType": [], + "ServiceName": "network", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "medium", + "ResourceType": "VPC", + "Description": "Checks if VPC allows access from the public internet, by verifying if an external router is configured.", + "Risk": "", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Review the external router settings for the VPC Subnet.", + "Url": "" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/nhn/services/network/network_vpc_subnet_has_external_router/network_vpc_subnet_has_external_router.py b/prowler/providers/nhn/services/network/network_vpc_subnet_has_external_router/network_vpc_subnet_has_external_router.py new file mode 100644 index 0000000000..c8c7f42d64 --- /dev/null +++ b/prowler/providers/nhn/services/network/network_vpc_subnet_has_external_router/network_vpc_subnet_has_external_router.py @@ -0,0 +1,21 @@ +from prowler.lib.check.models import Check, CheckReportNHN +from prowler.providers.nhn.services.network.network_client import network_client + + +class network_vpc_subnet_has_external_router(Check): + def execute(self): + findings = [] + for network in network_client.networks: + for subnet in network.subnets: + report = CheckReportNHN( + metadata=self.metadata(), + resource=network, + ) + report.status = "PASS" + report.status_extended = f"VPC {network.name} Subnet {subnet.name} does not have an external router." + if subnet.external_router: + report.status = "FAIL" + report.status_extended = f"VPC {network.name} Subnet {subnet.name} has an external router." + findings.append(report) + + return findings diff --git a/tests/lib/cli/parser_test.py b/tests/lib/cli/parser_test.py index 16b322ac9c..37bec15d94 100644 --- a/tests/lib/cli/parser_test.py +++ b/tests/lib/cli/parser_test.py @@ -16,11 +16,11 @@ prowler_command = "prowler" # capsys # https://docs.pytest.org/en/7.1.x/how-to/capture-stdout-stderr.html -prowler_default_usage_error = "usage: prowler [-h] [--version] {aws,azure,gcp,kubernetes,microsoft365,dashboard} ..." +prowler_default_usage_error = "usage: prowler [-h] [--version] {aws,azure,gcp,kubernetes,microsoft365,nhn,dashboard} ..." def mock_get_available_providers(): - return ["aws", "azure", "gcp", "kubernetes", "microsoft365"] + return ["aws", "azure", "gcp", "kubernetes", "microsoft365", "nhn"] @pytest.mark.arg_parser diff --git a/tests/providers/nhn/lib/mutelist/fixtures/nhn_mutelist.yaml b/tests/providers/nhn/lib/mutelist/fixtures/nhn_mutelist.yaml new file mode 100644 index 0000000000..6a4be42b4b --- /dev/null +++ b/tests/providers/nhn/lib/mutelist/fixtures/nhn_mutelist.yaml @@ -0,0 +1,16 @@ +### 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: + "subscription_1": + Checks: + "compute_instance_public_ip": + Regions: + - "*" + Resources: + - "resource_1" + - "resource_2" \ No newline at end of file diff --git a/tests/providers/nhn/lib/mutelist/nhn_mutelist_test.py b/tests/providers/nhn/lib/mutelist/nhn_mutelist_test.py new file mode 100644 index 0000000000..dcdc17b2d9 --- /dev/null +++ b/tests/providers/nhn/lib/mutelist/nhn_mutelist_test.py @@ -0,0 +1,100 @@ +import yaml +from mock import MagicMock + +from prowler.providers.nhn.lib.mutelist.mutelist import NHNMutelist +from tests.lib.outputs.fixtures.fixtures import generate_finding_output + +MUTELIST_FIXTURE_PATH = "tests/providers/nhn/lib/mutelist/fixtures/nhn_mutelist.yaml" + + +class TestNHNMutelist: + def test_get_mutelist_file_from_local_file(self): + mutelist = NHNMutelist(mutelist_path=MUTELIST_FIXTURE_PATH) + + with open(MUTELIST_FIXTURE_PATH) as f: + mutelist_fixture = yaml.safe_load(f)["Mutelist"] + + assert mutelist.mutelist == mutelist_fixture + assert mutelist.mutelist_file_path == MUTELIST_FIXTURE_PATH + + def test_get_mutelist_file_from_local_file_non_existent(self): + mutelist_path = "tests/lib/mutelist/fixtures/not_present" + mutelist = NHNMutelist(mutelist_path=mutelist_path) + + assert mutelist.mutelist == {} + assert mutelist.mutelist_file_path == mutelist_path + + def test_validate_mutelist_not_valid_key(self): + mutelist_path = MUTELIST_FIXTURE_PATH + with open(mutelist_path) as f: + mutelist_fixture = yaml.safe_load(f)["Mutelist"] + + mutelist_fixture["Accounts1"] = mutelist_fixture["Accounts"] + del mutelist_fixture["Accounts"] + + mutelist = NHNMutelist(mutelist_content=mutelist_fixture) + + assert not mutelist.validate_mutelist() + assert mutelist.mutelist == {} + assert mutelist.mutelist_file_path is None + + def test_is_finding_muted(self): + # Mutelist + mutelist_content = { + "Accounts": { + "resource_1": { + "Checks": { + "check_test": { + "Regions": ["*"], + "Resources": ["test_resource"], + } + } + } + } + } + + mutelist = NHNMutelist(mutelist_content=mutelist_content) + + finding = MagicMock() + finding.resource_id = "resource_1" + finding.check_metadata = MagicMock() + finding.check_metadata.CheckID = "check_test" + finding.status = "FAIL" + finding.resource_name = "test_resource" + finding.location = "test_region" + finding.resource_tags = [] + + assert mutelist.is_finding_muted(finding) + + def test_mute_finding(self): + # Mutelist + mutelist_content = { + "Accounts": { + "resource_1": { + "Checks": { + "check_test": { + "Regions": ["*"], + "Resources": ["test_resource"], + } + } + } + } + } + + mutelist = NHNMutelist(mutelist_content=mutelist_content) + + finding_1 = generate_finding_output( + check_id="check_test", + status="FAIL", + account_uid="resource_1", + region="test_region", + resource_uid="test_resource", + resource_tags=[], + muted=False, + ) + + muted_finding = mutelist.mute_finding(finding=finding_1) + + assert muted_finding.status == "MUTED" + assert muted_finding.muted + assert muted_finding.raw["status"] == "FAIL" diff --git a/tests/providers/nhn/nhn_fixtures.py b/tests/providers/nhn/nhn_fixtures.py new file mode 100644 index 0000000000..a8a1fbd53e --- /dev/null +++ b/tests/providers/nhn/nhn_fixtures.py @@ -0,0 +1,29 @@ +from mock import MagicMock + +from prowler.providers.nhn.nhn_provider import NhnProvider + + +def set_mocked_nhn_provider( + username="test_user", + password="test_password", + tenant_id="tenant123", + audit_config=None, + fixer_config=None, +): + """ + Creates a mocked NHN Provider object for testing without real network calls. + """ + provider = MagicMock(spec=NhnProvider) # or just MagicMock() + + provider.type = "nhn" + provider._username = username + provider._password = password + provider._tenant_id = tenant_id + provider._token = "fake_keystone_token" + + provider.session = MagicMock() + + provider.audit_config = audit_config + provider.fixer_config = fixer_config + + return provider diff --git a/tests/providers/nhn/nhn_provider_test.py b/tests/providers/nhn/nhn_provider_test.py new file mode 100644 index 0000000000..dc59ac1347 --- /dev/null +++ b/tests/providers/nhn/nhn_provider_test.py @@ -0,0 +1,157 @@ +import os +from unittest.mock import MagicMock, patch + +import pytest + +from prowler.providers.common.models import Connection +from prowler.providers.nhn.nhn_provider import NhnProvider + + +class TestNhnProvider: + @patch.dict( + os.environ, + { + "NHN_USERNAME": "env_user", + "NHN_PASSWORD": "env_pass", + "NHN_TENANT_ID": "env_tenant", + }, + ) + @patch("prowler.providers.nhn.nhn_provider.load_and_validate_config_file") + @patch("requests.post") + def test_nhn_provider_init_success(self, mock_post, mock_load_config): + """ + Test a successful initialization of NhnProvider + with valid username/password/tenant_id and a Keystone token response = 200. + """ + # 1) Mock load_and_validate_config_file to avoid reading real config file + mock_load_config.return_value = {} + + # 2) Mock the requests.post to simulate a successful token response + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "access": {"token": {"id": "fake_keystone_token"}} + } + mock_post.return_value = mock_response + + # 3) Create provider + provider = NhnProvider( + username="test_user", + password="test_pass", + tenant_id="test_tenant", + ) + + # 4) Assertions + assert provider._token == "fake_keystone_token" + assert provider.session is not None + assert provider.session.headers["X-Auth-Token"] == "fake_keystone_token" + + @patch.dict(os.environ, {}, clear=True) + @patch( + "prowler.providers.nhn.nhn_provider.load_and_validate_config_file", + return_value={}, + ) + def test_nhn_provider_init_missing_args(self, mock_load_config): + """ + Test initialization when username/password/tenant_id is missing => ValueError + """ + with pytest.raises(ValueError) as exc_info: + NhnProvider(username="", password="secret", tenant_id="tenant") + assert "requires username, password and tenant_id" in str(exc_info.value) + + @patch( + "prowler.providers.nhn.nhn_provider.load_and_validate_config_file", + return_value={}, + ) + @patch("requests.post") + def test_nhn_provider_init_token_fail(self, mock_post, mock_load_config): + """ + Test the case where Keystone token request fails (non-200) + => provider._session remains None + """ + mock_post.return_value.status_code = 401 + mock_post.return_value.text = "Unauthorized" + + with pytest.raises(ValueError) as exc_info: + NhnProvider( + username="test_user", + password="test_pass", + tenant_id="tenant123", + ) + + assert "Failed to get NHN token" in str(exc_info.value) + + @patch("prowler.providers.nhn.nhn_provider.requests") + def test_test_connection_success(self, mock_requests): + """ + Test test_connection static method => success case + """ + # 1) Mock token success + mock_post_response = MagicMock() + mock_post_response.status_code = 200 + mock_post_response.json.return_value = { + "access": {"token": {"id": "fake_keystone_token"}} + } + # 2) Mock /servers success + mock_get_response = MagicMock() + mock_get_response.status_code = 200 + mock_requests.post.return_value = mock_post_response + mock_requests.get.return_value = mock_get_response + + conn = NhnProvider.test_connection( + username="test_user", + password="test_pass", + tenant_id="tenant123", + raise_on_exception=True, + ) + + assert isinstance(conn, Connection) + assert conn.is_connected is True + assert conn.error is None + + @patch("prowler.providers.nhn.nhn_provider.requests") + def test_test_connection_token_fail(self, mock_requests): + """ + Test test_connection => token request fails => returns Connection(error=...) + """ + mock_requests.post.return_value.status_code = 403 + mock_requests.post.return_value.text = "Forbidden" + + conn = NhnProvider.test_connection( + username="bad_user", + password="bad_pass", + tenant_id="tenant123", + raise_on_exception=False, # so we don't raise, we get Connection object + ) + assert conn.is_connected is False + assert conn.error is not None + assert "Failed to get token" in str(conn.error) + + @patch("prowler.providers.nhn.nhn_provider.requests") + def test_test_connection_servers_fail(self, mock_requests): + """ + Test test_connection => token OK, but /servers fails => returns Connection(error=...) + """ + # Keystone token success + mock_post_response = MagicMock() + mock_post_response.status_code = 200 + mock_post_response.json.return_value = { + "access": {"token": {"id": "fake_keystone_token"}} + } + mock_requests.post.return_value = mock_post_response + + # /servers fail + mock_get_response = MagicMock() + mock_get_response.status_code = 500 + mock_get_response.text = "Internal Server Error" + mock_requests.get.return_value = mock_get_response + + conn = NhnProvider.test_connection( + username="test_user", + password="test_pass", + tenant_id="tenant123", + raise_on_exception=False, + ) + assert conn.is_connected is False + assert conn.error is not None + assert "/servers call failed" in str(conn.error) diff --git a/tests/providers/nhn/services/compute/compute_instance_login_user/compute_instance_login_user_test_for_nhn.py b/tests/providers/nhn/services/compute/compute_instance_login_user/compute_instance_login_user_test_for_nhn.py new file mode 100644 index 0000000000..5e545d87c3 --- /dev/null +++ b/tests/providers/nhn/services/compute/compute_instance_login_user/compute_instance_login_user_test_for_nhn.py @@ -0,0 +1,110 @@ +from unittest import mock +from uuid import uuid4 + +from prowler.providers.nhn.services.compute.compute_service import Instance +from tests.providers.nhn.nhn_fixtures import set_mocked_nhn_provider + + +class Test_compute_instance_login_user: + def test_no_instances(self): + # 1) Make a MagicMock for compute_client + compute_client = mock.MagicMock() + compute_client.instances = [] + + # 2) Patch get_global_provider() to return a mocked NHN provider + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_nhn_provider(), + ), + mock.patch( + # patch the 'compute_instance_login_user.compute_client' used in the check code + "prowler.providers.nhn.services.compute.compute_instance_login_user.compute_instance_login_user.compute_client", + new=compute_client, + ), + ): + # 3) Import the check code AFTER patching + from prowler.providers.nhn.services.compute.compute_instance_login_user.compute_instance_login_user import ( + compute_instance_login_user, + ) + + # 4) Run the check + check = compute_instance_login_user() + result = check.execute() + + # 5) Assertions + assert len(result) == 0 # no instances => no findings + + def test_has_instance_non_admin_login(self): + # Make a MagicMock for compute_client + compute_client = mock.MagicMock() + + # Suppose we have 1 instance with login_user=False => PASS expected + instance_id = str(uuid4()) + instance_name = "testVM" + mock_instance = mock.MagicMock(spec=Instance) + mock_instance.id = instance_id + mock_instance.name = instance_name + mock_instance.login_user = False # => means not admin login + compute_client.instances = [mock_instance] + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_nhn_provider(), + ), + mock.patch( + "prowler.providers.nhn.services.compute.compute_instance_login_user.compute_instance_login_user.compute_client", + new=compute_client, + ), + ): + from prowler.providers.nhn.services.compute.compute_instance_login_user.compute_instance_login_user import ( + compute_instance_login_user, + ) + + check = compute_instance_login_user() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert "has a appropriate login user" in result[0].status_extended + assert result[0].resource_name == instance_name + assert result[0].resource_id == instance_id + + def test_has_instance_admin_login(self): + # Another scenario: instance with login_user=True => FAIL expected + compute_client = mock.MagicMock() + + instance_id = str(uuid4()) + instance_name = "rootVM" + mock_instance = mock.MagicMock(spec=Instance) + mock_instance.id = instance_id + mock_instance.name = instance_name + mock_instance.login_user = True # => admin or root user + compute_client.instances = [mock_instance] + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_nhn_provider(), + ), + mock.patch( + "prowler.providers.nhn.services.compute.compute_instance_login_user.compute_instance_login_user.compute_client", + new=compute_client, + ), + ): + from prowler.providers.nhn.services.compute.compute_instance_login_user.compute_instance_login_user import ( + compute_instance_login_user, + ) + + check = compute_instance_login_user() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + "has an Administrative(admin/root) login user" + in result[0].status_extended + ) + assert result[0].resource_name == instance_name + assert result[0].resource_id == instance_id diff --git a/tests/providers/nhn/services/compute/compute_instance_public_ip/compute_instance_public_ip_test_for_nhn.py b/tests/providers/nhn/services/compute/compute_instance_public_ip/compute_instance_public_ip_test_for_nhn.py new file mode 100644 index 0000000000..2b72460c78 --- /dev/null +++ b/tests/providers/nhn/services/compute/compute_instance_public_ip/compute_instance_public_ip_test_for_nhn.py @@ -0,0 +1,107 @@ +from unittest import mock +from uuid import uuid4 + +from prowler.providers.nhn.services.compute.compute_service import Instance +from tests.providers.nhn.nhn_fixtures import set_mocked_nhn_provider + + +class Test_compute_instance_public_ip: + def test_no_instances(self): + # 1) Make a MagicMock for compute_client + compute_client = mock.MagicMock() + compute_client.instances = [] + + # 2) Patch get_global_provider() to return a mocked NHN provider + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_nhn_provider(), + ), + mock.patch( + # patch the 'compute_instance_public_ip.compute_client' used in the check code + "prowler.providers.nhn.services.compute.compute_instance_public_ip.compute_instance_public_ip.compute_client", + new=compute_client, + ), + ): + # 3) Import the check code AFTER patching + from prowler.providers.nhn.services.compute.compute_instance_public_ip.compute_instance_public_ip import ( + compute_instance_public_ip, + ) + + # 4) Run the check + check = compute_instance_public_ip() + result = check.execute() + + # 5) Assertions + assert len(result) == 0 # no instances => no findings + + def test_has_instance_non_public_ip(self): + # Make a MagicMock for compute_client + compute_client = mock.MagicMock() + + # Suppose we have 1 instance with public_ip=False => PASS expected + instance_id = str(uuid4()) + instance_name = "testVM" + mock_instance = mock.MagicMock(spec=Instance) + mock_instance.id = instance_id + mock_instance.name = instance_name + mock_instance.public_ip = False # => means does not have public IP + compute_client.instances = [mock_instance] + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_nhn_provider(), + ), + mock.patch( + "prowler.providers.nhn.services.compute.compute_instance_public_ip.compute_instance_public_ip.compute_client", + new=compute_client, + ), + ): + from prowler.providers.nhn.services.compute.compute_instance_public_ip.compute_instance_public_ip import ( + compute_instance_public_ip, + ) + + check = compute_instance_public_ip() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert "does not have a public IP" in result[0].status_extended + assert result[0].resource_name == instance_name + assert result[0].resource_id == instance_id + + def test_has_instance_public_ip(self): + # Another scenario: instance with public_ip=True => FAIL expected + compute_client = mock.MagicMock() + + instance_id = str(uuid4()) + instance_name = "rootVM" + mock_instance = mock.MagicMock(spec=Instance) + mock_instance.id = instance_id + mock_instance.name = instance_name + mock_instance.public_ip = True # => means has public IP + compute_client.instances = [mock_instance] + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_nhn_provider(), + ), + mock.patch( + "prowler.providers.nhn.services.compute.compute_instance_public_ip.compute_instance_public_ip.compute_client", + new=compute_client, + ), + ): + from prowler.providers.nhn.services.compute.compute_instance_public_ip.compute_instance_public_ip import ( + compute_instance_public_ip, + ) + + check = compute_instance_public_ip() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert "has a public IP" in result[0].status_extended + assert result[0].resource_name == instance_name + assert result[0].resource_id == instance_id diff --git a/tests/providers/nhn/services/compute/compute_instance_security__groups/compute_instance_security_groups_test_for_nhn.py b/tests/providers/nhn/services/compute/compute_instance_security__groups/compute_instance_security_groups_test_for_nhn.py new file mode 100644 index 0000000000..194c80be2c --- /dev/null +++ b/tests/providers/nhn/services/compute/compute_instance_security__groups/compute_instance_security_groups_test_for_nhn.py @@ -0,0 +1,108 @@ +from unittest import mock +from uuid import uuid4 + +from prowler.providers.nhn.services.compute.compute_service import Instance +from tests.providers.nhn.nhn_fixtures import set_mocked_nhn_provider + + +class Test_compute_instance_security_groups: + def test_no_instances(self): + # 1) Make a MagicMock for compute_client + compute_client = mock.MagicMock() + compute_client.instances = [] + + # 2) Patch get_global_provider() to return a mocked NHN provider + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_nhn_provider(), + ), + mock.patch( + # patch the 'compute_instance_security_groups.compute_client' used in the check code + "prowler.providers.nhn.services.compute.compute_instance_security_groups.compute_instance_security_groups.compute_client", + new=compute_client, + ), + ): + # 3) Import the check code AFTER patching + from prowler.providers.nhn.services.compute.compute_instance_security_groups.compute_instance_security_groups import ( + compute_instance_security_groups, + ) + + # 4) Run the check + check = compute_instance_security_groups() + result = check.execute() + + # 5) Assertions + assert len(result) == 0 # no instances => no findings + + def test_has_instance_variety_security_groups(self): + # Make a MagicMock for compute_client + compute_client = mock.MagicMock() + + # Suppose we have 1 instance with security_groups=False => PASS expected + instance_id = str(uuid4()) + instance_name = "testVM" + mock_instance = mock.MagicMock(spec=Instance) + mock_instance.id = instance_id + mock_instance.name = instance_name + mock_instance.security_groups = False # => means has variety of security groups + compute_client.instances = [mock_instance] + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_nhn_provider(), + ), + mock.patch( + "prowler.providers.nhn.services.compute.compute_instance_security_groups.compute_instance_security_groups.compute_client", + new=compute_client, + ), + ): + from prowler.providers.nhn.services.compute.compute_instance_security_groups.compute_instance_security_groups import ( + compute_instance_security_groups, + ) + + check = compute_instance_security_groups() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert "has a variety of security groups" in result[0].status_extended + assert result[0].resource_name == instance_name + assert result[0].resource_id == instance_id + + def test_has_instance_security_groups(self): + # Another scenario: instance with security_groups=True => FAIL expected + compute_client = mock.MagicMock() + + instance_id = str(uuid4()) + instance_name = "rootVM" + mock_instance = mock.MagicMock(spec=Instance) + mock_instance.id = instance_id + mock_instance.name = instance_name + mock_instance.security_groups = ( + True # => means has only the default security group + ) + compute_client.instances = [mock_instance] + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_nhn_provider(), + ), + mock.patch( + "prowler.providers.nhn.services.compute.compute_instance_security_groups.compute_instance_security_groups.compute_client", + new=compute_client, + ), + ): + from prowler.providers.nhn.services.compute.compute_instance_security_groups.compute_instance_security_groups import ( + compute_instance_security_groups, + ) + + check = compute_instance_security_groups() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert "has only the default security group" in result[0].status_extended + assert result[0].resource_name == instance_name + assert result[0].resource_id == instance_id diff --git a/tests/providers/nhn/services/compute/compute_service_test_for_nhn.py b/tests/providers/nhn/services/compute/compute_service_test_for_nhn.py new file mode 100644 index 0000000000..d5a8726b8d --- /dev/null +++ b/tests/providers/nhn/services/compute/compute_service_test_for_nhn.py @@ -0,0 +1,100 @@ +from unittest.mock import MagicMock, patch + +from prowler.providers.nhn.services.compute.compute_service import NHNComputeService +from tests.providers.nhn.nhn_fixtures import set_mocked_nhn_provider + + +class TestNHNComputeService: + @patch("prowler.providers.nhn.services.compute.compute_service.logger") + def test_compute_service_basic(self, mock_logger): + """ + Test that NHNComputeService properly calls _list_servers(), + _get_server_detail() for each server, and populates self.instances. + """ + # create a mocked NHN Provider + provider = set_mocked_nhn_provider( + username="testUser", + password="testPass", + tenant_id="tenant123", + ) + + # define mocked responses + mocked_response_servers = MagicMock() + mocked_response_servers.status_code = 200 + mocked_response_servers.json.return_value = { + "servers": [ + {"id": "server1", "name": "myserver1"}, + {"id": "server2", "name": "myserver2"}, + ] + } + + mocked_response_server1 = MagicMock() + mocked_response_server1.status_code = 200 + mocked_response_server1.json.return_value = { + "server": { + "addresses": { + "vpc1": [ + {"OS-EXT-IPS:type": "floating"}, + ] + }, + "security_groups": [{"name": "default"}], + "metadata": {"login_username": "root"}, + } + } + + mocked_response_server2 = MagicMock() + mocked_response_server2.status_code = 200 + mocked_response_server2.json.return_value = { + "server": { + "addresses": { + "vpc1": [ + {"OS-EXT-IPS:type": "fixed"}, + ] + }, + "security_groups": [{"name": "default"}, {"name": "other-sg"}], + "metadata": {"login_username": "regularuser"}, + } + } + + def get_side_effect(url, timeout=10): + print(f"Called with timeout={timeout}") + if ( + "/v2/tenant123/servers" in url + and not url.endswith("server1") + and not url.endswith("server2") + ): + return mocked_response_servers + elif url.endswith("server1"): + return mocked_response_server1 + elif url.endswith("server2"): + return mocked_response_server2 + else: + mock_404 = MagicMock() + mock_404.status_code = 404 + mock_404.text = "Not Found" + return mock_404 + + provider.session.get.side_effect = get_side_effect + + # create NHNComputeService, which internally calls _get_instances() + compute_service = NHNComputeService(provider) + + assert len(compute_service.instances) == 2 + + # first instance + inst1 = compute_service.instances[0] + assert inst1.id == "server1" + assert inst1.name == "myserver1" + assert inst1.public_ip is True + assert inst1.security_groups is True + assert inst1.login_user is True + + # second instance + inst2 = compute_service.instances[1] + assert inst2.id == "server2" + assert inst2.name == "myserver2" + assert inst2.public_ip is False + assert inst2.security_groups is False + assert inst2.login_user is False + + mock_logger.error.assert_not_called() diff --git a/tests/providers/nhn/services/network/network_service_test_for_nhn.py b/tests/providers/nhn/services/network/network_service_test_for_nhn.py new file mode 100644 index 0000000000..ca781e8e75 --- /dev/null +++ b/tests/providers/nhn/services/network/network_service_test_for_nhn.py @@ -0,0 +1,98 @@ +from unittest.mock import MagicMock, patch + +from prowler.providers.nhn.services.network.network_service import NHNNetworkService +from tests.providers.nhn.nhn_fixtures import set_mocked_nhn_provider + + +class TestNHNNetworkService: + @patch("prowler.providers.nhn.services.network.network_service.logger") + def test_network_service_basic(self, mock_logger): + """ + Test that NHNNetworkService correctly calls _list_vpcs(), + _get_vpc_detail() for each VPC, and populates self.networks and self.subnets. + """ + # create a mocked NHN Provider + provider = set_mocked_nhn_provider( + username="testUser", + password="testPass", + tenant_id="tenant123", + ) + + # define mocked responses for VPCs and Subnets + mocked_response_vpcs = MagicMock() + mocked_response_vpcs.status_code = 200 + mocked_response_vpcs.json.return_value = { + "vpcs": [ + {"id": "vpc1", "name": "myvpc1"}, + {"id": "vpc2", "name": "myvpc2"}, + ] + } + + mocked_response_vpc1 = MagicMock() + mocked_response_vpc1.status_code = 200 + mocked_response_vpc1.json.return_value = { + "vpc": { + "routingtables": [], + "subnets": [ + {"name": "subnet1", "router:external": True, "enable_dhcp": False}, + ], + } + } + + mocked_response_vpc2 = MagicMock() + mocked_response_vpc2.status_code = 200 + mocked_response_vpc2.json.return_value = { + "vpc": { + "routingtables": [{"id": "rt1"}], + "subnets": [ + {"name": "subnet2", "router:external": False, "enable_dhcp": True}, + ], + } + } + + def get_side_effect(url, timeout=10): + print(f"Called with timeout={timeout}") + if ( + "/v2.0/vpcs" in url + and not url.endswith("vpc1") + and not url.endswith("vpc2") + ): + return mocked_response_vpcs + elif url.endswith("vpc1"): + return mocked_response_vpc1 + elif url.endswith("vpc2"): + return mocked_response_vpc2 + else: + mock_404 = MagicMock() + mock_404.status_code = 404 + mock_404.text = "Not Found" + return mock_404 + + provider.session.get.side_effect = get_side_effect + + # create NHNNetworkService, which internally calls _get_networks() and _get_subnets() + network_service = NHNNetworkService(provider) + + assert len(network_service.networks) == 2 + + # first network + net1 = network_service.networks[0] + assert net1.id == "vpc1" + assert net1.name == "myvpc1" + assert net1.empty_routingtables is True + assert len(net1.subnets) == 1 + assert net1.subnets[0].name == "subnet1" + assert net1.subnets[0].external_router is True + assert net1.subnets[0].enable_dhcp is False + + # second network + net2 = network_service.networks[1] + assert net2.id == "vpc2" + assert net2.name == "myvpc2" + assert net2.empty_routingtables is False # Assuming there's a routing table + assert len(net2.subnets) == 1 + assert net2.subnets[0].name == "subnet2" + assert net2.subnets[0].external_router is False + assert net2.subnets[0].enable_dhcp is True + + mock_logger.error.assert_not_called() diff --git a/tests/providers/nhn/services/network/network_vpc_has_empty_routingtables/network_vpc_has_empty_routingtables_test_for_nhn.py b/tests/providers/nhn/services/network/network_vpc_has_empty_routingtables/network_vpc_has_empty_routingtables_test_for_nhn.py new file mode 100644 index 0000000000..3af49eb699 --- /dev/null +++ b/tests/providers/nhn/services/network/network_vpc_has_empty_routingtables/network_vpc_has_empty_routingtables_test_for_nhn.py @@ -0,0 +1,107 @@ +from unittest import mock +from uuid import uuid4 + +from prowler.providers.nhn.services.network.network_service import Network +from tests.providers.nhn.nhn_fixtures import set_mocked_nhn_provider + + +class Test_vpc_has_empty_routingtables: + def test_no_networks(self): + # 1) Make a MagicMock for network_client + network_client = mock.MagicMock() + network_client.networks = [] + + # 2) Patch get_global_provider() to return a mocked NHN provider + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_nhn_provider(), + ), + mock.patch( + # patch the 'network_empty_routingtables.network_client' used in the check code + "prowler.providers.nhn.services.network.network_vpc_has_empty_routingtables.network_vpc_has_empty_routingtables.network_client", + new=network_client, + ), + ): + # 3) Import the check code AFTER patching + from prowler.providers.nhn.services.network.network_vpc_has_empty_routingtables.network_vpc_has_empty_routingtables import ( + network_vpc_has_empty_routingtables, + ) + + # 4) Run the check + check = network_vpc_has_empty_routingtables() + result = check.execute() + + # 5) Assertions + assert len(result) == 0 # no networks => no findings + + def test_vpc_has_empty_routingtables(self): + # Make a MagicMock for network_client + network_client = mock.MagicMock() + + # Suppose we have 1 network with empty_routingtables=True => FAIL expected + network_id = str(uuid4()) + network_name = "testNetwork" + mock_network = mock.MagicMock(spec=Network) + mock_network.id = network_id + mock_network.name = network_name + mock_network.empty_routingtables = True + network_client.networks = [mock_network] + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_nhn_provider(), + ), + mock.patch( + "prowler.providers.nhn.services.network.network_vpc_has_empty_routingtables.network_vpc_has_empty_routingtables.network_client", + new=network_client, + ), + ): + from prowler.providers.nhn.services.network.network_vpc_has_empty_routingtables.network_vpc_has_empty_routingtables import ( + network_vpc_has_empty_routingtables, + ) + + check = network_vpc_has_empty_routingtables() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert "has empty routingtables" in result[0].status_extended + assert result[0].resource_name == network_name + assert result[0].resource_id == network_id + + def test_vpc_does_not_have_empty_routingtables(self): + # Another scenario: network with empty_routingtables=False => PASS expected + network_client = mock.MagicMock() + + network_id = str(uuid4()) + network_name = "testNetwork" + mock_network = mock.MagicMock(spec=Network) + mock_network.id = network_id + mock_network.name = network_name + mock_network.empty_routingtables = False + network_client.networks = [mock_network] + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_nhn_provider(), + ), + mock.patch( + "prowler.providers.nhn.services.network.network_vpc_has_empty_routingtables.network_vpc_has_empty_routingtables.network_client", + new=network_client, + ), + ): + from prowler.providers.nhn.services.network.network_vpc_has_empty_routingtables.network_vpc_has_empty_routingtables import ( + network_vpc_has_empty_routingtables, + ) + + check = network_vpc_has_empty_routingtables() + result = check.execute() + + assert len(result) == 0 + assert result[0].status == "PASS" + assert "dose not have empty routingtables" in result[0].status_extended + assert result[0].resource_name == network_name + assert result[0].resource_id == network_id diff --git a/tests/providers/nhn/services/network/network_vpc_subnet_enable_dhcp/network_vpc_subnet_enable_dhcp_for_nhn.py b/tests/providers/nhn/services/network/network_vpc_subnet_enable_dhcp/network_vpc_subnet_enable_dhcp_for_nhn.py new file mode 100644 index 0000000000..2c3c1cf722 --- /dev/null +++ b/tests/providers/nhn/services/network/network_vpc_subnet_enable_dhcp/network_vpc_subnet_enable_dhcp_for_nhn.py @@ -0,0 +1,113 @@ +from unittest import mock +from uuid import uuid4 + +from prowler.providers.nhn.services.network.network_service import Network, Subnet +from tests.providers.nhn.nhn_fixtures import set_mocked_nhn_provider + + +class Test_network_vpc_subnet_enable_dhcp: + def test_no_networks(self): + # 1) Make a MagicMock for network_client + network_client = mock.MagicMock() + network_client.networks = [] + + # 2) Patch get_global_provider() to return a mocked NHN provider + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_nhn_provider(), + ), + mock.patch( + # patch the 'network_vpc_subnet_enable_dhcp.network_client' used in the check code + "prowler.providers.nhn.services.network.network_vpc_subnet_enable_dhcp.network_vpc_subnet_enable_dhcp.network_client", + new=network_client, + ), + ): + # 3) Import the check code AFTER patching + from prowler.providers.nhn.services.network.network_vpc_subnet_enable_dhcp.network_vpc_subnet_enable_dhcp import ( + network_vpc_subnet_enable_dhcp, + ) + + # 4) Run the check + check = network_vpc_subnet_enable_dhcp() + result = check.execute() + + # 5) Assertions + assert len(result) == 0 # no networks => no findings + + def test_vpc_subnet_enable_dhcp(self): + # Make a MagicMock for network_client + network_client = mock.MagicMock() + + # Suppose we have 1 network with enable_dhcp=True => FAIL expected + network_id = str(uuid4()) + network_name = "testNetwork" + mock_network = mock.MagicMock(spec=Network) + mock_network.id = network_id + mock_network.name = network_name + mock_subnet = mock.MagicMock(spec=Subnet) + mock_subnet.name = "subnet1" + mock_subnet.enable_dhcp = True + mock_network.subnets = [mock_subnet] + network_client.networks = [mock_network] + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_nhn_provider(), + ), + mock.patch( + "prowler.providers.nhn.services.network.network_vpc_subnet_enable_dhcp.network_vpc_subnet_enable_dhcp.network_client", + new=network_client, + ), + ): + from prowler.providers.nhn.services.network.network_vpc_subnet_enable_dhcp.network_vpc_subnet_enable_dhcp import ( + network_vpc_subnet_enable_dhcp, + ) + + check = network_vpc_subnet_enable_dhcp() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert "has DHCP enabled" in result[0].status_extended + assert result[0].resource_name == network_name + assert result[0].resource_id == network_id + + def test_vpc_subnet_unable_dhcp(self): + # Another scenario: network with enable_dhcp=False => PASS expected + network_client = mock.MagicMock() + + network_id = str(uuid4()) + network_name = "testNetwork" + mock_network = mock.MagicMock(spec=Network) + mock_network.id = network_id + mock_network.name = network_name + mock_subnet = mock.MagicMock(spec=Subnet) + mock_subnet.name = "subnet1" + mock_subnet.enable_dhcp = False + mock_network.subnets = [mock_subnet] + network_client.networks = [mock_network] + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_nhn_provider(), + ), + mock.patch( + "prowler.providers.nhn.services.network.network_vpc_subnet_enable_dhcp.network_vpc_subnet_enable_dhcp.network_client", + new=network_client, + ), + ): + from prowler.providers.nhn.services.network.network_vpc_subnet_enable_dhcp.network_vpc_subnet_enable_dhcp import ( + network_vpc_subnet_enable_dhcp, + ) + + check = network_vpc_subnet_enable_dhcp() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert "does not have DHCP enabled" in result[0].status_extended + assert result[0].resource_name == network_name + assert result[0].resource_id == network_id diff --git a/tests/providers/nhn/services/network/network_vpc_subnet_has_external_router/network_vpc_subnet_has_external_router_for_nhn.py b/tests/providers/nhn/services/network/network_vpc_subnet_has_external_router/network_vpc_subnet_has_external_router_for_nhn.py new file mode 100644 index 0000000000..ec45a22ecd --- /dev/null +++ b/tests/providers/nhn/services/network/network_vpc_subnet_has_external_router/network_vpc_subnet_has_external_router_for_nhn.py @@ -0,0 +1,104 @@ +from unittest import mock +from uuid import uuid4 + +from prowler.providers.nhn.services.network.network_service import Network, Subnet +from tests.providers.nhn.nhn_fixtures import set_mocked_nhn_provider + + +class Test_network_vpc_subnet_has_external_router: + def test_no_networks(self): + network_client = mock.MagicMock() + network_client.networks = [] + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_nhn_provider(), + ), + mock.patch( + "prowler.providers.nhn.services.network.network_vpc_subnet_has_external_router.network_vpc_subnet_has_external_router.network_client", + new=network_client, + ), + ): + from prowler.providers.nhn.services.network.network_vpc_subnet_has_external_router.network_vpc_subnet_has_external_router import ( + network_vpc_subnet_has_external_router, + ) + + check = network_vpc_subnet_has_external_router() + result = check.execute() + + assert len(result) == 0 + + def test_vpc_subnet_has_external_router(self): + network_client = mock.MagicMock() + + network_id = str(uuid4()) + network_name = "testNetwork" + mock_network = mock.MagicMock(spec=Network) + mock_network.id = network_id + mock_network.name = network_name + mock_subnet = mock.MagicMock(spec=Subnet) + mock_subnet.name = "subnet1" + mock_subnet.external_router = True + mock_network.subnets = [mock_subnet] + network_client.networks = [mock_network] + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_nhn_provider(), + ), + mock.patch( + "prowler.providers.nhn.services.network.network_vpc_subnet_has_external_router.network_vpc_subnet_has_external_router.network_client", + new=network_client, + ), + ): + from prowler.providers.nhn.services.network.network_vpc_subnet_has_external_router.network_vpc_subnet_has_external_router import ( + network_vpc_subnet_has_external_router, + ) + + check = network_vpc_subnet_has_external_router() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert "has external router" in result[0].status_extended + assert result[0].resource_id == network_id + assert result[0].resource_name == network_name + + def test_vpc_subnet_no_external_router(self): + network_client = mock.MagicMock() + + network_id = str(uuid4()) + network_name = "testNetwork" + mock_network = mock.MagicMock(spec=Network) + mock_network.id = network_id + mock_network.name = network_name + mock_subnet = mock.MagicMock(spec=Subnet) + mock_subnet.name = "subnet1" + mock_subnet.external_router = False + mock_network.subnets = [mock_subnet] + network_client.networks = [mock_network] + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_nhn_provider(), + ), + mock.patch( + "prowler.providers.nhn.services.network.network_vpc_subnet_has_external_router.network_vpc_subnet_has_external_router.network_client", + new=network_client, + ), + ): + from prowler.providers.nhn.services.network.network_vpc_subnet_has_external_router.network_vpc_subnet_has_external_router import ( + network_vpc_subnet_has_external_router, + ) + + check = network_vpc_subnet_has_external_router() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert "no external router" in result[0].status_extended + assert result[0].resource_id == network_id + assert result[0].resource_name == network_name