Skip to content

Commit 6c17e60

Browse files
authored
Merge branch 'main' into new/tailscale
2 parents eaa3f0d + ea2f078 commit 6c17e60

File tree

13 files changed

+1575
-201
lines changed

13 files changed

+1575
-201
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ If you need help setting up a custom integration, you can create an [issue](http
2929
- [Drata](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/drata/)
3030
- [Extreme Networks CloudIQ](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/extreme-cloud-iq/)
3131
- [Ghost Security](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/ghost/)
32+
- [Guardicore Centra](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/akamai_guardicore_centra/)
3233
- [JAMF](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/jamf/)
3334
- [Kandji](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/kandji/)
3435
- [Lima Charlie](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/lima-charlie/)
@@ -39,9 +40,11 @@ If you need help setting up a custom integration, you can create an [issue](http
3940
- [runZero Task Sync](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/task-sync/)
4041
- [Scale Computing](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/scale-computing/)
4142
- [Snipe-IT](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/snipe-it/)
43+
- [Snow License Manager](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/snow_license_manager/)
4244
- [Stairwell](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/stairwell/)
4345
- [Tailscale](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/tailscale/)
4446
- [Tanium](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/tanium/)
47+
- [Ubiquiti Unifi Network](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/ubiquiti-unifi-network/)
4548
## Export from runZero
4649
- [Audit Log to Webhook](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/audit-events-to-webhook/)
4750
- [runZero Vunerability Workflow](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/vulnerability-workflow/)

akamai-guardicore-centra/README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Custom Integration: Akamai Guardicore Centra
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+
## Guardicore Centra requirements
16+
17+
**Instance URL** - The domain or IP of the Guardicore Centra web server e.g. "https://<url of guardicore centra console>" (defined within the starlark script as `CENTRA_BASE_URL`)
18+
19+
`client_id` - login username for authentication to retrieve JWT token (configured in Credentials section of runZero)
20+
21+
`client_secret` - login password for authentication to retrieve JWT token (configured in Credentials section of runZero)
22+
23+
## Guardicore Centra API Docs
24+
25+
- requires customer account
26+
27+
## Steps
28+
29+
### Guardicore Centra configuration
30+
31+
1. Select appropriate script to use.
32+
- Guardicore Centra concurrently supports two different API versions: the v3 API and the v4 API. Thus there is a script named centrav3.star and centrav4.star accordingly.
33+
- A large portion of the data provided by each API overlaps but there are differences between the two.
34+
- Review the script for each API to see if one better suits your use case than the other. Unless there is a specific need to use the centrav3.star script (v3 API) then it is recommended to use the centrav4.star script (v4 API).
35+
2. Determine the proper Guardicore Centra URL:
36+
- Assign the URL to `CENTRA_BASE_URL` within the starlark script
37+
3. Create login credentials with necessary, read-only access to retrieve JWT token for API access:
38+
- Copy the username to the value for `access_key` when creating the Custom Integration credentials in the runZero console (see below)
39+
- Copy the password to the the value for `access_secret` when creating the Custom Integration credentials in the runZero console (see below)
40+
41+
### runZero configuration
42+
43+
1. (OPTIONAL) - make any neccessary changes to the script to align with your environment.
44+
- Modify API calls as needed to filter assets
45+
>- The script is configured to return assets with a status of 'On' and 'Off' by default.
46+
>- Assets with a Status of 'Deleted' are ignored (Centra retains these records indefinitely)
47+
>- If status 'Off" assets are not desired the user can remove the while loop in the get_assets function as indicated by the comment.
48+
>- If a user wants to import all assets, including deleted assets:
49+
>>- Remove the second while loop as above
50+
>>- Remove the "'status': 'on'" parameter from the GET request in the remaining while loop
51+
- Modify datapoints uploaded to runZero as needed
52+
2. [Create the Credential for the Custom Integration](https://console.runzero.com/credentials)
53+
- Select the type `Custom Integration Script Secrets`
54+
- Both `access_key` and `access_secret` are required
55+
- `access_key` corresponds to the Client ID provided when creating the Guardicore Centra Application Registration
56+
- `access_secret` corresponds to the Client secret provided when creating the Guardicore Centra Application Registration
57+
3. [Create the Custom Integration](https://console.runzero.com/custom-integrations/new)
58+
- Add a Name and Icon
59+
- Toggle `Enable custom integration script` to input your finalized script
60+
- Click `Validate` to ensure it has valide syntax
61+
- Click `Save` to create the Custom Integration
62+
4. [Create the Custom Integration task](https://console.runzero.com/ingest/custom/)
63+
- Select the Credential and Custom Integration created in steps 2 and 3
64+
- Update the task schedule to recur at the desired timeframes
65+
- Select the Explorer you'd like the Custom Integration to run from
66+
- Click `Save` to kick off the first task
67+
68+
69+
### What's next?
70+
71+
- You will see the task kick off on the [tasks](https://console.runzero.com/tasks) page like any other integration
72+
- The task will update the existing assets with the data pulled from the Custom Integration source
73+
- The task will create new assets for when there are no existing assets that meet merge criteria (hostname, MAC, etc)
74+
- 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": "Akamai Guardicore Centra", "type": "inbound" }
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
load('runzero.types', 'ImportAsset', 'NetworkInterface')
2+
load('http', http_get='get', http_post='post', 'url_encode')
3+
load('json', json_encode='encode', json_decode='decode')
4+
load('net', 'ip_address')
5+
load('time', 'parse_time')
6+
load('uuid', 'new_uuid')
7+
8+
#Change the URL to match your Guardicore Centra server
9+
CENTRA_BASE_URL = 'https://<Guardicore Centra URL>'
10+
RUNZERO_REDIRECT = 'https://console.runzero.com/'
11+
12+
def build_assets(assets):
13+
assets_import = []
14+
for asset in assets:
15+
asset_id = str(asset.get('id', new_uuid))
16+
agent_info = asset.get('guest_agent_details', {})
17+
hardware = agent_info.get('hardware', {})
18+
os_info = agent_info.get('os_details', {})
19+
hostname = agent_info.get('hostname', '')
20+
os = os_info.get('os_version_name', '')
21+
vendor = hardware.get('vendor', '')
22+
first_seen = asset.get('first_seen', None)
23+
24+
# create the network interfaces
25+
interfaces = []
26+
networks = agent_info.get('network', [])
27+
for network in networks:
28+
ips = [address.get('address', '') for address in network.get('ip_addresses', [])]
29+
interface = build_network_interface(ips=ips, mac=network.get('hardware_address', None))
30+
interfaces.append(interface)
31+
32+
# Retrieve and map custom attributes
33+
active = asset.get('active', '')
34+
agent_last_seen = asset.get('last_guest_agent_details_update', '')
35+
agent_type = agent_info.get('agent_type', '')
36+
agent_version = agent_info.get('agent_version', '')
37+
arch = hardware.get('architecture', '')
38+
bios_uuid = asset.get('bios_uuid', '')
39+
client_cert = agent_info.get('client_cert_ssl_cn_name', '')
40+
comments = asset.get('comments', '')
41+
doc_version = asset.get('doc_version', '')
42+
hw_uuid = hardware.get('hw_uuid', '')
43+
is_on = asset.get('is_on', '')
44+
labels = agent_info.get('labels', [])
45+
last_seen = asset.get('last_seen', '')
46+
kernel_major = os_info.get('os_kernel_major', '')
47+
kernel_minor = os_info.get('os_kernel_minor', '')
48+
os_kernel = str(kernel_major) + '.' + str(kernel_minor)
49+
os_type = os_info.get('os_type', '')
50+
proc_count = os_info.get('num_of_processors', '')
51+
recent_domains = asset.get('recent_domains', [])
52+
serial = hardware.get('serial', '')
53+
status = asset.get('status', '')
54+
vm_id = asset.get('vm_id', '')
55+
vm_name = asset.get('vm_name', '')
56+
57+
custom_attributes = {
58+
'active': active,
59+
'agent.LastSeenTS': agent_last_seen,
60+
'agent.type': agent_type,
61+
'agent.Version': agent_version,
62+
'biosUuid': bios_uuid,
63+
'client_cert': client_cert,
64+
'comments': comments,
65+
'docVersion': doc_version,
66+
'hardware.Arch': arch,
67+
'hardware.SerialNumber': serial,
68+
'hardware.Uuid': hw_uuid,
69+
'isOn': is_on,
70+
'labels': labels,
71+
'firstSeenTS': first_seen
72+
'lastSeenTS': last_seen,
73+
'recentDomains': recent_domains,
74+
'status': status,
75+
'osInfo.fullKernelVersion': os_kernel,
76+
'osInfo.osType': os_type,
77+
'processorCount': proc_count,
78+
'vmId': vm_id,
79+
'vmName': vm_name
80+
}
81+
82+
agent_labels = agent_info.get('labels', [])
83+
for label in agent_labels:
84+
custom_attributes['agent.labels.' + str(agent_labels.index(label))] = label
85+
labels = asset.get('lables', [])
86+
for label in labels:
87+
for k, v in label.items():
88+
custom_attributes['labels.' + str(labels.index(label)) + '.' + k ] = v
89+
metadata = asset.get('metadata', [])
90+
for data in metadata:
91+
custom_attributes['metadata.' + str(metadata.index(data))] = ':'.join(data)
92+
orc_details = asset.get('orchestration_details', [])
93+
for detail in orc_details:
94+
for k, v in detail.items():
95+
custom_attributes['orchestrationDetails.' + str(orc_details.index(detail)) + '.' + k ] = v
96+
agent_supported_features = agent_info.get('supported_features', {}).get('RevealAgent', [])
97+
for feature in agent_supported_features:
98+
custom_attributes['agent.supportedFeatures.' + str(agent_supported_features.index(feature))] = feature
99+
100+
# Build assets for import
101+
assets_import.append(
102+
ImportAsset(
103+
id=asset_id,
104+
manufacturer=vendor,
105+
hostnames=[hostname],
106+
os=os,
107+
networkInterfaces=interfaces,
108+
customAttributes=custom_attributes
109+
)
110+
)
111+
return assets_import
112+
113+
def build_network_interface(ips, mac):
114+
ip4s = []
115+
ip6s = []
116+
for ip in ips[:99]:
117+
ip_addr = ip_address(ip)
118+
if ip_addr.version == 4:
119+
ip4s.append(ip_addr)
120+
elif ip_addr.version == 6:
121+
ip6s.append(ip_addr)
122+
else:
123+
continue
124+
if not mac:
125+
return NetworkInterface(ipv4Addresses=ip4s, ipv6Addresses=ip6s)
126+
else:
127+
return NetworkInterface(macAddress=mac, ipv4Addresses=ip4s, ipv6Addresses=ip6s)
128+
129+
def get_assets(token):
130+
assets_all = []
131+
results_per_page = 1000
132+
start = 0
133+
last_return = 1000
134+
135+
# Return all 'status:on' assets
136+
while True:
137+
url = CENTRA_BASE_URL + '/api/v4.0/assets?'
138+
headers = {'Accept': 'application/json',
139+
'Authorization': 'Bearer ' + token}
140+
params = {'max_results': results_per_page,
141+
'start_at': start,
142+
'status': 'on'}
143+
response = http_get(url, headers=headers, params=params)
144+
if response.status_code != 200:
145+
print('failed to retrieve "on" assets ' + str(start) + ' to ' + str(start + results_per_page), 'status code: ' + str(response.status_code))
146+
break
147+
else:
148+
data = json_decode(response.body)
149+
assets = data['objects']
150+
assets_all.extend(assets)
151+
last_return = len(assets)
152+
start += last_return
153+
if last_return < results_per_page:
154+
start = 0
155+
last_return = 1000
156+
break
157+
# Return all 'status:off' assets. Remove this while loop to restrict import to only status 'on' assets.
158+
while True:
159+
url = CENTRA_BASE_URL + '/api/v3.0/assets?'
160+
headers = {'Accept': 'application/json',
161+
'Authorization': 'Bearer ' + token}
162+
params = {'max_results': results_per_page,
163+
'start_at': start,
164+
'status': 'off'}
165+
response = http_get(url, headers=headers, params=params)
166+
if response.status_code != 200:
167+
print('failed to retrieve "off" assets ' + str(start) + ' to ' + str(start + results_per_page), 'status code: ' + str(response.status_code))
168+
break
169+
else:
170+
data = json_decode(response.body)
171+
assets = data['objects']
172+
assets_all.extend(assets)
173+
last_return = len(assets)
174+
start += last_return
175+
if last_return < results_per_page:
176+
break
177+
178+
return assets_all
179+
180+
def get_token(username, password):
181+
url = CENTRA_BASE_URL + '/api/v3.0/authenticate'
182+
headers = {'Content-Type': 'application/json'}
183+
payload = {'username': username,
184+
'password': password}
185+
186+
response = http_post(url, headers=headers, body=bytes(json_encode(payload)))
187+
if response.status_code != 200:
188+
print('authentication failed: ' + str(response.status_code))
189+
return None
190+
191+
auth_data = json_decode(response.body)
192+
if not auth_data:
193+
print('invalid authentication data')
194+
return None
195+
196+
return auth_data['access_token']
197+
198+
def main(*args, **kwargs):
199+
username = kwargs['access_key']
200+
password = kwargs['access_secret']
201+
token = get_token(username, password)
202+
assets = get_assets(token)
203+
204+
# Format asset list for import into runZero
205+
import_assets = build_assets(assets)
206+
if not import_assets:
207+
print('no assets')
208+
return None
209+
210+
return import_assets

0 commit comments

Comments
 (0)