appNovi
Search across meshed network, security, and business data in appNovi to make efficient informed security decisions for risk management and incident response. Gain immediate intelligence on assets, visualize risk and threats across your network, and undertake interactive investigations across the network to reduce MTTR for incident response.
Analytics & SIEM · AppNovi
Details
| ID | appNovi |
|---|---|
| Provider | Fenix24 |
| Category | Analytics & SIEM |
| From Version | 6.5.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
Search across meshed network, security, and business data in appNovi to make efficient informed security decisions for risk management and incident response. Gain immediate intelligence on assets, visualize risk and threats across your network, and undertake interactive investigations across the network to reduce MTTR for incident response.
This integration was integrated and tested with appNovi v2.0
Configure appNovi in Cortex
| Parameter | Description | Required |
|---|---|---|
| Full URL of appNovi instance API. | True | |
| API Token | appNovi token URL for authentication | True |
| Trust any certificate (not secure) | False | |
| Use system proxy settings | False |
Commands
You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
search-appnovi-components
Search for Components by name or value
Base Command
search-appnovi-components
Input
| Argument Name | Description | Required |
|---|---|---|
| search_term | The string to use to search for Components. | Required |
| max_results | Number of results. Default is 25. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| appnovi.components.name | String | Name of thing |
| appnovi.components.coll | String | Collection containing thing |
| appnovi.components.u._id | String | appNovi Database ID. |
| appnovi.components.u._key | String | appNovi Database Key. |
| appnovi.components.u.identity.company | String | Company ID |
| appnovi.components.u.identity.type | String | Thing type |
| appnovi.components.u.identity.value | String | Thing name |
| appnovi.components.u.identity.datacenter | String | Datacenter ID |
| appnovi.components.u.identity.domain | String | Domain ID |
| appnovi.components.u.lastSeen | Date | Time thing was last seen. |
| appnovi.components.u.source | Date | This is information about the source of the entity. |
| appnovi.components.u.userProperties | String | These are the custom properties of the entity. |
| appnovi.components.connections | Number | Number of connections. |
| appnovi.time | Number | Query time (for diagnostics) |
search-appnovi-connected
Search for Components connected to supplied identity
Base Command
search-appnovi-connected
Input
| Argument Name | Description | Required |
|---|---|---|
| identity | appNovi identifier key-value or use key “_id” when searching via appNovi ID. | Required |
| category | Optional category of components to return. Possible values are: BaseComponent, Identity, IPAddress, Interface, Server, Storage, Hypervisor, CloudInfra, CloudService, NetworkInfra, Cluster, Container, MessageQueue, Vulnerability, CVE, Employee, Client, Software, Policy, Databases. | Optional |
| type | Optional type of components to return. Possible values are: genericcomponent, genericmetadata, rdns, user, machine, iamrole, iamuser, ip, ipv4, ipv6, mac, eni, interface, vmwarevm, ec2, azurevm, vm, server, physical, s3, datastore, bucket, storage, esx, vcenter, hypervisor, vpc, subscription, account, resourcegroup, region, availabilityzone, autoscalinggroup, elasticloadbalancer, org, folder, project, vnet, rds, ecr, lambda, redshift, dynamodbtable, router, firewall, switch, loadbalancer, proxy, middleware, kubernetes, aks, eks, swarm, mesos, nomad, docker, containerd, messagetopic, messageserver, vulnerability, cve, employee, contractor, desktop, laptop, pc, vdi, mobile, client, software, ami, securitygroup, iampolicy, column, database, table, view. | Optional |
| max_results | Number of results. Default is 25. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| appnovi._key | String | appNovi database key |
| appnovi._id | String | appNovi database ID. |
| appnovi.category | String | appNovi Category (e.g. Interface, Server) |
| appnovi.firstSeen | Date | When was this Thing first seen? |
| appnovi.identity.company | String | Company ID |
| appnovi.identity.datacenter | String | Datacenter ID |
| appnovi.identity.domain | String | Domain ID |
| appnovi.identity.type | String | Thing type |
| appnovi.identity.value | String | Identity value |
| appnovi.lastSeen | Date | Last time thing was seen |
| appnovi.name | String | Name of entity. |
| appnovi.source | Unknown | This is information about the source of the entity. |
| appnovi.userProperties | String | These are the custom properties of the entity. |
| appnovi.applications | String | List of applications |
search-appnovi-cve
Search for servers with matching CVE
Base Command
search-appnovi-cve
Input
| Argument Name | Description | Required |
|---|---|---|
| cve | CVE e.g. “CVE-2017-0143”. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| appnovi._key | String | appNovi database key |
| appnovi._id | String | appNovi database ID. |
| appnovi.category | String | appNovi Category (e.g. Interface, Server) |
| appnovi.firstSeen | Date | When was this Thing first seen? |
| appnovi.identity.company | String | Company ID |
| appnovi.identity.datacenter | String | Datacenter ID |
| appnovi.identity.domain | String | Domain ID |
| appnovi.identity.type | String | Thing type |
| appnovi.identity.value | String | Identity value |
| appnovi.lastSeen | Date | Last time thing was seen |
| appnovi.name | String | Name of entity. |
| appnovi.source | Unknown | This is information about the source of the entity. |
| appnovi.userProperties | String | These are the custom properties of the entity. |
| appnovi.applications | String | List of applications |
search-appnovi-component-property
Search for Components by property and value
Base Command
search-appnovi-component-property
Input
| Argument Name | Description | Required |
|---|---|---|
| property | Name of property. | Required |
| value | Value of property. | Required |
| max_results | Number of results. Default is 25. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| appnovi.components.name | String | Name of thing |
| appnovi.components.coll | String | Collection containing thing |
| appnovi.components.u._id | String | appNovi Database ID. |
| appnovi.components.u._key | String | appNovi Database Key. |
| appnovi.components.u.identity.company | String | Company ID |
| appnovi.components.u.identity.type | String | Thing type |
| appnovi.components.u.identity.value | String | Thing name |
| appnovi.components.u.identity.datacenter | String | Datacenter ID |
| appnovi.components.u.identity.domain | String | Domain ID |
| appnovi.components.u.lastSeen | Date | Time thing was last seen. |
| appnovi.components.u.source | Date | This is information about the source of the entity. |
| appnovi.components.u.userProperties | String | These are the custom properties of the entity. |
| appnovi.components.connections | Number | Number of connections. |
| appnovi.time | Number | Query time (for diagnostics) |
search-appnovi-server-by-ip
Search for servers using IP address
Base Command
search-appnovi-server-by-ip
Input
| Argument Name | Description | Required |
|---|---|---|
| ip | Server IP to search. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| appnovi._key | String | appNovi database key |
| appnovi._id | String | appNovi database ID. |
| appnovi.category | String | appNovi Category (e.g. Interface, Server) |
| appnovi.firstSeen | Date | When was this Thing first seen? |
| appnovi.identity.company | String | Company ID |
| appnovi.identity.datacenter | String | Datacenter ID |
| appnovi.identity.domain | String | Domain ID |
| appnovi.identity.type | String | Thing type |
| appnovi.identity.value | String | Identity value |
| appnovi.lastSeen | Date | Last time thing was seen |
| appnovi.name | String | Name of entity. |
| appnovi.source | Unknown | This is information about the source of the entity. |
| appnovi.userProperties | String | These are the custom properties of the entity. |
| appnovi.applications | String | List of applications |
Configuration parameters
appnovi_url— Full URL of appNovi instance. (required)appnovi_token— API Token (required)insecure— Trust any certificate (not secure)proxy— Use system proxy settings
Commands (5)
-
search-appnovi-component-propertySearch for Components by property and value.
-
search-appnovi-componentsSearch for Components by name or value.
-
search-appnovi-connectedSearch for Components connected to supplied identity.
-
search-appnovi-cveSearch for servers with matching CVE.
-
search-appnovi-server-by-ipSearch for servers using IP address.
import demistomock as demisto from CommonServerPython import * from CommonServerUserPython import * import json from typing import Any import urllib3 # Disable insecure warnings urllib3.disable_warnings() """ CONSTANTS """ API_PREFIX = "/api/v1" """ CLIENT CLASS """ class Client(BaseClient): """Client class to interact with the service API Interaction with the appNovi API """ def get_search_results(self, search_term: str, max_results: int = 25) -> dict[str, Any]: """Gets the IP reputation using the '/ip' API endpoint :type search_term: ``str`` :param search_term: Search for anything in appNovi :return: dict containing the results as returned from the API :rtype: ``Dict[str, Any]`` """ return self._http_request( method="GET", url_suffix="/components/search", params={ "string": search_term, "include_properties": True, "max_results": max_results, }, ) def get_connected_results( self, search_identity: str | dict, connect_type: Optional[list] = None, connect_category: Optional[list] = None, max_results: int = 25, ) -> list[dict[str, Any]]: # Can't use json= since it's already in use by xsoar return self._http_request( method="POST", url_suffix="/components/connected", data=json.dumps([search_identity]), params={ "max_results": max_results, "type": connect_type, "category": connect_category, }, ) def get_types(self): return self._http_request( method="GET", url_suffix="/components/types", ) def get_prop_search_results(self, prop: str, value: str, max_results: int = 25) -> dict: return self._http_request( method="GET", url_suffix="/components/propsearch", params={ "prop": prop, "value": value, "include_properties": True, "max_results": max_results, }, ) """ HELPER FUNCTIONS """ def dict_by_path(full_dict: dict, dict_path: str) -> Any: """Get values from dictionary by path just some code """ paths = dict_path.split(".") return_value = full_dict for path in paths: try: return_value = return_value[path] except (KeyError, TypeError): return None return return_value def process_sources(source_dict: dict) -> str: """Parse source dict into something readable""" source = source_dict.keys() return ",".join([s for s in source if len(s)]) """ COMMAND FUNCTIONS """ def test_module(client: Client) -> str: """Test Integration :type client: ``Client`` :return: A ``str`` representing if authentication was successful :rtype: ``str`` """ # Call the Client function and get the raw response try: client.get_types() except DemistoException as e: if "Forbidden" in str(e): return "Authorization Error: make sure API Key is correctly set" else: raise e return "ok" def search_appnovi_command(client: Client, args: dict[str, Any]) -> CommandResults: search_term = args.get("search_term", None) if not search_term: raise ValueError("Search term not specified") results = client.get_search_results(search_term, args.get("max_results", 25)) table_layout = { "name": "name", "appnoviid": "u._id", "type": "u.identity.type", "value": "u.identity.value", "lastSeen": "u.lastSeen", "connections": "connections", } readable_output = "### Search Results\n" + " | ".join(table_layout.keys()) + " | sources" + "\n" readable_output += "|".join(["-----" for th in table_layout]) + "|----" + "\n" for result in results["components"]: readable_output += ( " | ".join([str(dict_by_path(result, f)) for f in table_layout.values()]) + " | " + process_sources(result["u"].get("source", {})) + "\n" ) return CommandResults( readable_output=readable_output, outputs_prefix="appnovi.search", outputs_key_field=table_layout["appnoviid"], outputs=results, ) def search_appnovi_prop_command(client: Client, args: dict[str, Any]) -> CommandResults: search_prop = args.get("property", None) search_value = args.get("value", None) if not search_prop or not search_value: raise ValueError("Search terms not specified") results = client.get_prop_search_results(search_prop, search_value, args.get("max_results", 25)) table_layout = { "name": "name", "appnoviid": "u._id", "type": "u.identity.type", "value": "u.identity.value", "lastSeen": "u.lastSeen", "connections": "connections", } readable_output = "### Search Results\n" + " | ".join(table_layout.keys()) + " | sources" + "\n" readable_output += "|".join(["-----" for th in table_layout]) + "|----" + "\n" for result in results["components"]: readable_output += ( " | ".join([str(dict_by_path(result, f)) for f in table_layout.values()]) + " | " + process_sources(result["u"].get("source", {})) + "\n" ) return CommandResults( readable_output=readable_output, outputs_prefix="appnovi.searchProp", outputs_key_field=table_layout["appnoviid"], outputs=results, ) def search_appnovi_connected_command(client: Client, args: dict[str, Any]) -> CommandResults: """Search for components connected to other components. Can be limited in the types of things returned""" identity = args.get("identity", None) if not identity: raise ValueError("Identity not specified") if appnovi_id := identity.get("_id", None): identity = appnovi_id # Check for arguments cats = argToList(args.get("category", "")) or None types = argToList(args.get("type", "")) or None # Process identity results = client.get_connected_results(identity, types, cats) table_layout = { "name": "name", "appnoviid": "_id", "category": "category", "type": "identity.type", "value": "identity.value", } readable_output = "### Search Results\n" + " | ".join(table_layout.keys()) + "\n" readable_output += "|".join(["-----" for th in table_layout]) + "\n" for result in results: readable_output += " | ".join([str(dict_by_path(result, f)) for f in table_layout.values()]) + "\n" return CommandResults( readable_output=readable_output, outputs_prefix="appnovi.connected", outputs_key_field=table_layout["appnoviid"], outputs=results, ) def search_appnovi_cve_servers_command(client: Client, args: dict[str, Any]) -> CommandResults: """Find Servers with CVE This is a convenience command using the connected search""" cve = args.get("cve", None) if not cve: raise ValueError("CVE not specified") results = client.get_connected_results({"type": "cve", "value": cve.upper()}, None, ["Server"]) table_layout = { "name": "name", "appnoviid": "_id", "category": "category", "type": "identity.type", "value": "identity.value", } readable_output = "### Search Results\n" + " | ".join(table_layout.keys()) + "\n" readable_output += "|".join(["-----" for th in table_layout]) + "\n" for result in results: readable_output += " | ".join([str(dict_by_path(result, f)) for f in table_layout.values()]) + "\n" return CommandResults( readable_output=readable_output, outputs_prefix="appnovi.cveServers", outputs_key_field=table_layout["appnoviid"], outputs=results, ) def find_server_by_ip_command(client: Client, args: dict[str, Any]) -> CommandResults: """Use the connected function to return servers owning a given IP Good example of how you an chain requests to walk the graph via command or playbook """ ip = args.get("ip", None) if not ip: raise ValueError("IP not specified") # Keep track of things servers = {} interfaces: list[str] = [] # Let's get any servers or interfaces connected to the IP first_walk = client.get_connected_results({"type": "ip", "value": ip}, None, ["Server", "Interface"]) # Examine first walk for thing in first_walk: category = thing.get("category", None) # Collect interfaces for next walk if category == "Interface": # Make mypy happy by checking thing.get("_id") for str type. _id = thing.get("_id") if isinstance(_id, str): interfaces.append(_id) # Servers are usually not directly connected to IP, but in case... if category == "Server": servers[thing.get("_id")] = thing # Walk each interface. Serching by _id is /very/ fast for interface in interfaces: possible_server = client.get_connected_results(interface, None, ["Server"]) for server in possible_server: servers[server.get("_id")] = server # Walk finished, output some results readable_output = f"### Servers with IP {ip}\n" if len(servers.keys()): table_layout = { "name": "name", "appnoviid": "_id", "type": "identity.type", "value": "identity.value", } readable_output = "### Search Results\n" + " | ".join(table_layout.keys()) + "\n" readable_output += "|".join(["-----" for th in table_layout]) + "\n" for result in servers.values(): readable_output += " | ".join([str(dict_by_path(result, f)) for f in table_layout.values()]) + "\n" else: readable_output += "No Results \n" return CommandResults( readable_output=readable_output, outputs_prefix="appnovi.server", outputs_key_field="", outputs=[v for k, v in servers.items()], ) """ MAIN FUNCTION """ def main() -> None: # pragma: no cover """main function, parses params and runs command functions :return: :rtype: """ params = demisto.params() command = demisto.command() api_key = params.get("appnovi_token") base_url = urljoin(params["appnovi_url"], API_PREFIX) verify_certificate = not params.get("insecure", False) proxy = params.get("proxy", False) demisto.debug(f"Command being called is {command}") try: headers = {"Authorization": f"Bearer {api_key}"} client = Client(base_url=base_url, verify=verify_certificate, headers=headers, proxy=proxy) command = demisto.command() if command == "test-module": return_results(test_module(client)) return commands = { "search-appnovi-components": search_appnovi_command, "search-appnovi-component-property": search_appnovi_prop_command, "search-appnovi-connected": search_appnovi_connected_command, "search-appnovi-cve": search_appnovi_cve_servers_command, "search-appnovi-server-by-ip": find_server_by_ip_command, } fn = commands.get(command, None) if fn: return_results(fn(client, demisto.args())) # Log exceptions and return errors except Exception as e: return_error(f"Failed to execute {command} command.\nError:\n{str(e)}") """ ENTRY POINT """ if __name__ in ("__main__", "__builtin__", "builtins"): main()