InferWhetherServiceIsDev
Identify whether the service is a "development" server. Development servers have no external users and run no production workflows. These servers might be named "dev", but they might also be named "qa", "pre-production", "user acceptance testing", or use other non-production terms. This automation uses both public data visible to anyone (`active_classifications` as derived by Xpanse ASM) as well as checking internal data for AI-learned indicators of development systems (`asm_tags` as derived from integrations with non-public systems).
python · Cortex Attack Surface Management
Details
| ID | InferWhetherServiceIsDev |
|---|---|
| Language | python |
| From Version | 6.5.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
README
Identify whether the service is a “development” server. Development servers have no external users and run no production workflows. These servers might be named “dev”, but they might also be named “qa”, “pre-production”, “user acceptance testing”, or use other non-production terms. This automation uses both public data visible to anyone (active_classifications as derived by Xpanse ASM) as well as checking internal data for AI-learned indicators of development systems (asm_tags as derived from integrations with non-public systems).
Script Data
| Name | Description |
|---|---|
| Script Type | python3 |
| Cortex XSOAR Version | 6.5.0 |
Used In
This script is used in the following playbooks and scripts.
- Cortex ASM - Enrichment
Inputs
| Argument Name | Description |
|---|---|
| asm_tags | Array of key-value objects. Each object within the array must contain the keys “Key” and “Value” to be considered. The values associated with those keys can be arbitrary. Example: [{“Key”: “env”, “Value”: “dev”}, {“Key”: “Name”, “Value”: “ssh-ec2-machine-name”}]. |
| active_classifications | Array of strings representing the Xpanse ASM “active classifications” for the service. Example: [“RdpServer”, “SelfSignedCertificate”]. |
| hierarchy_info | Infrastructure hierarchy information to include CSPs (such GCP folder, AWS account and Azure subscription names, which can indicate the environment is dev). |
| provider | Provider of the asset as returned by Xpanse. |
Outputs
There are no outputs for this script.
import demistomock as demisto # noqa: F401 import pytest def test_canonicalize(): from InferWhetherServiceIsDev import _canonicalize_string assert _canonicalize_string("BLAH") == "blah" assert _canonicalize_string("'BLAH'") == "blah" assert _canonicalize_string('" BLAH" ') == "blah" @pytest.mark.parametrize( "raw,matches,list_type", [ ([{"key": "ENV", "value": "non-prd"}], [{"key": "ENV", "value": "non-prd"}], "dictionary"), ([{"key": "ENV", "value": "prd"}], [], "dictionary"), ( [{"key": "ENV", "value": "dv"}, {"key": "stage", "value": "sbx"}], [{"key": "ENV", "value": "dv"}, {"key": "stage", "value": "sbx"}], "dictionary", ), (["eng-dev", "rando"], ["eng-dev"], "string"), (["eng-dev"], [], "break"), ], ) def test_get_indicators_from_list(raw, matches, list_type): from InferWhetherServiceIsDev import get_indicators_from_list from InferWhetherServiceIsDev import is_dev_indicator assert get_indicators_from_list(raw, is_dev_indicator, list_type) == matches def test_is_dev_indicator(): from InferWhetherServiceIsDev import is_dev_indicator # Test Dev Matches assert is_dev_indicator("dev") assert is_dev_indicator("uat") assert is_dev_indicator("non-prod") assert is_dev_indicator("noprod") # Test no match assert not is_dev_indicator("devops") assert not is_dev_indicator("prod") assert not is_dev_indicator("pr") def test_is_prod_indicator(): from InferWhetherServiceIsDev import is_prod_indicator # Test Dev Matches assert is_prod_indicator("pr") assert is_prod_indicator("prod") # Test no Matches assert not is_prod_indicator("non-prod") assert not is_prod_indicator("staging") @pytest.mark.parametrize( "classifications, matches", [(["SshServer", "DevelopmentEnvironment"], ["DevelopmentEnvironment"]), (["SshServer"], [])] ) def test_get_indicators_from_external_classification(classifications, matches): from InferWhetherServiceIsDev import get_indicators_from_external_classification assert get_indicators_from_external_classification(classifications) == matches @pytest.mark.parametrize( "external, internal, reason", [ (["DevelopmentEnvironment"], [], "match on external classification of DevelopmentEnvironment"), ( ["DevelopmentEnvironment"], [{"key": "env", "value": "non-prod", "source": "AWS"}], "match on external classification of DevelopmentEnvironment and tag {env: non-prod} from AWS", ), ([], [{"key": "env", "value": "non-prod", "source": "AWS"}], "match on tag {env: non-prod} from AWS"), ( [], [{"key": "env", "value": "non-prod", "source": "AWS"}, {"key": "stage", "value": "sbx", "source": "GCP"}], "match on tag {env: non-prod} from AWS and tag {stage: sbx} from GCP", ), ], ) def test_determine_reason(external, internal, reason): from InferWhetherServiceIsDev import determine_reason assert determine_reason(external, internal, [], "") == reason def test_full_truth_table(): sample_dev_tag = [{"key": "stage", "value": "non-prod", "source": "AWS"}] sample_prod_tag = [{"key": "tier", "value": "prod", "source": "Tenable.io"}] # Blank list means no external classification or tag matches. sample_no_match = [] sample_dev_classification = ["DevelopmentEnvironment"] sample_dev_hierarchy = ["ENG-DEV"] sample_prod_hierarchy = ["ENG-PROD"] from InferWhetherServiceIsDev import final_decision # dev == True, all else is False # kv pair contains no indicators # DevEnv is set (--> dev) assert final_decision(sample_dev_classification, sample_no_match, sample_no_match, sample_no_match, sample_no_match, "")[ "result" ] # DevEnv is not set (--> can't tell) assert not final_decision(sample_no_match, sample_no_match, sample_no_match, sample_no_match, sample_no_match, "")["result"] # kv pair contains dev indicators only # DevEnv is set (--> dev) # Dev Tags only assert final_decision(sample_dev_classification, sample_dev_tag, sample_no_match, sample_no_match, sample_no_match, "")[ "result" ] # Dev Hierachy only assert final_decision(sample_dev_classification, sample_no_match, sample_no_match, sample_dev_hierarchy, sample_no_match, "")[ "result" ] # Both Dev Tags and Hierarchy assert final_decision(sample_dev_classification, sample_dev_tag, sample_no_match, sample_dev_hierarchy, sample_no_match, "")[ "result" ] # # DevEnv is not set (--> dev) # Dev Tag only assert final_decision(sample_no_match, sample_dev_tag, sample_no_match, sample_no_match, sample_no_match, "")["result"] # Dev Hierachy only assert final_decision(sample_no_match, sample_no_match, sample_no_match, sample_dev_hierarchy, sample_no_match, "")["result"] # Both Dev Tags and Hierarchy assert final_decision(sample_no_match, sample_dev_tag, sample_no_match, sample_dev_hierarchy, sample_no_match, "")["result"] # kv pair contains prod indicators only # DevEnv is set (--> conflicting) # PROD Tag only assert not final_decision(sample_dev_classification, sample_no_match, sample_prod_tag, sample_no_match, sample_no_match, "")[ "result" ] # PROD Hierachy only assert not final_decision( sample_dev_classification, sample_no_match, sample_no_match, sample_no_match, sample_prod_hierarchy, "" )["result"] # Both PROD Tags and Hierarchy assert not final_decision( sample_dev_classification, sample_no_match, sample_prod_tag, sample_no_match, sample_prod_hierarchy, "" )["result"] # # DevEnv is not set (--> prod) # PROD Tag only assert not final_decision(sample_no_match, sample_no_match, sample_prod_tag, sample_no_match, sample_no_match, "")["result"] # PROD Hierachy only assert not final_decision(sample_no_match, sample_no_match, sample_no_match, sample_no_match, sample_prod_hierarchy, "")[ "result" ] # Both PROD Tags and Hierarchy assert not final_decision(sample_no_match, sample_no_match, sample_prod_tag, sample_no_match, sample_prod_hierarchy, "")[ "result" ] # kv pair contains conflicting indicators # DevEnv is set (--> conflicting) # Conflicting tags only assert not final_decision(sample_dev_classification, sample_dev_tag, sample_prod_tag, sample_no_match, sample_no_match, "")[ "result" ] # Conflicting hierarchy only assert not final_decision( sample_dev_classification, sample_no_match, sample_no_match, sample_dev_hierarchy, sample_prod_hierarchy, "" )["result"] # Conflicting hiearchy and tags (would need other combinations to do full truth table) assert not final_decision( sample_dev_classification, sample_dev_tag, sample_prod_tag, sample_dev_hierarchy, sample_prod_hierarchy, "" )["result"] # # DevEnv is not set (--> conflicting) assert not final_decision(sample_no_match, sample_dev_tag, sample_prod_tag, sample_no_match, sample_no_match, "")["result"] # Conflicting hierarchy only assert not final_decision(sample_no_match, sample_no_match, sample_no_match, sample_dev_hierarchy, sample_prod_hierarchy, "")[ "result" ] # Conflicting hiearchy and tags (would need other combinations to do full truth table) assert not final_decision(sample_no_match, sample_dev_tag, sample_prod_tag, sample_dev_hierarchy, sample_prod_hierarchy, "")[ "result" ] @pytest.mark.parametrize( "in_classifications,in_tags,expected_out_boolean", [ ( [], [{"key": "ENV", "value": "non-prod", "source": "AWS"}], [ { "result": True, "result_readable": "The service is development", "confidence": "Likely Development", "reason": "match on tag {ENV: non-prod} from AWS", } ], ) ], ) def test_main(mocker, in_classifications, in_tags, expected_out_boolean): import InferWhetherServiceIsDev import unittest # Construct payload arg_payload = {} if in_classifications: arg_payload["active_classifications"] = in_classifications if in_tags: arg_payload["asm_tags"] = in_tags mocker.patch.object(demisto, "args", return_value=arg_payload) # Execute main using a mock that we can inspect for `executeCommand` demisto_execution_mock = mocker.patch.object(demisto, "executeCommand") InferWhetherServiceIsDev.main() # Verify the output value was set expected_calls_to_mock_object = [unittest.mock.call("setAlert", {"asmdevcheckdetails": expected_out_boolean})] assert demisto_execution_mock.call_args_list == expected_calls_to_mock_object