My First Custom Integration Command
LAST UPDATED: DEC 23, 2024
Users can develop their own integration commands and utility commands, using Python scripts or playbook-driven workflows. For simplicity and ease of onboarding, the focus of this article will be on building a Python integration command for the VirusTotal v3 integration.
Developing a Python Custom Command
Navigate to the VirusTotal v3 integration.
Click on the Configuration navigational link.
Click on the (Integrations) module.
Input VirusTotal v3 in the search field, then press the Enter key.
Select the integration.
Click on the + Custom Command button.
Configure and create the command.
Enter a unique command name.
Click on the + Add button.
READER NOTE
The text below the custom command name is its internal name, automatically generated from the user’s input. It is this internal name that is used as the command’s Python function name. D3 recommends using this internal name as the input parameter name for easier code matching.
Replace the auto-generated custom command stub function with the following code:
import requests
import json
# Retrieves the server URL, API version and API key from the runtime.
serverUrl = runtime["connector"].get("serverurl").strip("/")
version = runtime["connector"].get("version").strip()
apikey = runtime["connector"].get("apikey").strip()
# Constructs the base URL for API requests using the server URL and version.
BASE_URL = serverUrl + "/api/" + version
# Defines headers for the API requests.
HEADERS = {
"x-apikey": apikey
}
def isJson(inputString):
"""
Checks if the given string is in JSON format.
:param inputString: The string to verify.
:return: True if valid JSON, False otherwise.
"""
try:
json.loads(inputString) # Tries to parse the input string as JSON.
return True # Returns True if the input string is a valid JSON.
except json.JSONDecodeError:
return False # Returns False if parsing fails, indicating the input is not a valid JSON.
def Getdomainreport(*args):
"""
Fetches the domain report for the provided domain name.
:param args: Domain name (required).
:return: Command output model.
"""
errors = [] # Initializes a list to store error messages encountered during execution.
returnData = "Successful" # Sets "Successful" as the default return status.
context = [] # Initializes an empty list to hold any additional context data.
raw = {
"Results": [], # Stores the original JSON response.
"D3Errors": [] # Stores error messages if any errors are encountered.
}
keyFields = {} # Initializes key fields that might be required for subsequent commands.
resultData = {} # Initializes result data to store relevant extracted fields.
try:
if not args: # Checks if no arguments (i.e., domain names) are provided.
errors.append("Domain is required.") # Appends an error message indicating that a domain name is required.
else:
actionUrl = f"/domains/{args[0].strip()}" # Constructs the specific API endpoint URL for the domain report using the provided domain name.
httpMethod = "GET" # Sets the HTTP method used for this request.
response = requests.request(httpMethod, f"{BASE_URL}{actionUrl}", headers=HEADERS, verify=False) # Sends the HTTP request with the specified method, URL, headers, and query parameters.
if not response.ok: # Checks if the response status code indicates failure (outside the 200–299 range).
originalResponse = response.json() if isJson(response.text) else response.text # Assigns the Python-converted JSON to originalResponse if the response is valid JSON, else assigns the raw text.
raw["D3Errors"].append({ # Appends error information to the raw "D3Errors" list for debugging.
"FailedAction": f"Failed to get domain report for domain ({args[0]}).", # Describes the failed action.
"StatusCode": response.status_code, # Logs the HTTP status code of the failed request.
"Reason": response.reason, # Logs the reason phrase returned by the server.
"Message": originalResponse # Logs the original response message or JSON.
})
else: # Executes if the response status code indicates success (within the 200–299 range).
responseJson = response.json() # Assigns the Python-converted JSON to responseJson.
attributes = responseJson.get("data", {}).get("attributes", {}) # Assigns a value associated with a key (at a certain level of nesting) from the response to attributes.
raw["Results"].append(responseJson) # Appends the full response JSON to the "Results" list.
resultData = { # Extracts relevant fields for the result data.
"domain": args[0], # Sets the domain name provided in the arguments.
"reputation": attributes.get("reputation", ""), # Sets the domain reputation value.
"suspicious": attributes.get("last_analysis_stats", {}).get("suspicious", ""), # Sets the number of suspicious findings.
"malicious": attributes.get("last_analysis_stats", {}).get("malicious", "") # Sets the number of malicious findings.
}
except Exception as err:
errors.extend(err.args) # Extends the errors list with the details of the exception.
if errors or len(raw["D3Errors"]) > 0: # Checks if any errors are encountered or if there are logged "D3Errors."
returnData = "Failed" # Sets the return status to "Failed" if any errors occur.
errors.extend(raw["D3Errors"]) # Appends the logged "D3Errors" to the error list.
# Returns the command output model with all the gathered information.
return pb.returnOutputModel(
resultData,
returnData,
keyFields,
context,
raw,
errors
)
READER NOTE
The sample code above fetches and extracts a domain report for a single domain from an external API and returns the results in D3’s format.
See Domain objects information at: https://docs.virustotal.com/reference/domains-object.
Add a Domain input parameter.
Navigate to the Overview tab.
Click on the Inputs tab.
Click on the + New Input Parameter button.
Input the parameter details, then click on the + Add button.
Set the Parameter Name to Getdomainreport
Set the Display Name to Domain
Set the Parameter Type to Text
Set the Is Required? field to Yes
Set the Description to The domain to analyze
Set the Sample Data to xmr.pool.minergate.com
Verify that the Domain parameter has been added, then click on the button.
Test the command.
Create a new connection or select an existing one. See the integration’s documentation for details.
Paste in the domain from the sample data in step 6, or enter a different domain.
Click on the Test Command button, then observe the test results.
Enable this command for use in specific areas within the D3 platform.
For now, ensure the following options are selected:
Command Task
Conditional Task
Ad-hoc Command
Click on the Submit button.
Click on the Submit button within the Submit Command popup.
The custom command is now live and ready to be used.