Skip to content

Commit 3d61b6d

Browse files
authored
Merge branch 'main' into new/scan-passive-assets
2 parents 2a2cb99 + f2bb9d4 commit 3d61b6d

File tree

7 files changed

+197
-3
lines changed

7 files changed

+197
-3
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ If you need help setting up a custom integration, you can create an [issue](http
4242
- [Scale Computing](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/scale-computing/)
4343
- [Snipe-IT](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/snipe-it/)
4444
- [Snow License Manager](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/snow-license-manager/)
45+
- [Solarwinds Information Service](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/solarwinds-information-service/)
4546
- [Stairwell](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/stairwell/)
4647
- [Tailscale](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/tailscale/)
4748
- [Tanium](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/tanium/)

akamai-guardicore-centra/custom-integration-centra-v3-api.star

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def build_assets(assets):
8282
agent_labels = agent_info.get('labels', [])
8383
for label in agent_labels:
8484
custom_attributes['agent.labels.' + str(agent_labels.index(label))] = label
85-
labels = asset.get('lables', [])
85+
labels = asset.get('labels', [])
8686
for label in labels:
8787
for k, v in label.items():
8888
custom_attributes['labels.' + str(labels.index(label)) + '.' + k ] = v

akamai-guardicore-centra/custom-integration-centra-v4-api.star

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ def build_assets(assets, token):
7373
label_mapping[k] = v
7474
name = label_mapping.get(guid, '')
7575
label_names.append(name)
76+
77+
tags = []
78+
for label in label_names:
79+
split_label = label.split(':')
80+
tag = split_label[0] + '=' + split_label[1]
81+
tags.append(tag)
7682

7783

7884
custom_attributes = {
@@ -113,7 +119,8 @@ def build_assets(assets, token):
113119
os=os,
114120
first_seen_ts=first_seen,
115121
networkInterfaces=interfaces,
116-
customAttributes=custom_attributes
122+
customAttributes=custom_attributes,
123+
tags=tags
117124
)
118125
)
119126
return assets_import

