Build custom connectors with the SDK
The Surface Command Software Development Kit (SDK), surcom-sdk, provides tools to develop Surface Command data connectors for the Rapid7 Command Platform. To learn more about connectors, see Connectors. To learn more about Surface Command and Attack Surface Management, see Get started with Attack Surface Management.
Prerequisites
- Python version 3.11 or greater
- Using a virtual environment is recommended
- Docker fully configured and running
- Permissions to access Attack Surface Management capabilities and create an API key in the Command Platform
- Familiarity with the Attack Surface Management type system
- Optional:
- Integrated Developer Environment (IDE)
- Example: Microsoft Visual Studio Code
- 1Password
- Integrated Developer Environment (IDE)
Install and configure the SDK
Complete the following tasks to set up the SDK properly:
Task 1: Create an API Key
Generate a new user API key in the Command Platform and copy the key before closing the window. You can save the key as an API Credential in 1Password (recommended) so it can be accessed using a 1Password Secret Reference . Alternatively, you can save it as plain text (not recommended).
Task 2: Configure your local environment
-
Open a terminal.
-
Create a
developmentdirectory to house your SDK development files:cd ~ mkdir development cd development -
Clone the Surface Command Connectors repository :
git clone https://github.com/rapid7/r7-surcom-connectors -
Create a Python virtual environment:
python -m venv sdk-dev-env source sdk-dev-env/bin/activate -
Install the SDK:
python -m pip install r7-surcom-sdk
Task 3: Configure the SDK
-
Optionally, turn on tab auto complete:
surcom config enable-tabs -
Initialize the SDK configuration:
surcom config init -
Enter the path to your Connectors Workspace:
Choose the appropriate directory
The Connectors repository has directories for different developer types (for example, rapid7, partners). Externally-developed Connectors should go in the partners directory.
> ~/development/r7-surcom-connectors/connectors/partners-
Enter the URL for the Rapid7 Surface Command Platform (default:
us.surface.insight.rapid7.com):> us2.surface.insight.rapid7.com -
Enter a name for the connection (normally the name of your Organization). Connection name should follow the format:
<region>-<staging|prod|poc>-<companyname>(for example,eu-prod-examplecorp):> us-staging-rapid7 -
Enter the API Key. Rapid7 recommends using a 1Password Secret Reference. See the 1Password documentation for details. Plain text API keys are not recommended as they are saved directly to the configuration file.
-
Validate the configuration:
surcom config test
This creates a surcom_config file at ~/.r7-surcom-sdk/surcom_config with your initial connection configuration.
SDK command-line interface (CLI) reference
This section provides reference information for the surcom-sdk command-line interface (CLI). It includes usage syntax, available commands, options, and examples.
Command syntax and tree
usage:
$ surcom <command> ...
$ surcom -v <command> ...
$ surcom --version
options:
-h, --help show this help message and exit
-v, --verbose Set the log level to DEBUG
--version Print the version of the surcom SDK and exit
commands:
config Configure the surcom-sdk
connector Develop Connectors for the Rapid7 Surface Command Platform
type Manage Surface Command Types
data Interact with Surface Command Dataconfig
Configures the SDK.
Usage:
surcom config <command> [options]Commands:
| Command | Description | Options |
|---|---|---|
init | Creates an initial configuration file if none exists | None |
add | Adds a connection to the configuration file | None |
test | Tests the SDK configuration file | None |
delete | Deletes a connection from the configuration file |
|
list | Lists all configured connections | None |
set-active | Sets the active connection for testing |
|
enable-tabs | Enable tab auto-completion when using the SDK | None |
connector
Builds Connectors.
Usage:
surcom connector <command> [options]Commands:
| Command | Description | Options |
|---|---|---|
init | Initializes a new connector | None |
codegen | Generates basic code for a new connector |
|
invoke | Invokes a connector |
|
package | Packages a connector for submission |
|
validate | Validates a connector as it is configured |
|
type
Manages Surface Command types. To learn more about the Surface Command type system, see Explore the Attack Surface Management type system.
Usage:
surcom type <command> [options]Commands:
| Command | Description | Options |
|---|---|---|
generate | Generates a type definition |
|
install | Installs a type on your Surface Command instance |
|
data
Enables interaction with Attack Surface Management data.
Usage:
surcom data <command> [options]Commands:
| Command | Description | Options |
|---|---|---|
import | Imports type data into your Surface Command instance |
|
Build an example connector
The following sections build an example connector that fetches data from the MockServer that is loaded with the SDK.
Want to see the example before getting started?
You can see the full example connector in the following directory: r7-surcom-connectors/connectors/rapid7/demo_connector
Task 1: Initialize the connector
-
Open a terminal.
-
Go to your connectors directory:
cd ~/development/r7-surcom-connectors/connectors/partners -
Initialize the connector:
surcom connector init -
Enter a display name (for example,
Demo Connector):> Demo Connector -
Enter a connector ID (for example,
demo.connector):> demo.connector -
Enter the author (for example,
username):> username
A template connector specification file (connector.spec.yaml) is generated in your workspace (defined by the configuration file).
Task 2: Modify the connector specification file
-
Open the connector specification file (
connector.spec.yaml) in the IDE of your choice. Rapid7 recommends using Microsoft Visual Studio Code. -
Replace the template types:
types: - DemoConnectorDevice - DemoConnectorUser -
Update the
import_allfunction:- id: import_all title: Import All description: Import all devices and users from Demo Connector return_types: - DemoConnectorDevice - DemoConnectorUser -
Replace the
settingsblock:settings: url: type: string title: URL description: >- Base URL of where the MockServer is hosted when you run it using the surcom-sdk. example: http://host.docker.internal:1080 default: http://host.docker.internal:1080 api_key: type: string title: API Key description: >- API key for the MockServer that is provided in the instructions. for this tutorial. format: password verify_tls: type: boolean title: Verify TLS? description: If enabled, verify the server's identity. default: true page_limit: type: integer title: Page Limit description: Maximum number of items to return per page. enum: - 2 - 4 - 6 default: 6 # This is an optional setting that can be used to control pagination. total_pages: type: integer title: Total Pages description: >- Total number of pages to retrieve. Set it to 0 to retrieve all pages. default: 2 nullable: true
Task 3: Generate a basic connector structure
-
Open a terminal.
-
Go to the new connector directory:
cd ~/development/r7-surcom-connectors/connectors/partners/demo_connector -
Generate basic template code for your connector:
surcom connector codegen -
Confirm file structure for the connector has been created.
Task 4: Add connector functions
Functions are located in demo_connector/functions. For this example, you need to complete the following updates:
Update functions/helpers.py with new get_devices, get_users, and get_permissions methods and add a REQUIRED_PERMISSIONS list:
REQUIRED_PERMISSIONS = [
"readUsers",
"readDevices"
]
# Here is an example of a simple client that interacts with a third-party API.
class DemoConnectorClient():
def __init__(
self,
user_log: Logger,
settings: Settings
):
# Expose the logger to the client
self.logger = user_log
# Expose the Connector Settings to the client
self.settings = settings
# Get the URL from the settings and ensure it is properly formatted
self.base_url = settings.get("url").strip().rstrip("/")
# Setup a Session using the Surcom HttpSession class
self.session = HttpSession()
# Use the value of our `verify_tls` setting to determine if we should verify TLS
self.session.verify = settings.get("verify_tls")
# Here we update the session header with the API Key from the Connector Settings.
# Authentication methods will vary based on the third-party API. Refer to the specific
# API documentation for details.
self.session.headers.update({
"x-api-key": settings.get("api_key")
})
def get_devices(
self,
page_number: int = -1,
):
url = f"{self.base_url}/api/devices"
params = {
"page": page_number,
"items_per_page": self.settings.get("page_limit", -1)
}
self.logger.debug("Requesting devices from '%s' with params: %s", url, params)
r = self.session.get(url, params=params)
r.raise_for_status()
return r.json()
def get_permissions(self):
url = f"{self.base_url}/api/permissions"
self.logger.debug("Calling permissions endpoint '%s'", url)
r = self.session.get(url)
r.raise_for_status()
return r.json()
def get_users(
self,
page_number: int = -1,
):
url = f"{self.base_url}/api/users"
params = {
"page": page_number,
"items_per_page": self.settings.get("page_limit", -1)
}
self.logger.debug("Requesting users from '%s' with params: %s", url, params)
r = self.session.get(url, params=params)
r.raise_for_status()
return r.json()
Update functions/fn_import_all.py with:
from logging import Logger
from typing import Callable
from . import helpers
from .sc_settings import Settings
from .sc_types import DemoConnectorDevice, DemoConnectorUser
def _get_asset(
user_log: Logger,
settings: Settings,
api_method: Callable,
surcom_type: dict
):
user_log.info("Getting '%s' from '%s'", surcom_type.__name__, settings.get("url"))
# We set and keep track of the current page number
current_page = 1
while True:
# Get some assets from the client
r = api_method(current_page)
# If we don't have a response or the response doesn't contain data, we log a warning and break
if not r or not r.get("data") or not r.get("page"):
user_log.warning("No data found in response for page %d. Response: %s", current_page, r)
break
data = r.get("data", [])
if data:
user_log.info("Processing %d items from page %d", len(data), current_page)
# For each asset in the response, yield a Surcom type to ingest
for asset in data:
# Ensure the ID is a string
if asset.get("id"):
asset["id"] = str(asset["id"])
yield surcom_type(asset)
# We check if we have reached the last page
if r.get("page") == r.get("total_pages"):
user_log.debug("Reached the last page: %d", current_page)
break
# If the total pages setting is > 0, we check if we have reached it
elif settings.get("total_pages") and r.get("page") >= settings.get("total_pages"):
user_log.info("Not getting any more pages")
break
# Else, we increment the page number and continue
next_page = current_page + 1
user_log.debug("Moving to the next page: %d", next_page)
current_page = next_page
def import_all(
user_log: Logger,
settings: Settings
):
# Instantiate the DemoConnectorClient
client = helpers.DemoConnectorClient(user_log, settings)
# Import all DemoConnectorDevice
yield from _get_asset(
user_log=user_log,
settings=settings,
api_method=client.get_devices,
surcom_type=DemoConnectorDevice
)
# Import all DemoConnectorUser
yield from _get_asset(
user_log=user_log,
settings=settings,
api_method=client.get_users,
surcom_type=DemoConnectorUser
)
Task 5: Update your configuration, create a type, and import data
Have 1Password?
For this tutorial we use the API Key surcom-demo-connector-api-key. If you have 1Password, we recommend saving the key as an API Credential in 1Password so it can be accessed using a 1Password Secret Reference . Alternatively, you can use it as plain text in the configuration file (not recommended).
-
Use the SDK to start MockServer and load the tutorial:
surcom mockserver load-tutorial -
Invoke your connector function:
surcom connector invoke -f import_all -
Using the
invokecommand for the first time prompts you for the following settings:url: Accept the default (http://host.docker.internal:1080).api_key: Enter the 1Password Secret Reference for your API key (recommended) or entersurcom-demo-connector-api-keyas a plain text string (not recommended).verify_tls: Accept the default (True).page_limit: Accept the default (6).total_pages: Set to2.
This creates a new section (
[connector.demo.connector]) in the configuration file (default:~/.r7-surcom-sdk/surcom_config). You can update this file or use the CLI to overwrite the settings. -
If you used a 1Password Secret Reference, authorize 1Password. After successful authorization, the output for the command is written to the
build/outputdirectory and a terminal message confirms this. -
Create a custom type:
surcom type generate build/output/DemoConnectorDevice.json -
Import connector data into Surface Command:
surcom connector invoke -f import_all --import-data -
Confirm the operation.
-
Verify the type definition is correctly imported into Surface Command by opening the Workspace and running the following query:
MATCH (d:DemoConnectorDevice) RETURN d -
Now implement the
testfunction infunctions/fn_test.pyby following the source code in our repository . This function is invoked when you click theTest Connectionbutton in the Surface Command UI. -
Validate the connector and fix any critical issues.
surcom connector validate
Troubleshoot your connector
How can I debug my connector?
The Surface Command SDK also has optional debug functionality.
To install and configure the SDK debugger using Microsoft Visual Studio Code:
-
Open Microsoft Visual Studio Code and open an integrated terminal.
-
Install the extra debugger dependencies:
pip install 'r7-surcom-sdk[debug]' -
Open
.vscode/launch.jsonand copy and paste the example launch configuration. -
In the duplicate example launch configuration:
- Update
namewith the name of your connector. - Update the
pathMappings.localRootto the path of your connector’sfunctionscode.
- Update
-
Save
.vscode/launch.json. -
In the terminal, change directory to your connector:
cd ~/r7-surcom-connectors/connectors/partners/<connector-name> -
Run:
surcom connector invoke -f import_all --debug -
In the code window, add a breakpoint and when you see the terminal return
Debug mode enabled, waiting for debugger to attach, launch the debugger in Microsoft Visual Studio Code.
How can I turn on tab completion?
To turn on tab completion:
-
Run:
surcom config enable-tabs -
Restart your shell.
How do I find the surcom_config file?
surcom_config file?To find the location of the surcom-config file:
-
Run:
surcom config init
Configuration file hidden
The configuration file is hidden and prefixed with a .
How do I manage configuration file settings?
You can comment out a setting in the configuration file by prefixing with a -. For example, to ignore the total_pages setting:
[connector.demo.connector]
url = http://host.docker.internal:1080
api_key = "op://Employee/DEMO/SURCOM DEMO API KEY"
verify_tls = True
page_limit = 6
-total_pages = 2How do I turn on verbose logging?
To turn on verbose logging:
-
Add the
-vflag command. For example:surcom -v connector invoke -f import_all --import-data
How do I preview my documentation?
Microsoft Visual Studio Code has a built-in preview feature for Markdown files.
To preview your connector’s documentation:
- Open the
docs/INSTRUCTIONS.mdfile in Microsoft Visual Studio Code. - Right-click the file’s name in the file explorer.
- Click Open Preview.
How do I run a function locally?
Use the invoke command with the -f flag and its ID:
surcom connector invoke -f testHow do I overwrite the settings from the CLI?
Use the invoke command to pass any settings and overwrite values in the surcom_config file. For example:
surcom connector invoke -f import_all -s page_limit=4,api_key=xxx