From cca53098daba15f7e43b9244bd891efa2298c83b Mon Sep 17 00:00:00 2001 From: TechnoSavage Date: Wed, 22 Oct 2025 10:53:01 -0400 Subject: [PATCH 1/4] Initial commit for Akamai Guardicore Centra integration. --- akamai_guardicore_centra/README.md | 70 +++++++ akamai_guardicore_centra/config.json | 1 + .../guardicorecentra.star | 190 ++++++++++++++++++ 3 files changed, 261 insertions(+) create mode 100644 akamai_guardicore_centra/README.md create mode 100644 akamai_guardicore_centra/config.json create mode 100644 akamai_guardicore_centra/guardicorecentra.star diff --git a/akamai_guardicore_centra/README.md b/akamai_guardicore_centra/README.md new file mode 100644 index 0000000..90e2426 --- /dev/null +++ b/akamai_guardicore_centra/README.md @@ -0,0 +1,70 @@ +# Custom Integration: Akamai Guardicore Centra + +## Getting Started + +- Clone this repository + +``` +git clone https://github.com/TechnoSavage/runZero.git +``` + +## runZero requirements + +- Superuser access to the [Custom Integrations configuration](https://console.runzero.com/custom-integrations) in runZero + +## Guardicore Centra requirements + +**Instance URL** - The domain or IP of the Guardicore Centra web server e.g. "https://" (defined within the starlark script as `CENTRA_BASE_URL`) + +`client_id` - login username for authentication to retrieve JWT token (configured in Credentials section of runZero) + +`client_secret` - login password for authentication to retrieve JWT token (configured in Credentials section of runZero) + +## Guardicore Centra API Docs + +- requires customer account + +## Steps + +### Guardicore Centra configuration + +1. Determine the proper Guardicore Centra URL: + - Assign the URL to `CENTRA_BASE_URL` within the starlark script +2. Create login credentials with necessary, read-only access to retrieve JWT token for API access: + - Copy the username to the value for `access_key` when creating the Custom Integration credentials in the runZero console (see below) + - Copy the password to the the value for `access_secret` when creating the Custom Integration credentials in the runZero console (see below) + +### runZero configuration + +1. (OPTIONAL) - make any neccessary changes to the script to align with your environment. + - Modify API calls as needed to filter assets + >- The script is configured to return assets with a status of 'On' and 'Off' by default. + >- Assets with a Status of 'Deleted' are ignored (Centra retains these records indefinitely) + >- If status 'Off" assets are not desired the user can remove the while loop in the get_assets function as indicated by the comment. + >- If a user wants to import all assets, including deleted assets: + >>- Remove the second while loop as above + >>- Remove the "'status': 'on'" parameter from the GET request in the remaining while loop + - Modify datapoints uploaded to runZero as needed +2. [Create the Credential for the Custom Integration](https://console.runzero.com/credentials) + - Select the type `Custom Integration Script Secrets` + - Both `access_key` and `access_secret` are required + - `access_key` corresponds to the Client ID provided when creating the Guardicore Centra Application Registration + - `access_secret` corresponds to the Client secret provided when creating the Guardicore Centra Application Registration +3. [Create the Custom Integration](https://console.runzero.com/custom-integrations/new) + - Add a Name and Icon + - Toggle `Enable custom integration script` to input your finalized script + - Click `Validate` to ensure it has valide syntax + - Click `Save` to create the Custom Integration +4. [Create the Custom Integration task](https://console.runzero.com/ingest/custom/) + - Select the Credential and Custom Integration created in steps 2 and 3 + - Update the task schedule to recur at the desired timeframes + - Select the Explorer you'd like the Custom Integration to run from + - Click `Save` to kick off the first task + + +### What's next? + +- You will see the task kick off on the [tasks](https://console.runzero.com/tasks) page like any other integration +- The task will update the existing assets with the data pulled from the Custom Integration source +- The task will create new assets for when there are no existing assets that meet merge criteria (hostname, MAC, etc) +- You can search for assets enriched by this custom integration with the runZero search `custom_integration:` \ No newline at end of file diff --git a/akamai_guardicore_centra/config.json b/akamai_guardicore_centra/config.json new file mode 100644 index 0000000..d70654b --- /dev/null +++ b/akamai_guardicore_centra/config.json @@ -0,0 +1 @@ +{ "name": "Guardicore Centra", "type": "inbound" } \ No newline at end of file diff --git a/akamai_guardicore_centra/guardicorecentra.star b/akamai_guardicore_centra/guardicorecentra.star new file mode 100644 index 0000000..b5bfcca --- /dev/null +++ b/akamai_guardicore_centra/guardicorecentra.star @@ -0,0 +1,190 @@ +load('runzero.types', 'ImportAsset', 'NetworkInterface') +load('http', http_get='get', http_post='post', 'url_encode') +load('json', json_encode='encode', json_decode='decode') +load('net', 'ip_address') +load('time', 'parse_time') +load('uuid', 'new_uuid') + +#Change the URL to match your Guardicore Centra server +CENTRA_BASE_URL = 'https://' +RUNZERO_REDIRECT = 'https://console.runzero.com/' + +def build_assets(assets): + assets_import = [] + for asset in assets: + asset_id = str(asset.get('id', new_uuid)) + hostname = asset.get('name', '') + os_info = asset.get('os_info', {}) + os = os_info.get('type', '') + first_seen = asset.get('first_seen', '') + #reformat first_seen timestamp for runZero parsing + if first_seen != '': + trim_decimal = first_seen.split('.') + split_space = trim_decimal[0] + reformat = split_space[0] + 'T' + split_space[1] + 'Z' + first_seen = parse_time(reformat) + + # create the network interfaces + interfaces = [] + nics = asset.get('nics', []) + for nic in nics: + addresses = nic.get('ip_addresses', []) + interface = build_network_interface(ips=addresses, mac=nic.get('mac_address', None)) + interfaces.append(interface) + + # Retrieve and map custom attributes + asset_type = asset.get('asset_type', '') + os_kernel = os_info.get('full_kernel_version', '') + bios_uuid = asset.get('bios_uuid', '') + scoping_details = asset.get('scoping_details', {}).get('worksite', {}) + worksite_mod = scoping_details.get('modified', '') + worksite_name = scoping_details.get('name', '') + last_seen = asset.get('last_seen', '') + mssp_tenant = asset.get('mssp_tenant_name', '') + status = asset.get('status', '') + instance_id = asset.get('instance_is', '') + agent_info = asset.get('agent', {}) + agent_last_seen = agent_info.get('agent_last_seen', '') + agent_version = agent_info.get('agent_version', '') + comments = asset.get('comments', '') + orchestration_metadata = asset.get('orchestration_metadata', {}) + orc_asset_type = orchestration_metadata.get('asset_type', '') + orc_dev_name = orchestration_metadata.get('f5_device_hostname', '') + orc_partition = orchestration_metadata.get('partition', '') + orc_vs_name = orchestration_metadata.get('vs_name', '') + + custom_attributes = { + 'assetType': asset_type, + 'osInfo.fullKernelVersion': os_kernel, + 'biosUuid': bios_uuid, + 'scopingDetails.worksite.modified': worksite_mod, + 'scopingDetails.worksite.name': worksite_name, + 'lastSeenTS': last_seen, + 'msspTenantName': mssp_tenant, + 'status': status, + 'instanceId': instance_id, + 'agentLastSeenTS': agent_last_seen, + 'agentVersion': agent_version, + 'orchestrationMetadata.assetType': orc_asset_type, + 'orchestrationMetadata.f5DeviceHostname': orc_dev_name, + 'orchestrationMetadata.partition': orc_partition, + 'orchestrationMetadata.vsName': orc_vs_name + } + + ## Additional custom attributes to implement: + # label_groups = asset.get('label_groups', []) + # orchestration_details = asset.get('orchestration_details', []) + # labels = asset.get('labels', []) + + + # Build assets for import + assets_import.append( + ImportAsset( + id=asset_id, + hostnames=[hostname], + os=os, + first_seen_ts=first_seen, + networkInterfaces=interfaces, + customAttributes=custom_attributes + ) + ) + return assets_import + +def build_network_interface(ips, mac): + ip4s = [] + ip6s = [] + for ip in ips[:99]: + ip_addr = ip_address(ip) + if ip_addr.version == 4: + ip4s.append(ip_addr) + elif ip_addr.version == 6: + ip6s.append(ip_addr) + else: + continue + if not mac: + return NetworkInterface(ipv4Addresses=ip4s, ipv6Addresses=ip6s) + else: + return NetworkInterface(macAddress=mac, ipv4Addresses=ip4s, ipv6Addresses=ip6s) + +def get_assets(token): + assets_all = [] + results_per_page = 1000 + start = 0 + last_return = 1000 + + # Return all 'status:on' assets + while True: + url = CENTRA_BASE_URL + '/api/v4.0/assets?' + headers = {'Accept': 'application/json', + 'Authorization': 'Bearer ' + token} + params = {'max_results': results_per_page, + 'start_at': start, + 'status': 'on'} + response = http_get(url, headers=headers, params=params) + if response.status_code != 200: + print('failed to retrieve "on" assets ' + str(start) + ' to ' + str(start + results_per_page), 'status code: ' + str(response.status_code)) + break + else: + data = json_decode(response.body) + assets = data['objects'] + assets_all.extend(assets) + last_return = len(assets) + start += last_return + if last_return < results_per_page: + start = 0 + last_return = 1000 + break + # Return all 'status:off' assets. Remove this while loop to restrict import to only status 'on' assets. + while True: + url = CENTRA_BASE_URL + '/api/v4.0/assets?' + headers = {'Accept': 'application/json', + 'Authorization': 'Bearer ' + token} + params = {'max_results': results_per_page, + 'start_at': start, + 'status': 'off'} + response = http_get(url, headers=headers, params=params) + if response.status_code != 200: + print('failed to retrieve "off" assets ' + str(start) + ' to ' + str(start + results_per_page), 'status code: ' + str(response.status_code)) + break + else: + data = json_decode(response.body) + assets = data['objects'] + assets_all.extend(assets) + last_return = len(assets) + start += last_return + if last_return < results_per_page: + break + + return assets_all + +def get_token(username, password): + url = CENTRA_BASE_URL + '/api/v3.0/authenticate' + headers = {'Content-Type': 'application/json'} + payload = {'username': username, + 'password': password} + + response = http_post(url, headers=headers, body=bytes(json_encode(payload))) + if response.status_code != 200: + print('authentication failed: ' + str(response.status_code)) + return None + + auth_data = json_decode(response.body) + if not auth_data: + print('invalid authentication data') + return None + + return auth_data['access_token'] + +def main(*args, **kwargs): + username = kwargs['access_key'] + password = kwargs['access_secret'] + token = get_token(username, password) + assets = get_assets(token) + + # Format asset list for import into runZero + import_assets = build_assets(assets) + if not import_assets: + print('no assets') + return None + + return import_assets \ No newline at end of file From e6ccd7989e8dd6b006ba150e2ca1341cd7d705c5 Mon Sep 17 00:00:00 2001 From: TechnoSavage Date: Fri, 31 Oct 2025 14:17:26 -0400 Subject: [PATCH 2/4] Initial commit for Akamai Guardicore Centra inbound integration. --- akamai_guardicore_centra/README.md | 10 +- akamai_guardicore_centra/centrav3.star | 210 ++++++++++++++++++ .../{guardicorecentra.star => centrav4.star} | 81 ++++--- 3 files changed, 268 insertions(+), 33 deletions(-) create mode 100644 akamai_guardicore_centra/centrav3.star rename akamai_guardicore_centra/{guardicorecentra.star => centrav4.star} (79%) diff --git a/akamai_guardicore_centra/README.md b/akamai_guardicore_centra/README.md index 90e2426..904ccd8 100644 --- a/akamai_guardicore_centra/README.md +++ b/akamai_guardicore_centra/README.md @@ -5,7 +5,7 @@ - Clone this repository ``` -git clone https://github.com/TechnoSavage/runZero.git +git clone https://github.com/runZeroInc/runzero-custom-integrations.git ``` ## runZero requirements @@ -28,9 +28,13 @@ git clone https://github.com/TechnoSavage/runZero.git ### Guardicore Centra configuration -1. Determine the proper Guardicore Centra URL: +1. Select appropriate script to use. + - 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. + - A large portion of the data provided by each API overlaps but there are differences between the two. + - 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). +2. Determine the proper Guardicore Centra URL: - Assign the URL to `CENTRA_BASE_URL` within the starlark script -2. Create login credentials with necessary, read-only access to retrieve JWT token for API access: +3. Create login credentials with necessary, read-only access to retrieve JWT token for API access: - Copy the username to the value for `access_key` when creating the Custom Integration credentials in the runZero console (see below) - Copy the password to the the value for `access_secret` when creating the Custom Integration credentials in the runZero console (see below) diff --git a/akamai_guardicore_centra/centrav3.star b/akamai_guardicore_centra/centrav3.star new file mode 100644 index 0000000..97720a2 --- /dev/null +++ b/akamai_guardicore_centra/centrav3.star @@ -0,0 +1,210 @@ +load('runzero.types', 'ImportAsset', 'NetworkInterface') +load('http', http_get='get', http_post='post', 'url_encode') +load('json', json_encode='encode', json_decode='decode') +load('net', 'ip_address') +load('time', 'parse_time') +load('uuid', 'new_uuid') + +#Change the URL to match your Guardicore Centra server +CENTRA_BASE_URL = 'https://' +RUNZERO_REDIRECT = 'https://console.runzero.com/' + +def build_assets(assets): + assets_import = [] + for asset in assets: + asset_id = str(asset.get('id', new_uuid)) + agent_info = asset.get('guest_agent_details', {}) + hardware = agent_info.get('hardware', {}) + os_info = agent_info.get('os_details', {}) + hostname = agent_info.get('hostname', '') + os = os_info.get('os_version_name', '') + vendor = hardware.get('vendor', '') + first_seen = asset.get('first_seen', None) + + # create the network interfaces + interfaces = [] + networks = agent_info.get('network', []) + for network in networks: + ips = [address.get('address', '') for address in network.get('ip_addresses', [])] + interface = build_network_interface(ips=ips, mac=network.get('hardware_address', None)) + interfaces.append(interface) + + # Retrieve and map custom attributes + active = asset.get('active', '') + agent_last_seen = asset.get('last_guest_agent_details_update', '') + agent_type = agent_info.get('agent_type', '') + agent_version = agent_info.get('agent_version', '') + arch = hardware.get('architecture', '') + bios_uuid = asset.get('bios_uuid', '') + client_cert = agent_info.get('client_cert_ssl_cn_name', '') + comments = asset.get('comments', '') + doc_version = asset.get('doc_version', '') + hw_uuid = hardware.get('hw_uuid', '') + is_on = asset.get('is_on', '') + labels = agent_info.get('labels', []) + last_seen = asset.get('last_seen', '') + kernel_major = os_info.get('os_kernel_major', '') + kernel_minor = os_info.get('os_kernel_minor', '') + os_kernel = str(kernel_major) + '.' + str(kernel_minor) + os_type = os_info.get('os_type', '') + proc_count = os_info.get('num_of_processors', '') + recent_domains = asset.get('recent_domains', []) + serial = hardware.get('serial', '') + status = asset.get('status', '') + vm_id = asset.get('vm_id', '') + vm_name = asset.get('vm_name', '') + + custom_attributes = { + 'active': active, + 'agent.LastSeenTS': agent_last_seen, + 'agent.type': agent_type, + 'agent.Version': agent_version, + 'biosUuid': bios_uuid, + 'client_cert': client_cert, + 'comments': comments, + 'docVersion': doc_version, + 'hardware.Arch': arch, + 'hardware.SerialNumber': serial, + 'hardware.Uuid': hw_uuid, + 'isOn': is_on, + 'labels': labels, + 'firstSeenTS': first_seen + 'lastSeenTS': last_seen, + 'recentDomains': recent_domains, + 'status': status, + 'osInfo.fullKernelVersion': os_kernel, + 'osInfo.osType': os_type, + 'processorCount': proc_count, + 'vmId': vm_id, + 'vmName': vm_name + } + + agent_labels = agent_info.get('labels', []) + for label in agent_labels: + custom_attributes['agent.labels.' + str(agent_labels.index(label))] = label + labels = asset.get('lables', []) + for label in labels: + for k, v in label.items(): + custom_attributes['labels.' + str(labels.index(label)) + '.' + k ] = v + metadata = asset.get('metadata', []) + for data in metadata: + custom_attributes['metadata.' + str(metadata.index(data))] = ':'.join(data) + orc_details = asset.get('orchestration_details', []) + for detail in orc_details: + for k, v in detail.items(): + custom_attributes['orchestrationDetails.' + str(orc_details.index(detail)) + '.' + k ] = v + agent_supported_features = agent_info.get('supported_features', {}).get('RevealAgent', []) + for feature in agent_supported_features: + custom_attributes['agent.supportedFeatures.' + str(agent_supported_features.index(feature))] = feature + + # Build assets for import + assets_import.append( + ImportAsset( + id=asset_id, + manufacturer=vendor, + hostnames=[hostname], + os=os, + networkInterfaces=interfaces, + customAttributes=custom_attributes + ) + ) + return assets_import + +def build_network_interface(ips, mac): + ip4s = [] + ip6s = [] + for ip in ips[:99]: + ip_addr = ip_address(ip) + if ip_addr.version == 4: + ip4s.append(ip_addr) + elif ip_addr.version == 6: + ip6s.append(ip_addr) + else: + continue + if not mac: + return NetworkInterface(ipv4Addresses=ip4s, ipv6Addresses=ip6s) + else: + return NetworkInterface(macAddress=mac, ipv4Addresses=ip4s, ipv6Addresses=ip6s) + +def get_assets(token): + assets_all = [] + results_per_page = 1000 + start = 0 + last_return = 1000 + + # Return all 'status:on' assets + while True: + url = CENTRA_BASE_URL + '/api/v4.0/assets?' + headers = {'Accept': 'application/json', + 'Authorization': 'Bearer ' + token} + params = {'max_results': results_per_page, + 'start_at': start, + 'status': 'on'} + response = http_get(url, headers=headers, params=params) + if response.status_code != 200: + print('failed to retrieve "on" assets ' + str(start) + ' to ' + str(start + results_per_page), 'status code: ' + str(response.status_code)) + break + else: + data = json_decode(response.body) + assets = data['objects'] + assets_all.extend(assets) + last_return = len(assets) + start += last_return + if last_return < results_per_page: + start = 0 + last_return = 1000 + break + # Return all 'status:off' assets. Remove this while loop to restrict import to only status 'on' assets. + while True: + url = CENTRA_BASE_URL + '/api/v3.0/assets?' + headers = {'Accept': 'application/json', + 'Authorization': 'Bearer ' + token} + params = {'max_results': results_per_page, + 'start_at': start, + 'status': 'off'} + response = http_get(url, headers=headers, params=params) + if response.status_code != 200: + print('failed to retrieve "off" assets ' + str(start) + ' to ' + str(start + results_per_page), 'status code: ' + str(response.status_code)) + break + else: + data = json_decode(response.body) + assets = data['objects'] + assets_all.extend(assets) + last_return = len(assets) + start += last_return + if last_return < results_per_page: + break + + return assets_all + +def get_token(username, password): + url = CENTRA_BASE_URL + '/api/v3.0/authenticate' + headers = {'Content-Type': 'application/json'} + payload = {'username': username, + 'password': password} + + response = http_post(url, headers=headers, body=bytes(json_encode(payload))) + if response.status_code != 200: + print('authentication failed: ' + str(response.status_code)) + return None + + auth_data = json_decode(response.body) + if not auth_data: + print('invalid authentication data') + return None + + return auth_data['access_token'] + +def main(*args, **kwargs): + username = kwargs['access_key'] + password = kwargs['access_secret'] + token = get_token(username, password) + assets = get_assets(token) + + # Format asset list for import into runZero + import_assets = build_assets(assets) + if not import_assets: + print('no assets') + return None + + return import_assets \ No newline at end of file diff --git a/akamai_guardicore_centra/guardicorecentra.star b/akamai_guardicore_centra/centrav4.star similarity index 79% rename from akamai_guardicore_centra/guardicorecentra.star rename to akamai_guardicore_centra/centrav4.star index b5bfcca..b6f1a49 100644 --- a/akamai_guardicore_centra/guardicorecentra.star +++ b/akamai_guardicore_centra/centrav4.star @@ -12,17 +12,16 @@ RUNZERO_REDIRECT = 'https://console.runzero.com/' def build_assets(assets): assets_import = [] for asset in assets: - asset_id = str(asset.get('id', new_uuid)) - hostname = asset.get('name', '') + agent_info = asset.get('agent', {}) os_info = asset.get('os_info', {}) + asset_id = agent_info.get('id', '') + hostname = asset.get('name', '') os = os_info.get('type', '') first_seen = asset.get('first_seen', '') #reformat first_seen timestamp for runZero parsing if first_seen != '': - trim_decimal = first_seen.split('.') - split_space = trim_decimal[0] - reformat = split_space[0] + 'T' + split_space[1] + 'Z' - first_seen = parse_time(reformat) + split_space = first_seen.split(' ') + first_seen = parse_time(split_space[0] + 'T' + split_space[1] + 'Z') # create the network interfaces interfaces = [] @@ -33,49 +32,69 @@ def build_assets(assets): interfaces.append(interface) # Retrieve and map custom attributes - asset_type = asset.get('asset_type', '') - os_kernel = os_info.get('full_kernel_version', '') - bios_uuid = asset.get('bios_uuid', '') + + orchestration_metadata = asset.get('orchestration_metadata', {}) scoping_details = asset.get('scoping_details', {}).get('worksite', {}) - worksite_mod = scoping_details.get('modified', '') - worksite_name = scoping_details.get('name', '') - last_seen = asset.get('last_seen', '') - mssp_tenant = asset.get('mssp_tenant_name', '') - status = asset.get('status', '') - instance_id = asset.get('instance_is', '') - agent_info = asset.get('agent', {}) + agent_id = agent_info.get('id', '') agent_last_seen = agent_info.get('agent_last_seen', '') + #reformat agent_last_seen timestamp for runZero parsing + if agent_last_seen != '': + strip_ms = agent_last_seen.split('.') + split_space = strip_ms[0].split(' ') + agent_last_seen = parse_time(split_space[0] + 'T' + split_space[1] + 'Z') agent_version = agent_info.get('agent_version', '') + asset_type = asset.get('asset_type', '') + bios_uuid = asset.get('bios_uuid', '') comments = asset.get('comments', '') - orchestration_metadata = asset.get('orchestration_metadata', {}) + instance_id = asset.get('instance_is', '') + last_seen = asset.get('last_seen', '') + #reformat last_seen timestamp for runZero parsing + if last_seen != '': + split_space = last_seen.split(' ') + last_seen = parse_time(split_space[0] + 'T' + split_space[1] + 'Z') + mssp_tenant = asset.get('mssp_tenant_name', '') orc_asset_type = orchestration_metadata.get('asset_type', '') orc_dev_name = orchestration_metadata.get('f5_device_hostname', '') orc_partition = orchestration_metadata.get('partition', '') orc_vs_name = orchestration_metadata.get('vs_name', '') + os_kernel = os_info.get('full_kernel_version', '') + status = asset.get('status', '') + worksite_mod = scoping_details.get('modified', '') + worksite_name = scoping_details.get('name', '') + custom_attributes = { + 'agent.Id': agent_id, + 'agent.LastSeenTS': agent_last_seen, + 'agent.Version': agent_version, 'assetType': asset_type, - 'osInfo.fullKernelVersion': os_kernel, 'biosUuid': bios_uuid, - 'scopingDetails.worksite.modified': worksite_mod, - 'scopingDetails.worksite.name': worksite_name, + 'comments': comments, + 'instanceId': instance_id, 'lastSeenTS': last_seen, 'msspTenantName': mssp_tenant, + 'osInfo.fullKernelVersion': os_kernel, + 'scopingDetails.worksite.modified': worksite_mod, + 'scopingDetails.worksite.name': worksite_name, 'status': status, - 'instanceId': instance_id, - 'agentLastSeenTS': agent_last_seen, - 'agentVersion': agent_version, 'orchestrationMetadata.assetType': orc_asset_type, 'orchestrationMetadata.f5DeviceHostname': orc_dev_name, 'orchestrationMetadata.partition': orc_partition, 'orchestrationMetadata.vsName': orc_vs_name } - ## Additional custom attributes to implement: - # label_groups = asset.get('label_groups', []) - # orchestration_details = asset.get('orchestration_details', []) - # labels = asset.get('labels', []) - + labels = asset.get('labels', []) + for item in labels: + for k, v in item.items(): + custom_attributes['label.' + str(labels.index(item)) + '.' + k] = v + label_groups = asset.get('label_groups', []) + for group in label_groups: + for k, v in group.items(): + custom_attributes['labelGroup.' + str(label_groups.index(group)) + '.' + k] = v + orchestration_details = asset.get('orchestration_details', []) + for detail in orchestration_details: + for k, v in detail.items(): + custom_attributes['orchestrationDetails.' + str(orchestration_details.index(detail)) + '.' + k] = v # Build assets for import assets_import.append( @@ -119,7 +138,8 @@ def get_assets(token): 'Authorization': 'Bearer ' + token} params = {'max_results': results_per_page, 'start_at': start, - 'status': 'on'} + 'status': 'on', + 'expand': 'agent'} response = http_get(url, headers=headers, params=params) if response.status_code != 200: print('failed to retrieve "on" assets ' + str(start) + ' to ' + str(start + results_per_page), 'status code: ' + str(response.status_code)) @@ -141,7 +161,8 @@ def get_assets(token): 'Authorization': 'Bearer ' + token} params = {'max_results': results_per_page, 'start_at': start, - 'status': 'off'} + 'status': 'off', + 'expand': 'agent'} response = http_get(url, headers=headers, params=params) if response.status_code != 200: print('failed to retrieve "off" assets ' + str(start) + ' to ' + str(start + results_per_page), 'status code: ' + str(response.status_code)) From d81818e892477449038b56c8565156910b374367 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 31 Oct 2025 18:19:15 +0000 Subject: [PATCH 3/4] Auto: update integrations JSON and README --- README.md | 1 + docs/integrations.json | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5bb430a..89f8cd9 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ If you need help setting up a custom integration, you can create an [issue](http - [Digital Ocean](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/digital-ocean/) - [Drata](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/drata/) - [Extreme Networks CloudIQ](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/extreme-cloud-iq/) +- [Guardicore Centra](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/akamai_guardicore_centra/) - [JAMF](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/jamf/) - [Kandji](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/kandji/) - [Lima Charlie](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/lima-charlie/) diff --git a/docs/integrations.json b/docs/integrations.json index badc1c7..5849e77 100644 --- a/docs/integrations.json +++ b/docs/integrations.json @@ -1,6 +1,6 @@ { - "lastUpdated": "2025-10-02T21:08:30.500200Z", - "totalIntegrations": 23, + "lastUpdated": "2025-10-31T18:19:15.255137Z", + "totalIntegrations": 24, "integrationDetails": [ { "name": "Lima Charlie", @@ -14,6 +14,12 @@ "readme": "https://github.com/runZeroInc/runzero-custom-integrations/blob/main/sumo-logic/README.md", "integration": "https://github.com/runZeroInc/runzero-custom-integrations/blob/main/sumo-logic/custom-integration-sumo.star" }, + { + "name": "Guardicore Centra", + "type": "inbound", + "readme": "https://github.com/runZeroInc/runzero-custom-integrations/blob/main/akamai_guardicore_centra/README.md", + "integration": "https://github.com/runZeroInc/runzero-custom-integrations/blob/main/akamai_guardicore_centra/centrav3.star" + }, { "name": "Cyberint", "type": "inbound", From 914cf3dc0072c3a0a9c1b7ab58403cc19afe575e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 3 Nov 2025 16:43:06 +0000 Subject: [PATCH 4/4] Auto: update integrations JSON and README --- README.md | 1 + docs/integrations.json | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0682637..3b0be41 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ If you need help setting up a custom integration, you can create an [issue](http - [Drata](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/drata/) - [Extreme Networks CloudIQ](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/extreme-cloud-iq/) - [Ghost Security](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/ghost/) +- [Guardicore Centra](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/akamai_guardicore_centra/) - [JAMF](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/jamf/) - [Kandji](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/kandji/) - [Lima Charlie](https://github.com/runZeroInc/runzero-custom-integrations/blob/main/lima-charlie/) diff --git a/docs/integrations.json b/docs/integrations.json index 3676d0c..ebe495f 100644 --- a/docs/integrations.json +++ b/docs/integrations.json @@ -1,6 +1,6 @@ { - "lastUpdated": "2025-11-03T16:33:29.642322Z", - "totalIntegrations": 27, + "lastUpdated": "2025-11-03T16:43:06.308528Z", + "totalIntegrations": 28, "integrationDetails": [ { "name": "Lima Charlie",