docs/integrations.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"lastUpdated": "2026-01-08T20:48:01.893869Z",
2+
"lastUpdated": "2026-01-15T15:30:47.444549Z",
33
"totalIntegrations": 31,
44
"integrationDetails": [
55
{
@@ -182,6 +182,12 @@
182182
"readme": "https://github.com/runZeroInc/runzero-custom-integrations/blob/main/snipe-it/README.md",
183183
"integration": "https://github.com/runZeroInc/runzero-custom-integrations/blob/main/snipe-it/snipeit.star"
184184
},
185+
{
186+
"name": "Solarwinds Information Service",
187+
"type": "inbound",
188+
"readme": "https://github.com/runZeroInc/runzero-custom-integrations/blob/main/solarwinds-information-service/README.md",
189+
"integration": "https://github.com/runZeroInc/runzero-custom-integrations/blob/main/solarwinds-information-service/custom-integration-swis.star"
190+
},
185191
{
186192
"name": "Proxmox",
187193
"type": "inbound",
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Custom Integration: SolarWinds Orion - SolarWinds Information Service (SWIS)
2+
3+
## Getting Started
4+
5+
- Clone this repository
6+
7+
```
8+
git clone https://github.com/runZeroInc/runzero-custom-integrations.git
9+
```
10+
11+
## runZero requirements
12+
13+
- Superuser access to the [Custom Integrations configuration](https://console.runzero.com/custom-integrations) in runZero
14+
15+
## SolarWinds SWIS requirements
16+
17+
**SWIS Instance URL** - The domain or IP of the Snow Atlas web server and the port number e.g. "https://my.solarwinds.instance:17774" (defined within the starlark script as `SWIS_BASE_URL`)
18+
19+
**username** - account username for Solarwinds access (configured in Credentials section of runZero)
20+
21+
**password** - account password (configured in Credentials section of runZero)
22+
23+
## SolarWinds SWIS API Docs
24+
25+
- [SWIS Cortex.Orion.Node Schema Reference](https://solarwinds.github.io/OrionSDK/schema/Cortex.Orion.Node.html)
26+
27+
- [Orion SDK SWIS Docs](https://github.com/solarwinds/OrionSDK/wiki/About-SWIS)
28+
29+
## Steps
30+
31+
### Solarwinds configuration
32+
33+
1. Determine the proper Solarwinds URL and port:
34+
- Common Solarwinds ports are 17774 (default since v2024) and 17778 (the default in v2023 and prior).
35+
- Assign the URL to `SWIS_BASE_URL` within the starlark script
36+
- Determine proper username and password credentials for access. These will be configured in the Custom Integration credentials section within the runZero console.
37+
38+
### runZero configuration
39+
40+
1. (Make any neccessary changes to the script to align with your environment.
41+
- Modify API calls as needed to filter assets
42+
>- Determine the proper SWQL query needed to return the data set to import to runZero
43+
>- Add this query to the integration script in the 'params' variable within the 'get_assets' function.
44+
- Modify attribute mapping based on the data returned by the SWQL query as needed
45+
>- The integration script outlines some common example attributes that could be brought in from Solarwinds but the attributes that are actually retrieved will be determined by the SWQL query passed in the API call params
46+
>- Modify the asset attributes and custom attributes to match the data provided by the SWQL query following the pattern outlined in the script
47+
>- For a list of "core" attributes that runZero maps, reference the Custom SDK documentation [here](https://runzeroinc.github.io/runzero-sdk-py/autoapi/runzero/types/_data_models_gen/index.html#runzero.types._data_models_gen.ImportAsset). All other attributes provided by Solarwinds should be mapped within 'Custom Attributes'
48+
2. [Create the Credential for the Custom Integration](https://console.runzero.com/credentials)
49+
- Select the type **Custom Integration Script Secrets**
50+
- Both **access_key** and **access_secret** are required
51+
- **access_key** corresponds to the username to access Solarwinds
52+
- **access_secret** corresponds to the password to access Solarwinds
53+
3. [Create the Custom Integration](https://console.runzero.com/custom-integrations/new)
54+
- Add a Name (e.g. solarwinds) and Icon
55+
- Toggle **Enable custom integration script** to input your finalized script
56+
- Click **Validate** to ensure it has valide syntax
57+
- Click **Save** to create the Custom Integration
58+
4. [Create the Custom Integration task](https://console.runzero.com/ingest/custom/)
59+
- Select the Credential and Custom Integration created in steps 2 and 3
60+
- Update the task schedule to recur at the desired timeframes
61+
- Select the Explorer you'd like the Custom Integration to run from
62+
- Click **Save** to start the task
63+
64+
65+
### What's next?
66+
67+
- You will see the task initilize on the [tasks](https://console.runzero.com/tasks) page like other integration tasks
68+
- The task will update the existing assets with the data pulled from the Custom Integration source
69+
- The task will create new assets for when there are no existing assets that meet merge criteria (hostname, MAC, IP, etc)
70+
- You can search for assets enriched by this custom integration with the runZero search `custom_integration:<INSERT_NAME_HERE>`
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ "name": "Solarwinds Information Service", "type": "inbound" }
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
load('runzero.types', 'ImportAsset', 'NetworkInterface')
2+
load('base64', base64_encode='encode', base64_decode='decode')
3+
load('http', http_get='get', http_post='post', 'url_encode')
4+
load('json', json_encode='encode', json_decode='decode')
5+
load('net', 'ip_address')
6+
load('uuid', 'new_uuid')
7+
8+
SWIS_BASE_URL = 'https://localhost:17774'
9+
RUNZERO_REDIRECT = 'https://console.runzero.com/'
10+
11+
def build_assets(assets):
12+
assets_import = []
13+
for asset in assets:
14+
asset_id = str(asset.get('NodeId', str(new_uuid)))
15+
hostname = asset.get('Fqdn', '')
16+
os = asset.get('OsVersion', '')
17+
vendor = asset.get('Vendor', '')
18+
19+
# create the network interfaces
20+
interfaces = []
21+
addresses = asset.get('IpAddress', [])
22+
interface = build_network_interface(ips=[addresses], mac=None)
23+
interfaces.append(interface)
24+
25+
# Retrieve and map custom attributes
26+
cpu_util = str(asset.get('CpuPercentUtilization', ''))
27+
discovery_profile_id = str(asset.get('DiscoveryProfileId', ''))
28+
mem_util_perc = str(asset.get('PercentMemoryUsed', ''))
29+
mem_util = str(asset.get('MemoryUsed', ''))
30+
pollers = asset.get('Pollers', '')
31+
response_time = str(asset.get('ResponseTime', ''))
32+
snmp_port = str(asset.get('SnmpPort', ''))
33+
snmp_version = str(asset.get('SnmpVersion', ''))
34+
status = asset.get('Status', '')
35+
sys_object_id = asset.get('SysObjectId', '')
36+
uptime = str(asset.get('Uptime', ''))
37+
38+
custom_attributes = {
39+
'percentCpuUtilization': cpu_util,
40+
'discoveryProfileId': discovery_profile_id,
41+
'percentMemoryUtilization': mem_util_perc,
42+
'memoryUtilized': mem_util,
43+
'pollers': pollers,
44+
'responseTime': response_time,
45+
'snmp.port': snmp_port,
46+
'snmp.version': snmp_version,
47+
'status': status,
48+
'sysObjectId': sys_object_id,
49+
'uptime': uptime
50+
}
51+
52+
# Build assets for import
53+
assets_import.append(
54+
ImportAsset(
55+
id=asset_id,
56+
hostnames=[hostname],
57+
os=os,
58+
manufacturer=vendor,
59+
networkInterfaces=interfaces,
60+
customAttributes=custom_attributes
61+
)
62+
)
63+
return assets_import
64+
65+
def build_network_interface(ips, mac):
66+
ip4s = []
67+
ip6s = []
68+
for ip in ips[:99]:
69+
ip_addr = ip_address(ip)
70+
if ip_addr.version == 4:
71+
ip4s.append(ip_addr)
72+
elif ip_addr.version == 6:
73+
ip6s.append(ip_addr)
74+
else:
75+
continue
76+
if not mac:
77+
return NetworkInterface(ipv4Addresses=ip4s, ipv6Addresses=ip6s)
78+
else:
79+
return NetworkInterface(macAddress=mac, ipv4Addresses=ip4s, ipv6Addresses=ip6s)
80+
81+
def get_assets(creds):
82+
83+
url = SWIS_BASE_URL + 'SolarWinds/InformationService/v3/Json/Query?'
84+
headers = {'Accept': 'application/json',
85+
'Authorization': 'Basic ' + creds}
86+
# Populate the SWQL query to return desired assets and attributes in the params query value e.g.
87+
# params = {'query': 'SELECT N.NodeID, N.OsVersion, N.Fqdn, N.Vendor, N.IPAddress, N.CpuPercentUtilization, N.DiscoveryProfileId, N.PercentMemoryUsed, N.MemoryUsed, N.Pollers, N.responseTime, N.snmp.port, N.snmp.version, N.status, N.sysObjectId, N.Uptime FROM Orion.Nodes'}
88+
params = {'query': ''}
89+
response = http_get(url, headers=headers, params=params)
90+
if response.status_code != 200:
91+
print('failed to retrieve assets', 'status code: ' + str(response.status_code))
92+
data = json_decode(response.body)
93+
assets_all.extend(data)
94+
95+
return assets_all
96+
97+
def main(*args, **kwargs):
98+
username = kwargs['access_key']
99+
password = kwargs['access_secret']
100+
b64_creds = base64_encode(username + ":" + password)
101+
assets = get_assets(b64_creds)
102+
103+
# Format asset list for import into runZero
104+
import_assets = build_assets(assets)
105+
if not import_assets:
106+
print('no assets')
107+
return None
108+
109+
return import_assets

0 commit comments

Comments
 (0)