diff --git a/jamf/jamf 1.2.2/assign_policy.py b/jamf/jamf 1.2.2/assign_policy.py new file mode 100644 index 00000000..e8af2d35 --- /dev/null +++ b/jamf/jamf 1.2.2/assign_policy.py @@ -0,0 +1,85 @@ +''' +Copyright © 2020 Forescout Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +''' + +import urllib.request +from urllib.request import HTTPError, URLError +import logging + +url = params["connect_jamf_url"] +policy_name = urllib.parse.quote(params["jamf_policy"]) +assign_policy_url = f"{url}/JSSResource/policies/name/{policy_name}" + +logging.debug("The URL is: {assign_policy_url}") +username = params["connect_jamf_username"] +password = params["connect_jamf_password"] + +# Proxy support +jamf_proxy_enabled = params.get("connect_proxy_enable") +jamf_proxy_basic_auth_ip = params.get("connect_proxy_ip") +jamf_proxy_port = params.get("connect_proxy_port") +jamf_proxy_username = params.get("connect_proxy_username") +jamf_proxy_password = params.get("connect_proxy_password") +opener = jamf_lib.handle_proxy_configuration(jamf_proxy_enabled,*-/+ + jamf_proxy_basic_auth_ip, + jamf_proxy_port, + jamf_proxy_username, + jamf_proxy_password, ssl_context) + +response = {} + +if "dhcp_hostname_v2" in params: + xml_body = '' + \ + params["jamf_policy"] + '' + computer = '' + params["dhcp_hostname_v2"] + '' + xml_body += computer + '' + logging.debug(f"Content of xml body: {xml_body}") + xml_body = xml_body.encode("utf-8") + + try: + request = urllib.request.Request(url, data=xml_body, method='PUT') + token = params.get('connect_authorization_token') + request.add_header("Authorization", f"Bearer {token}") + request.add_header("Content-Type", "application/xml") + + # resp = urllib.request.urlopen(request, context=ssl_context) + + assign_policy_handle = opener.open(request) + + logging.info(f"Response code is {assign_policy_handle.getcode()}") + if assign_policy_handle.getcode() >= 200 and assign_policy_handle.getcode() < 300: + response["succeeded"] = True + + except HTTPError as e: + response["succeeded"] = False + response["troubleshooting"] = f"Failed action. Response code: {e.code}" + except URLError as e: + response["succeeded"] = False + response["troubleshooting"] = f"Failed action. {e.reason}" + except Exception as e: + logging.exception(e) + response["succeeded"] = False + response["troubleshooting"] = f"Failed action. {str(e)}" +else: + logging.error( + "Adding the endpoint to the scope of the policy requires a hostname.") + response["succeeded"] = False + response["troubleshooting"] = "Failed action. Endpoint does not have a hostname." diff --git a/jamf/jamf 1.2.2/auth.py b/jamf/jamf 1.2.2/auth.py new file mode 100644 index 00000000..3d5e3298 --- /dev/null +++ b/jamf/jamf 1.2.2/auth.py @@ -0,0 +1,35 @@ +import requests +import re +import logging + +url = params["connect_jamf_url"] +username = params["connect_jamf_username"] +password = params["connect_jamf_password"] + +def get_jamf_token(username: str, password: str, url: str) -> str: + try: + response = requests.post(f"{url}/api/v1/auth/token", auth=(username, password), verify=False) + response.raise_for_status() + if response.json().get('token'): + return response.json().get('token') + else: + raise ValueError("Token not found in the response") + + except requests.exceptions.RequestException as e: + logging.error(f"JAMF FAILED TO GET TOKEN: {e}") + return None + except Exception as e: + logging.error(f"JAMF FAILED TO GET TOKEN: {e}") + return None + +response = {} +token = get_jamf_token(username, password, url) + +if token is not None: + response['token'] = token + response['succeeded'] = True +else: + response['token']= "" + response['succeeded'] = False + + diff --git a/jamf/jamf 1.2.2/images/np_ng/action_groups/connect_jamf_jamf/connect_jamf_jamf.png b/jamf/jamf 1.2.2/images/np_ng/action_groups/connect_jamf_jamf/connect_jamf_jamf.png new file mode 100644 index 00000000..42eab074 Binary files /dev/null and b/jamf/jamf 1.2.2/images/np_ng/action_groups/connect_jamf_jamf/connect_jamf_jamf.png differ diff --git a/jamf/jamf 1.2.2/images/np_ng/actions/connect_jamf_assign_policy/connect_jamf_assign_policy.png b/jamf/jamf 1.2.2/images/np_ng/actions/connect_jamf_assign_policy/connect_jamf_assign_policy.png new file mode 100644 index 00000000..42eab074 Binary files /dev/null and b/jamf/jamf 1.2.2/images/np_ng/actions/connect_jamf_assign_policy/connect_jamf_assign_policy.png differ diff --git a/jamf/jamf 1.2.2/images/np_ng/actions/connect_jamf_assign_policy/failed_connect_jamf_assign_policy.png b/jamf/jamf 1.2.2/images/np_ng/actions/connect_jamf_assign_policy/failed_connect_jamf_assign_policy.png new file mode 100644 index 00000000..e5498043 Binary files /dev/null and b/jamf/jamf 1.2.2/images/np_ng/actions/connect_jamf_assign_policy/failed_connect_jamf_assign_policy.png differ diff --git a/jamf/jamf 1.2.2/images/np_ng/actions/connect_jamf_assign_policy/gray_connect_jamf_assign_policy.png b/jamf/jamf 1.2.2/images/np_ng/actions/connect_jamf_assign_policy/gray_connect_jamf_assign_policy.png new file mode 100644 index 00000000..8415ed99 Binary files /dev/null and b/jamf/jamf 1.2.2/images/np_ng/actions/connect_jamf_assign_policy/gray_connect_jamf_assign_policy.png differ diff --git a/jamf/jamf 1.2.2/images/np_ng/actions/connect_jamf_assign_policy/waiting_connect_jamf_assign_policy.png b/jamf/jamf 1.2.2/images/np_ng/actions/connect_jamf_assign_policy/waiting_connect_jamf_assign_policy.png new file mode 100644 index 00000000..bc3f37af Binary files /dev/null and b/jamf/jamf 1.2.2/images/np_ng/actions/connect_jamf_assign_policy/waiting_connect_jamf_assign_policy.png differ diff --git a/jamf/jamf 1.2.2/images/np_ng/field_groups/connect_jamf_jamf/connect_jamf_jamf.png b/jamf/jamf 1.2.2/images/np_ng/field_groups/connect_jamf_jamf/connect_jamf_jamf.png new file mode 100644 index 00000000..42eab074 Binary files /dev/null and b/jamf/jamf 1.2.2/images/np_ng/field_groups/connect_jamf_jamf/connect_jamf_jamf.png differ diff --git a/jamf/jamf 1.2.2/images/np_ng/field_groups/connect_jamf_jamf_mobile/connect_jamf_jamf_mobile.png b/jamf/jamf 1.2.2/images/np_ng/field_groups/connect_jamf_jamf_mobile/connect_jamf_jamf_mobile.png new file mode 100644 index 00000000..42eab074 Binary files /dev/null and b/jamf/jamf 1.2.2/images/np_ng/field_groups/connect_jamf_jamf_mobile/connect_jamf_jamf_mobile.png differ diff --git a/jamf/jamf 1.2.2/images/np_ng/templatedirs/connect_jamf/connect_jamf.png b/jamf/jamf 1.2.2/images/np_ng/templatedirs/connect_jamf/connect_jamf.png new file mode 100644 index 00000000..42eab074 Binary files /dev/null and b/jamf/jamf 1.2.2/images/np_ng/templatedirs/connect_jamf/connect_jamf.png differ diff --git a/jamf/jamf 1.2.2/jamf_lib.py b/jamf/jamf 1.2.2/jamf_lib.py new file mode 100644 index 00000000..a494f64a --- /dev/null +++ b/jamf/jamf 1.2.2/jamf_lib.py @@ -0,0 +1,84 @@ +''' +Copyright © 2020 Forescout Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +''' + +import base64 + +import logging +import urllib.request + + +def create_auth(username, password): + basic_auth_string = base64.b64encode( + bytes('%s:%s' % (username, password), 'ascii')) + return basic_auth_string.decode('utf-8') + + +def get_proxy_dict(proxy_basic_auth_ip, proxy_port, proxy_username, proxy_password): + """ + Generates the proxy dictionary object to be used in the handle_proxy_configuration() function, and as the 'proxies' argument in requests library API call + :return: proxy dictionary for both http and https connections + """ + logging.debug("PROXY IS ENABLED --> Generating proxy dictionary...") + + logging.debug("Proxy Basic Auth IP: " + proxy_basic_auth_ip) + logging.debug("Proxy Port: " + proxy_port) + logging.debug("Proxy Username: " + proxy_username) + + if proxy_username is None or proxy_password is None: + proxy_dict = { + "http": "http://{}:{}".format(proxy_basic_auth_ip, proxy_port), + "https": "https://{}:{}".format(proxy_basic_auth_ip, proxy_port) + } + else: + proxy_dict = { + "http": "http://{}:{}@{}:{}".format(proxy_username, proxy_password, proxy_basic_auth_ip, proxy_port), + "https": "https://{}:{}@{}:{}".format(proxy_username, proxy_password, proxy_basic_auth_ip, proxy_port) + } + + #logging.debug("PROXY HTTP URL: {}".format(proxy_dict.get("http"))) + #logging.debug("PROXY HTTPS URL: {}".format(proxy_dict.get("https"))) + return(proxy_dict) + + +def handle_proxy_configuration(proxy_enabled, proxy_basic_auth_ip, proxy_port, proxy_username, proxy_password, ssl_context): + """ + Handles the proxy server in the case that proxy was enabled by the user + :return: opener that handles both proxy and no proxy ONLY for the urllib library + """ + logging.debug("Proxy Enabled: " + proxy_enabled) + + # creating the https handler object + https_handler = urllib.request.HTTPSHandler(context=ssl_context) + + # with proxy handler + if proxy_enabled == "true": + # get proxy_dict + proxy_dict = get_proxy_dict( + proxy_basic_auth_ip, proxy_port, proxy_username, proxy_password) + proxy_handler = urllib.request.ProxyHandler(proxy_dict) + opener = urllib.request.build_opener(proxy_handler, https_handler) + logging.debug("Opener object with Proxy support ENABLED") + else: + # without proxy handler + opener = urllib.request.build_opener(https_handler) + logging.debug("Opener object with Proxy DISABLED") + return(opener) diff --git a/jamf/jamf 1.2.2/license.txt b/jamf/jamf 1.2.2/license.txt new file mode 100644 index 00000000..4c702316 --- /dev/null +++ b/jamf/jamf 1.2.2/license.txt @@ -0,0 +1,19 @@ +Copyright © 2020 Forescout Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/jamf/jamf 1.2.2/policies/nptemplates/JamfAudit.xml b/jamf/jamf 1.2.2/policies/nptemplates/JamfAudit.xml new file mode 100644 index 00000000..e032f12b --- /dev/null +++ b/jamf/jamf 1.2.2/policies/nptemplates/JamfAudit.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jamf/jamf 1.2.2/policies/nptemplates/JamfCompliance.xml b/jamf/jamf 1.2.2/policies/nptemplates/JamfCompliance.xml new file mode 100644 index 00000000..86feca70 --- /dev/null +++ b/jamf/jamf 1.2.2/policies/nptemplates/JamfCompliance.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jamf/jamf 1.2.2/policies/nptemplates/JamfDiskCompliance.xml b/jamf/jamf 1.2.2/policies/nptemplates/JamfDiskCompliance.xml new file mode 100644 index 00000000..93e749d0 --- /dev/null +++ b/jamf/jamf 1.2.2/policies/nptemplates/JamfDiskCompliance.xml @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jamf/jamf 1.2.2/policies/nptemplates/JamfManageability.xml b/jamf/jamf 1.2.2/policies/nptemplates/JamfManageability.xml new file mode 100644 index 00000000..e6452b7d --- /dev/null +++ b/jamf/jamf 1.2.2/policies/nptemplates/JamfManageability.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jamf/jamf 1.2.2/poll.py b/jamf/jamf 1.2.2/poll.py new file mode 100644 index 00000000..5d700b04 --- /dev/null +++ b/jamf/jamf 1.2.2/poll.py @@ -0,0 +1,147 @@ +''' +Copyright © 2020 Forescout Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +''' + +import urllib.request +from urllib.request import HTTPError, URLError +import json +import logging + +jamf_to_ct_props_map = { + "id": "connect_jamf_id", + "managed": "connect_jamf_managed" +} + +jamf_to_ct_props_mobile_map = { + "id": "connect_jamf_mobile_id", + "managed": "connect_jamf_mobile_managed" +} + +url = params["connect_jamf_url"] + +username = params["connect_jamf_username"] +password = params["connect_jamf_password"] + +P_COMPUTER_POLL = params.get("connect_jamf_computer_poll", "") +logging.info(f"Computer polling is {P_COMPUTER_POLL}") +P_MOBILE_POLL = params.get("connect_jamf_mobiledevice_poll", "") +logging.info(f"Mobile polling is {P_MOBILE_POLL}") + +# Proxy support +jamf_proxy_enabled = params.get("connect_proxy_enable") +jamf_proxy_basic_auth_ip = params.get("connect_proxy_ip") +jamf_proxy_port = params.get("connect_proxy_port") +jamf_proxy_username = params.get("connect_proxy_username") +jamf_proxy_password = params.get("connect_proxy_password") +opener = jamf_lib.handle_proxy_configuration(jamf_proxy_enabled, + jamf_proxy_basic_auth_ip, + jamf_proxy_port, + jamf_proxy_username, + jamf_proxy_password, ssl_context) + +response = {} +endpoints = [] + +try: + if P_COMPUTER_POLL == 'true': + computer_poll_url = f"{url}/JSSResource/computers/subset/basic" + logging.info(f"The computer URL is: {computer_poll_url}") + + # Build Request + poll_request = urllib.request.Request(computer_poll_url) + # Add Headers + token = params.get('connect_authorization_token') + poll_request.add_header("Authorization", f"Bearer {token}" ) + poll_request.add_header("Accept", "application/json") + + resolve_response_handle = opener.open(poll_request) + logging.debug(f"The status code {resolve_response_handle.getcode()}") + poll_response = resolve_response_handle.read().decode("utf-8") + logging.debug(f"The poll_response {poll_response}") + + endpoint_data = json.loads(poll_response)["computers"] + logging.debug(f"The computer devices from Jamf {endpoint_data}") + + properties = {} + + for endpoint in endpoint_data: + new_endpoint = {} + mac = endpoint["mac_address"] + mac = mac.replace(":", "").lower() + new_endpoint["mac"] = mac + properties = {} + for key, value in jamf_to_ct_props_map.items(): + if key in endpoint: + properties[value] = endpoint[key] + new_endpoint["properties"] = properties + endpoints.append(new_endpoint) + logging.debug(f"computer endpoints : {endpoints}") + # response["endpoints"] = endpoints + + # If we are polling mobile devices + if P_MOBILE_POLL == 'true': + mobile_poll_url = f"{url}/JSSResource/mobiledevices/subset/basic" + logging.info(f"The mobile URL is: {mobile_poll_url}") + + # Build Request + poll_request = urllib.request.Request(mobile_poll_url) + # Add Headers + token = params.get('connect_authorization_token') + poll_request.add_header("Authorization", f"Bearer {token}" ) + poll_request.add_header("Accept", "application/json") + + resolve_response_handle = opener.open(poll_request) + logging.debug(f"The status code {resolve_response_handle.getcode()}") + poll_response = resolve_response_handle.read().decode("utf-8") + logging.debug(f"The poll_response {poll_response}") + + endpoint_data = json.loads(poll_response)["mobile_devices"] + logging.debug(f"The mobile devices from Jamf {endpoint_data}") + + properties = {} + # endpoints = [] + + for endpoint in endpoint_data: + new_endpoint = {} + mac = endpoint["wifi_mac_address"] + mac = mac.replace(":", "").lower() + new_endpoint["mac"] = mac + properties = {} + for key, value in jamf_to_ct_props_mobile_map.items(): + if key in endpoint: + properties[value] = endpoint[key] + # logging.debug(f"mobile value {value} : key {key}") + new_endpoint["properties"] = properties + endpoints.append(new_endpoint) + logging.debug(f"mobile endpoints : {endpoints}") + + response["endpoints"] = endpoints + +except HTTPError as e: + response["succeeded"] = False + response["error"] = f"HTTP Error : {e.code}" +except URLError as e: + response["succeeded"] = False + response["error"] = f"URL Error : Reason : {e.reason}" +except Exception as e: + logging.exception(e) + response["succeeded"] = False + response["error"] = f"Exception Error : {str(e)}" diff --git a/jamf/jamf 1.2.2/property.conf b/jamf/jamf 1.2.2/property.conf new file mode 100644 index 00000000..b5bf12b3 --- /dev/null +++ b/jamf/jamf 1.2.2/property.conf @@ -0,0 +1,963 @@ +{ + "version": "1.2.2", + "name": "Jamf", + "groups": [{ + "name": "connect_jamf_jamf", + "label": "Jamf Computer" + }, + { + "name": "connect_jamf_jamf_mobile", + "label": "Jamf Mobile" + }], + "properties": [{ + "tag": "connect_jamf_deviceName", + "label": "Jamf Device Name", + "description": "Jamf Device Name", + "type": "string", + "group": "connect_jamf_jamf", + "asset_portal": true, + "dependencies": [{ + "name": "mac", + "redo_new": true, + "redo_change": true + }, + { + "name": "dhcp_hostname_v2", + "redo_new": true, + "redo_change": true + }, + { + "name": "connect_globalprotect_computer_name", + "redo_new": true, + "redo_change": true + } + ] + }, + { + "tag": "connect_jamf_managed", + "label": "Jamf Managed", + "description": "Jamf Managed", + "type": "boolean", + "group": "connect_jamf_jamf", + "asset_portal": true, + "dependencies": [{ + "name": "mac", + "redo_new": true, + "redo_change": true + }, + { + "name": "dhcp_hostname_v2", + "redo_new": true, + "redo_change": true + }, + { + "name": "connect_globalprotect_computer_name", + "redo_new": true, + "redo_change": true + } + ] + }, + { + "tag": "connect_jamf_id", + "label": "Jamf ID", + "description": "Jamf ID", + "type": "integer", + "group": "connect_jamf_jamf", + "asset_portal": true, + "dependencies": [{ + "name": "mac", + "redo_new": true, + "redo_change": true + }, + { + "name": "dhcp_hostname_v2", + "redo_new": true, + "redo_change": true + }, + { + "name": "connect_globalprotect_computer_name", + "redo_new": true, + "redo_change": true + } + ] + }, + { + "tag": "connect_jamf_asset_purchasing", + "label": "Jamf Asset Purchasing", + "description": "Jamf Asset Purchasing", + "type": "composite", + "group": "connect_jamf_jamf", + "subfields": [{ + "tag": "is_leased", + "label": "Leased", + "description": "Leased", + "type": "boolean" + }, + { + "tag": "is_purchased", + "label": "Purchased", + "description": "Purchased", + "type": "boolean" + } + ], + "dependencies": [{ + "name": "mac", + "redo_new": true, + "redo_change": true + }, + { + "name": "dhcp_hostname_v2", + "redo_new": true, + "redo_change": true + }, + { + "name": "connect_globalprotect_computer_name", + "redo_new": true, + "redo_change": true + } + ] + }, + { + "tag": "connect_jamf_user_information", + "label": "Jamf User Information", + "description": "Jamf User Information", + "type": "composite", + "group": "connect_jamf_jamf", + "subfields": [{ + "tag": "real_name", + "label": "Real Name", + "description": "Real Name", + "type": "string" + }, + { + "tag": "email_address", + "label": "Email Address", + "description": "Email Address", + "type": "string" + }, + { + "tag": "username", + "label": "Username", + "description": "Username", + "type": "string" + }, + { + "tag": "phone_number", + "label": "Phone Number", + "description": "Phone Number", + "type": "string" + }, + { + "tag": "position", + "label": "Position", + "description": "Position", + "type": "string" + } + ], + "dependencies": [{ + "name": "mac", + "redo_new": true, + "redo_change": true + }, + { + "name": "dhcp_hostname_v2", + "redo_new": true, + "redo_change": true + }, + { + "name": "connect_globalprotect_computer_name", + "redo_new": true, + "redo_change": true + } + ] + }, + { + "tag": "connect_jamf_device_details", + "label": "Jamf Device Details", + "description": "Jamf Device Details", + "type": "composite", + "group": "connect_jamf_jamf", + "subfields": [{ + "tag": "total_ram", + "label": "Memory (Total)", + "description": "Memory (Total)", + "type": "integer" + }, + { + "tag": "os_name", + "label": "Operating System", + "description": "Operating System", + "type": "string" + }, + { + "tag": "serial_number", + "label": "Serial Number", + "description": "Serial Number", + "type": "string" + }, + { + "tag": "make", + "label": "Make", + "description": "Make", + "type": "string" + }, + { + "tag": "battery_capacity", + "label": "Battery Capacity", + "description": "Battery Capacity", + "type": "integer" + }, + { + "tag": "processor_speed", + "label": "Processor Speed", + "description": "Processor Speed", + "type": "integer" + }, + { + "tag": "model", + "label": "Model", + "description": "Model", + "type": "string" + }, + { + "tag": "os_version", + "label": "Operating System Version", + "description": "Operating System Version", + "type": "string" + }, + { + "tag": "processor_type", + "label": "Processor Type", + "description": "Processor Type", + "type": "string" + }, + { + "tag": "os_build", + "label": "Operating System Build", + "description": "Operating System Build", + "type": "string" + }, + { + "tag": "number_cores", + "label": "Processor Cores (Total)", + "description": "Processor Cores (Total)", + "type": "integer" + }, + { + "tag": "processor_architecture", + "label": "Processor Architecture", + "description": "Processor Architecture", + "type": "string" + }, + { + "tag": "number_processors", + "label": "Processors (Total)", + "description": "Processors (Total)", + "type": "integer" + } + ], + "dependencies": [{ + "name": "mac", + "redo_new": true, + "redo_change": true + }, + { + "name": "dhcp_hostname_v2", + "redo_new": true, + "redo_change": true + }, + { + "name": "connect_globalprotect_computer_name", + "redo_new": true, + "redo_change": true + } + ] + }, + { + "tag": "connect_jamf_boot_device", + "label": "Jamf Boot Device", + "description": "Jamf Boot Device", + "type": "composite", + "group": "connect_jamf_jamf", + "subfields": [{ + "tag": "filevault_status", + "label": "FileVault Status", + "description": "FileVault Status", + "type": "string" + }, + { + "tag": "name", + "label": "Name", + "description": "Name", + "type": "string" + }, + { + "tag": "size", + "label": "Size", + "description": "Size", + "type": "integer" + }, + { + "tag": "filevault_percent", + "label": "FileVault Percent", + "description": "FileVault Percent", + "type": "integer" + }, + { + "tag": "percentage_full", + "label": "Percentage Full", + "description": "Percentage Full", + "type": "integer" + } + ], + "dependencies": [{ + "name": "mac", + "redo_new": true, + "redo_change": true + }, + { + "name": "dhcp_hostname_v2", + "redo_new": true, + "redo_change": true + }, + { + "name": "connect_globalprotect_computer_name", + "redo_new": true, + "redo_change": true + } + ] + }, + { + "tag": "connect_jamf_software_installed", + "label": "Jamf Software Installed", + "description": "Jamf List of Software Installed", + "type": "string", + "group": "connect_jamf_jamf", + "list": true, + "overwrite": true, + "dependencies": [{ + "name": "mac", + "redo_new": true, + "redo_change": true + }, + { + "name": "dhcp_hostname_v2", + "redo_new": true, + "redo_change": true + }, + { + "name": "connect_globalprotect_computer_name", + "redo_new": true, + "redo_change": true + } + ] + }, + { + "tag": "connect_jamf_agent_information", + "label": "Jamf Agent Information", + "description": "Jamf Agent Information", + "type": "composite", + "group": "connect_jamf_jamf", + "subfields": [{ + "tag": "jamf_version", + "label": "Agent Version", + "description": "Agent Version", + "type": "string" + }, + { + "tag": "initial_entry_date_epoch", + "label": "Initial Entry", + "description": "Initial Entry", + "type": "date" + }, + { + "tag": "last_contact_time_epoch", + "label": "Last Contacted", + "description": "Last Contacted", + "type": "date" + } + ], + "dependencies": [{ + "name": "mac", + "redo_new": true, + "redo_change": true + }, + { + "name": "dhcp_hostname_v2", + "redo_new": true, + "redo_change": true + }, + { + "name": "connect_globalprotect_computer_name", + "redo_new": true, + "redo_change": true + } + ] + }, + { + "tag": "connect_jamf_policies", + "label": "Jamf Policies", + "description": "Jamf Policies", + "type": "string", + "list": true, + "group": "connect_jamf_jamf", + "asset_portal": true, + "overwrite": true, + "dependencies": [{ + "name": "mac", + "redo_new": true, + "redo_change": true + }, + { + "name": "dhcp_hostname_v2", + "redo_new": true, + "redo_change": true + }, + { + "name": "connect_globalprotect_computer_name", + "redo_new": true, + "redo_change": true + } + ] + }, + + + { + "tag": "connect_jamf_mobile_deviceName", + "label": "Jamf Mobile Device Name", + "description": "Jamf Mobile Device Name", + "type": "string", + "group": "connect_jamf_jamf_mobile", + "asset_portal": true, + "dependencies": [{ + "name": "mac", + "redo_new": true, + "redo_change": true + }, + { + "name": "dhcp_hostname_v2", + "redo_new": true, + "redo_change": true + }, + { + "name": "connect_globalprotect_computer_name", + "redo_new": true, + "redo_change": true + } + ] + }, + { + "tag": "connect_jamf_mobile_managed", + "label": "Jamf Mobile Managed", + "description": "Jamf Mobile Managed", + "type": "boolean", + "group": "connect_jamf_jamf_mobile", + "asset_portal": true, + "dependencies": [{ + "name": "mac", + "redo_new": true, + "redo_change": true + }, + { + "name": "dhcp_hostname_v2", + "redo_new": true, + "redo_change": true + }, + { + "name": "connect_globalprotect_computer_name", + "redo_new": true, + "redo_change": true + } + ] + }, + { + "tag": "connect_jamf_mobile_id", + "label": "Jamf Mobile ID", + "description": "Jamf Mobile ID", + "type": "integer", + "group": "connect_jamf_jamf_mobile", + "asset_portal": true, + "dependencies": [{ + "name": "mac", + "redo_new": true, + "redo_change": true + }, + { + "name": "dhcp_hostname_v2", + "redo_new": true, + "redo_change": true + }, + { + "name": "connect_globalprotect_computer_name", + "redo_new": true, + "redo_change": true + } + ] + }, + { + "tag": "connect_jamf_mobile_asset_purchasing", + "label": "Jamf Mobile Asset Purchasing", + "description": "Jamf Mobile Asset Purchasing", + "type": "composite", + "group": "connect_jamf_jamf_mobile", + "subfields": [{ + "tag": "is_leased", + "label": "Leased", + "description": "Leased", + "type": "boolean" + }, + { + "tag": "is_purchased", + "label": "Purchased", + "description": "Purchased", + "type": "boolean" + } + ], + "dependencies": [{ + "name": "mac", + "redo_new": true, + "redo_change": true + }, + { + "name": "dhcp_hostname_v2", + "redo_new": true, + "redo_change": true + }, + { + "name": "connect_globalprotect_computer_name", + "redo_new": true, + "redo_change": true + } + ] + }, + { + "tag": "connect_jamf_mobile_user_information", + "label": "Jamf Mobile User Information", + "description": "Jamf Mobile User Information", + "type": "composite", + "group": "connect_jamf_jamf_mobile", + "subfields": [{ + "tag": "real_name", + "label": "Real Name", + "description": "Real Name", + "type": "string" + }, + { + "tag": "email_address", + "label": "Email Address", + "description": "Email Address", + "type": "string" + }, + { + "tag": "username", + "label": "Username", + "description": "Username", + "type": "string" + }, + { + "tag": "phone_number", + "label": "Phone Number", + "description": "Phone Number", + "type": "string" + }, + { + "tag": "position", + "label": "Position", + "description": "Position", + "type": "string" + } + ], + "dependencies": [{ + "name": "mac", + "redo_new": true, + "redo_change": true + }, + { + "name": "dhcp_hostname_v2", + "redo_new": true, + "redo_change": true + }, + { + "name": "connect_globalprotect_computer_name", + "redo_new": true, + "redo_change": true + } + ] + }, + { + "tag": "connect_jamf_mobile_device_details", + "label": "Jamf Mobile Device Details", + "description": "Jamf Mobile Device Details", + "type": "composite", + "group": "connect_jamf_jamf_mobile", + "subfields": [{ + "tag": "capacity", + "label": "Capacity", + "description": "Capacity (total)", + "type": "integer" + }, + { + "tag": "os_type", + "label": "Operating System Type", + "description": "Operating System Type", + "type": "string" + }, + { + "tag": "serial_number", + "label": "Serial Number", + "description": "Serial Number", + "type": "string" + }, + { + "tag": "phone_number", + "label": "Phone Number", + "description": "Phone Number", + "type": "string" + }, + { + "tag": "udid", + "label": "UDID", + "description": "Unique Device ID", + "type": "string" + }, + { + "tag": "model", + "label": "Model", + "description": "Model", + "type": "string" + }, + { + "tag": "os_version", + "label": "Operating System Version", + "description": "Operating System Version", + "type": "string" + }, + { + "tag": "model_number", + "label": "Model Number", + "description": "Model Number", + "type": "string" + }, + { + "tag": "os_build", + "label": "Operating System Build", + "description": "Operating System Build", + "type": "string" + }, + { + "tag": "model_identifier", + "label": "Model Identifier", + "description": "Model Identifier", + "type": "string" + } + ], + "dependencies": [{ + "name": "mac", + "redo_new": true, + "redo_change": true + }, + { + "name": "dhcp_hostname_v2", + "redo_new": true, + "redo_change": true + }, + { + "name": "connect_globalprotect_computer_name", + "redo_new": true, + "redo_change": true + } + ] + }, + { + "tag": "connect_jamf_mobile_software_installed", + "label": "Jamf Mobile Software Installed", + "description": "Jamf Mobile List of Software Installed", + "type": "string", + "group": "connect_jamf_jamf_mobile", + "list": true, + "overwrite": true, + "dependencies": [{ + "name": "mac", + "redo_new": true, + "redo_change": true + }, + { + "name": "dhcp_hostname_v2", + "redo_new": true, + "redo_change": true + }, + { + "name": "connect_globalprotect_computer_name", + "redo_new": true, + "redo_change": true + } + ] + }, + { + "tag": "connect_jamf_mobile_agent_information", + "label": "Jamf Mobile Agent Information", + "description": "Jamf Mobile Agent Information", + "type": "composite", + "group": "connect_jamf_jamf_mobile", + "subfields": [{ + "tag": "jamf_version", + "label": "Agent Version", + "description": "Agent Version", + "type": "string" + }, + { + "tag": "initial_entry_date_epoch", + "label": "Initial Entry", + "description": "Initial Entry", + "type": "date" + }, + { + "tag": "last_contact_time_epoch", + "label": "Last Contacted", + "description": "Last Contacted", + "type": "date" + } + ], + "dependencies": [{ + "name": "mac", + "redo_new": true, + "redo_change": true + }, + { + "name": "dhcp_hostname_v2", + "redo_new": true, + "redo_change": true + }, + { + "name": "connect_globalprotect_computer_name", + "redo_new": true, + "redo_change": true + } + ] + }, + { + "tag": "connect_jamf_mobile_security", + "label": "Jamf Mobile Security Information", + "description": "Jamf Mobile Security Information", + "type": "composite", + "group": "connect_jamf_jamf_mobile", + "subfields": [{ + "tag": "data_protection", + "label": "Data Protection Enabled", + "description": "Data Protection Enabled", + "type": "boolean" + }, + { + "tag": "passcode_present", + "label": "Passcode Present", + "description": "Passcode Present", + "type": "boolean" + }, + { + "tag": "passcode_compliant", + "label": "Passcode Compliant", + "description": "Passcode Compliant", + "type": "boolean" + }, + { + "tag": "hardware_encryption", + "label": "Hardware Encryption", + "description": "Hardware Encryption", + "type": "integer" + }, + { + "tag": "activation_lock_enabled", + "label": "Activation Lock Enabled", + "description": "Activation Lock Enabled", + "type": "boolean" + }, + { + "tag": "lost_mode_enabled", + "label": "Lost Mode Enabled", + "description": "Lost Mode Enabled", + "type": "string" + }, + { + "tag": "lost_mode_enforced", + "label": "Lost Mode Enforced", + "description": "Lost Mode Enforced", + "type": "boolean" + }, + { + "tag": "lost_mode_enable_issued_epoch", + "label": "Lost Mode Enable Issued Date", + "description": "Lost Mode Enable Issued Date", + "type": "date" + }, + { + "tag": "jailbreak_detected", + "label": "Jailbreak Detected", + "description": "Jailbreak Detected", + "type": "string" + } + ], + "dependencies": [{ + "name": "mac", + "redo_new": true, + "redo_change": true + }, + { + "name": "dhcp_hostname_v2", + "redo_new": true, + "redo_change": true + }, + { + "name": "connect_globalprotect_computer_name", + "redo_new": true, + "redo_change": true + } + ] + } + ], + "action_groups": [{ + "name": "connect_jamf_jamf", + "label": "Jamf" + }], + "actions": [{ + "name": "connect_jamf_assign_policy", + "label": "Assign Device to Jamf Policy", + "group": "connect_jamf_jamf", + "description": "Assign Device to Jamf Policy", + "ip_required": false, + "params": [{ + "name": "jamf_policy", + "type": "string", + "label": "Jamf Policy Name", + "description": "Jamf Policy Name" + }], + "dependencies": [{ + "name": "mac", + "redo_new": true, + "redo_change": true + }, + { + "name": "dhcp_hostname_v2", + "redo_new": true, + "redo_change": true + }, + { + "name": "connect_globalprotect_computer_name", + "redo_new": true, + "redo_change": true + } + ] + }], + "scripts": [{ + "name": "test.py", + "test": true + }, + { + "name": "auth.py", + "authorization": true + }, + { + "name": "resolve.py", + "properties": [ + "connect_jamf_deviceName", + "connect_jamf_managed", + "connect_jamf_id", + "connect_jamf_asset_purchasing", + "connect_jamf_user_information", + "connect_jamf_device_details", + "connect_jamf_software_installed", + "connect_jamf_agent_information" + ] + }, + { + "name": "resolve_mobiledevices.py", + "properties": [ + "connect_jamf_mobile_deviceName", + "connect_jamf_mobile_managed", + "connect_jamf_mobile_id", + "connect_jamf_mobile_asset_purchasing", + "connect_jamf_mobile_user_information", + "connect_jamf_mobile_device_details", + "connect_jamf_mobile_software_installed", + "connect_jamf_mobile_agent_information", + "connect_jamf_mobile_security" + ] + }, + { + "name": "resolve_boot.py", + "properties": [ + "connect_jamf_boot_device" + ] + }, + { + "name": "resolve_policies.py", + "properties": [ + "connect_jamf_policies" + ] + }, + { + "name": "assign_policy.py", + "actions": [ + "connect_jamf_assign_policy" + ] + }, + { + "name": "jamf_lib.py", + "library_file": true + }, + { + "name": "poll.py", + "discovery": true + } + ], + "policy_template": { + "policy_template_group": { + "name": "connect_jamf", + "label": "Jamf", + "display": "Jamf", + "description": "Jamf templates", + "full_description": "Use Jamf policy templates to manage devices in a Jamf environment:", + "title_image": "connect_jamf.png" + }, + "policies": [{ + "name": "connect_jamf_compliant", + "label": "Jamf Enrolled Policies Compliance", + "display": "Jamf Enrolled Policies Compliance", + "help": "Jamf Enrolled Policies Compliance", + "description": "Use this template to create a Forescout policy that checks for one or more enrolled Jamf policies.", + "file_name": "JamfCompliance.xml", + "full_description": "Use this template to create a Forescout policy that checks for one or more enrolled Jamf policies. Note: Endpoints in this Forescout policy should belong to a Jamf managed group. Optional remediations actions can be used to: * Notify Forescout administrator to manually assign the endpoint to a Jamf policy * Send log events to syslog or SIEM service about the non-compliant state of the endpoint * Automatically assign endpoint to Jamf policy. These actions are disabled by default. ", + "title_image": "connect_jamf.png" + }, + { + "name": "connect_jamf_manageability", + "label": "Jamf Manageability", + "display": "Jamf Manageability", + "help": "Jamf Manageability", + "description": "Use this template to create a Forescout policy to check for manageability of this endpoint by Jamf", + "file_name": "JamfManageability.xml", + "full_description": "Use this template to create a Forescout policy to check for manageability of this endpoint by Jamf Note: Typically endpoints should be Mac OS devices but can include other devices such as iPhones and Apple TVs Optional remediations actions can be used to: * Notify Forescout administrator to manually enroll the endpoint in Jamf * Send log events to syslog or SIEM service about the managed state of the endpoint * Create a ServiceNOW IT incident ticket to begin Jamf onboarding remediation workflow. (Note: ServiceNOW eyeExtend module is required) These actions are disabled by default.", + "title_image": "connect_jamf.png" + }, + { + "name": "connect_jamf_audit", + "label": "Jamf example Installed Application Audit", + "display": "Jamf example Installed Application Audit", + "help": "Jamf example Installed Application Audit", + "description": "Use this template to create a Forescout policy to check for Installed Applications on Jamf managed endpoints.", + "file_name": "JamfAudit.xml", + "full_description": "Use this template to create a Forescout policy to check for Installed Applications on Jamf managed endpoints. Note: Endpoints in this Forescout policy should belong to a Jamf managed group. Optional remediation actions can be used to: * Notify Forescout administrator to manually install or remove software on the endpoint from Jamf * Send log events to syslog or SIEM service about the installed software on the endpoint. * Create a ServiceNOW IT incident ticket to begin Jamf manual remediation workflow. (Note: ServiceNOW eyeExtend module is required) These actions are disabled by default.", + "title_image": "connect_jamf.png" + }, + { + "name": "connect_jamf_disk_compliant", + "label": "Jamf FileVault Disk Encryption Compliance", + "display": "Jamf FileVault Disk Encryption Compliance", + "help": "Jamf FileVault Disk Encryption Compliance", + "description": "Use this template to create a Forescout policy to check for FileVault Disk Encryption on an endpoint managed by Jamf", + "file_name": "JamfDiskCompliance.xml", + "full_description": "Use this template to create a Forescout policy to check for FileVault Disk Encryption on an endpoint managed by Jamf Optional remediations actions can be used to: * Notify Desktop administrator to manually initiate FileVault encryption on the endpoint. * Send log events to syslog or SIEM service about the Encryption Status of the endpoint * Create a ServiceNOW IT incident ticket to begin Manual Encryption Remediation workflow. (Note: ServiceNOW eyeExtend module is required) These actions are disabled by default.", + "title_image": "connect_jamf.png" + } + ] + } +} \ No newline at end of file diff --git a/jamf/jamf 1.2.2/resolve.py b/jamf/jamf 1.2.2/resolve.py new file mode 100644 index 00000000..6ca206f0 --- /dev/null +++ b/jamf/jamf 1.2.2/resolve.py @@ -0,0 +1,134 @@ +''' +Copyright © 2020 Forescout Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +''' + +import urllib.request +from urllib.request import HTTPError, URLError +import json + +general_map = { + "name": "connect_jamf_deviceName", + "id": "connect_jamf_id" +} + +composite_map = { + "hardware": ["total_ram", "os_name", "make", "battery_capacity", "processor_speed", "model", "os_version", "processor_type", "os_build", "number_cores", "processor_architecture", "number_processors"], + "location": ["real_name", "email_address", "username", "phone_number", "position"], + "purchasing": ["is_leased", "is_purchased"], + "general": ["jamf_version", "initial_entry_date_epoch", "last_contact_time_epoch"], +} + + +def getSubFields(json_data, prop_name): + sub_fields_response = {} + for property in composite_map[prop_name]: + try: + sub_fields_response[property] = json_data[prop_name][property] + except: + logging.debug(f"{property} does not exist.") + return sub_fields_response + + +url = params["connect_jamf_url"] +resolve_url = f'{url}/JSSResource/computers/' +username = params["connect_jamf_username"] +password = params["connect_jamf_password"] + +# Proxy support +jamf_proxy_enabled = params.get("connect_proxy_enable") +jamf_proxy_basic_auth_ip = params.get("connect_proxy_ip") +jamf_proxy_port = params.get("connect_proxy_port") +jamf_proxy_username = params.get("connect_proxy_username") +jamf_proxy_password = params.get("connect_proxy_password") +opener = jamf_lib.handle_proxy_configuration(jamf_proxy_enabled, + jamf_proxy_basic_auth_ip, + jamf_proxy_port, + jamf_proxy_username, + jamf_proxy_password, ssl_context) + +response = {} +if "mac" in params: + uppercase_mac = params["mac"].upper() + colon_mac = ":".join(uppercase_mac[i:i+2] for i in range(0, 12, 2)) + resolve_url = resolve_url + "macaddress/" + colon_mac +elif "dhcp_hostname_v2" in params: + resolve_url = resolve_url + "name/" + params["dhcp_hostname_v2"] +elif "connect_globalprotect_computer_name" in params: + resolve_url = resolve_url + "name/" + \ + params["connect_globalprotect_computer_name"] +else: + logging.error("Insufficient information to query.") + +logging.info(f"The URL is: {resolve_url}") + +try: + # Build Request + resolve_request = urllib.request.Request(resolve_url) + # Add Headers + token = params.get('connect_authorization_token') + resolve_request.add_header("Authorization", f"Bearer {token}" ) + resolve_request.add_header("Accept", "application/json") + + resolve_response_handle = opener.open(resolve_request) + resolve_response = resolve_response_handle.read().decode("utf-8") + logging.debug(f"The response from Jamf is {resolve_response}") + + resolve_response_object = json.loads(resolve_response)["computer"] + + # Build Properties + properties = {} + general = resolve_response_object["general"] + for key in general_map: + properties[general_map[key]] = general[key] + properties["connect_jamf_asset_purchasing"] = getSubFields( + resolve_response_object, "purchasing") + properties["connect_jamf_user_information"] = getSubFields( + resolve_response, "location") + general_subfields = getSubFields(resolve_response_object, "general") + try: + general_subfields["initial_entry_date_epoch"] //= 1000 + general_subfields["last_contact_time_epoch"] //= 1000 + except: + logging.debug("Response does not have epoch fields.") + + properties["connect_jamf_agent_information"] = general_subfields + hardware_subfields = getSubFields(resolve_response_object, "hardware") + hardware_subfields["serial_number"] = resolve_response_object["general"]["serial_number"] + properties["connect_jamf_device_details"] = hardware_subfields + software_installed = [] + for application in resolve_response_object["software"]["applications"]: + software_installed.append(application["name"]) + + properties["connect_jamf_software_installed"] = software_installed + properties["connect_jamf_managed"] = resolve_response_object["general"]["remote_management"]["managed"] + logging.debug(f"properties {properties}") + response["properties"] = properties + +except HTTPError as e: + response["succeeded"] = False + response["error"] = f"Could not connect to Jamf. Response code: {e.code}" +except URLError as e: + response["succeeded"] = False + response["error"] = f"Could not connect to Jamf. {e.reason}" +except Exception as e: + logging.exception(e) + response["succeeded"] = False + response["error"] = f"Could not connect to Jamf. {str(e)}" diff --git a/jamf/jamf 1.2.2/resolve_boot.py b/jamf/jamf 1.2.2/resolve_boot.py new file mode 100644 index 00000000..07db342e --- /dev/null +++ b/jamf/jamf 1.2.2/resolve_boot.py @@ -0,0 +1,100 @@ +''' +Copyright © 2020 Forescout Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +''' +import urllib.request +from urllib.request import HTTPError, URLError + + +from xml.etree import ElementTree +import logging +boot_properties = ["filevault_status", "name", + "size", "filevault_percent", "percentage_full"] + +url = params["connect_jamf_url"] +resolve_boot_url = f"{url}/JSSResource/computers/" +username = params["connect_jamf_username"] +password = params["connect_jamf_password"] + +# Proxy support +jamf_proxy_enabled = params.get("connect_proxy_enable") +jamf_proxy_basic_auth_ip = params.get("connect_proxy_ip") +jamf_proxy_port = params.get("connect_proxy_port") +jamf_proxy_username = params.get("connect_proxy_username") +jamf_proxy_password = params.get("connect_proxy_password") +opener = jamf_lib.handle_proxy_configuration(jamf_proxy_enabled, + jamf_proxy_basic_auth_ip, + jamf_proxy_port, + jamf_proxy_username, + jamf_proxy_password, ssl_context) + +response = {} +if "mac" in params: + uppercase_mac = params["mac"].upper() + colon_mac = ":".join(uppercase_mac[i:i+2] for i in range(0, 12, 2)) + resolve_boot_url = resolve_boot_url + "macaddress/" + colon_mac +elif "dhcp_hostname_v2" in params: + resolve_boot_url = resolve_boot_url + "name/" + params["dhcp_hostname_v2"] +elif "connect_globalprotect_computer_name" in params: + resolve_boot_url = resolve_boot_url + "name/" + \ + params["connect_globalprotect_computer_name"] +else: + logging.error("Insufficient information to query.") + +logging.info( + "The resolve_boot_url is: {resolve_boot_url}") + +try: + # resp = urllib.request.urlopen(request, context=ssl_context) + properties = {} + + # Build Request + resolve_boot_request = urllib.request.Request(resolve_boot_url) + # Add Headers + token = params.get('connect_authorization_token') + resolve_boot_request.add_header("Authorization", f"Bearer {token}" ) + resolve_boot_request.add_header("Accept", "application/xml") + + resolve_response_handle = opener.open(resolve_boot_request) + resolve_boot_object = resolve_response_handle.read().decode("utf-8") + + tree = ElementTree.fromstring(resolve_boot_object) + logging.debug(f"The response from Jamf is {str(tree)}") + boot = {} + + for partition in tree.findall("hardware/storage/device/partitions/partition"): + if partition.find("type").text == "boot": + for prop in boot_properties: + prop_value = partition.find(prop) + if prop_value is not None: + boot[prop] = prop_value.text + properties["connect_jamf_boot_device"] = boot + response["properties"] = properties + +except HTTPError as e: + response["succeeded"] = False + response["error"] = f"Could not connect to Jamf. Response code: {e.code}" +except URLError as e: + response["succeeded"] = False + response["error"] = f"Could not connect to Jamf. {e.reason}" +except Exception as e: + logging.exception(e) + response["succeeded"] = False + response["error"] = f"Could not connect to Jamf. {str(e)}" diff --git a/jamf/jamf 1.2.2/resolve_mobiledevices.py b/jamf/jamf 1.2.2/resolve_mobiledevices.py new file mode 100644 index 00000000..5e8cb850 --- /dev/null +++ b/jamf/jamf 1.2.2/resolve_mobiledevices.py @@ -0,0 +1,133 @@ +''' +Copyright © 2020 Forescout Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +''' + +import urllib.request +from urllib.request import HTTPError, URLError +import json +import logging + +general_map = { + "name": "connect_jamf_mobile_deviceName", + "id": "connect_jamf_mobile_id" +} + +composite_map = { + "general": ["capacity", "os_type", "serial_number", "phone_number", "udid", "model", "os_version", "model_number", "os_build", "model_identifier"], + "location": ["real_name", "email_address", "username", "phone_number", "position"], + "purchasing": ["is_leased", "is_purchased"], + "security": ["data_protection", "passcode_present", "passcode_compliant", "hardware_encryption", "activation_lock_enabled", "lost_mode_enabled", "lost_mode_enforced", "lost_mode_enable_issued_epoch", "jailbreak_detected"] +} + + +def getSubFields(json_data, prop_name): + sub_fields_response = {} + for property in composite_map[prop_name]: + try: + sub_fields_response[property] = json_data[prop_name][property] + except: + logging.debug(f"{property} does not exist.") + return sub_fields_response + + +url = params["connect_jamf_url"] +mobile_url = f"{url}/JSSResource/mobiledevices/" +username = params["connect_jamf_username"] +password = params["connect_jamf_password"] + +# Proxy support +jamf_proxy_enabled = params.get("connect_proxy_enable") +jamf_proxy_basic_auth_ip = params.get("connect_proxy_ip") +jamf_proxy_port = params.get("connect_proxy_port") +jamf_proxy_username = params.get("connect_proxy_username") +jamf_proxy_password = params.get("connect_proxy_password") +opener = jamf_lib.handle_proxy_configuration(jamf_proxy_enabled, + jamf_proxy_basic_auth_ip, + jamf_proxy_port, + jamf_proxy_username, + jamf_proxy_password, ssl_context) + +response = {} +if "mac" in params: + uppercase_mac = params["mac"].upper() + colon_mac = ":".join(uppercase_mac[i:i+2] for i in range(0, 12, 2)) + mobile_url = mobile_url + "macaddress/" + colon_mac +elif "dhcp_hostname_v2" in params: + mobile_url = mobile_url + "name/" + params["dhcp_hostname_v2"] +elif "connect_globalprotect_computer_name" in params: + mobile_url = mobile_url + "name/" + \ + params["connect_globalprotect_computer_name"] +else: + logging.error("Insufficient information to query.") + + +logging.info(f"The resolve_mobiledevices URL is: {mobile_url}") + + +try: + # Build Request + mobile_request = urllib.request.Request(mobile_url) + # Add Headers + token = params.get('connect_authorization_token') + mobile_request.add_header("Authorization", f"Bearer {token}" ) + mobile_request.add_header("Accept", "application/json") + + mobile_response_handle = opener.open(mobile_request) + mobile_response = mobile_response_handle.read().decode("utf-8") + logging.debug(f"The response from Jamf is {mobile_response}") + + mobile_response_object = json.loads(mobile_response)["mobile_device"] + + properties = {} + + general = mobile_response_object["general"] + for key in general_map: + properties[general_map[key]] = general[key] + properties["connect_jamf_mobile_asset_purchasing"] = getSubFields( + mobile_response_object, "purchasing") + properties["connect_jamf_mobile_user_information"] = getSubFields( + mobile_response_object, "location") + properties["connect_jamf_mobile_security"] = getSubFields( + mobile_response_object, "security") + + general_subfields = getSubFields(mobile_response_object, "general") + properties["connect_jamf_mobile_device_details"] = general_subfields + general_subfields["serial_number"] = mobile_response_object["general"]["serial_number"] + software_installed = [] + + for application in mobile_response_object["applications"]: + software_installed.append(application["application_name"]) + + properties["connect_jamf_mobile_software_installed"] = software_installed + properties["connect_jamf_mobile_managed"] = mobile_response_object["general"]["managed"] + logging.debug(f"properties {properties}") + response["properties"] = properties + +except HTTPError as e: + response["succeeded"] = False + response["error"] = f"Could not connect to Jamf. Response code: {e.code}" +except URLError as e: + response["succeeded"] = False + response["error"] = f"Could not connect to Jamf. {e.reason}" +except Exception as e: + logging.exception(e) + response["succeeded"] = False + response["error"] = f"Could not connect to Jamf. {str(e)}" diff --git a/jamf/jamf 1.2.2/resolve_policies.py b/jamf/jamf 1.2.2/resolve_policies.py new file mode 100644 index 00000000..f99e5d6f --- /dev/null +++ b/jamf/jamf 1.2.2/resolve_policies.py @@ -0,0 +1,91 @@ +''' +Copyright © 2020 Forescout Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +''' +import urllib.request +from urllib.request import HTTPError, URLError +import json + +url = params["connect_jamf_url"] +resolve_p_url = f"{url}/JSSResource/computermanagement/" +username = params["connect_jamf_username"] +password = params["connect_jamf_password"] + +# Proxy support +jamf_proxy_enabled = params.get("connect_proxy_enable") +jamf_proxy_basic_auth_ip = params.get("connect_proxy_ip") +jamf_proxy_port = params.get("connect_proxy_port") +jamf_proxy_username = params.get("connect_proxy_username") +jamf_proxy_password = params.get("connect_proxy_password") +opener = jamf_lib.handle_proxy_configuration(jamf_proxy_enabled, + jamf_proxy_basic_auth_ip, + jamf_proxy_port, + jamf_proxy_username, + jamf_proxy_password, ssl_context) + +response = {} +if "mac" in params: + uppercase_mac = params["mac"].upper() + colon_mac = ":".join(uppercase_mac[i:i+2] for i in range(0, 12, 2)) + resolve_p_url = resolve_p_url + "macaddress/" + colon_mac +elif "dhcp_hostname_v2" in params: + resolve_p_url = resolve_p_url + "name/" + params["dhcp_hostname_v2"] +elif "connect_globalprotect_computer_name" in params: + resolve_p_url = resolve_p_url + "name/" + \ + params["connect_globalprotect_computer_name"] +else: + logging.error("Insufficient information to query.") + +logging.info(f"The URL is: {resolve_p_url}") + + +try: + # Build Request + resolve_p_request = urllib.request.Request(resolve_p_url) + # Add Headers + token = params.get('connect_authorization_token') + resolve_p_request.add_header("Authorization", f"Bearer {token}" ) + resolve_p_request.add_header("Accept", "application/json") + + resolve_p_response_handle = opener.open(resolve_p_request) + resolve_p_response = resolve_p_response_handle.read().decode("utf-8") + + request_p_object = json.loads(resolve_p_response) + logging.debug(f"The response from Jamf is {request_p_object}") + properties = {} + general = request_p_object["computer_management"] + policies = general["policies"] + policy_list = [] + + for policy in policies: + policy_list.append(policy["name"]) + properties["connect_jamf_policies"] = policy_list + response["properties"] = properties + +except HTTPError as e: + response["succeeded"] = False + response["error"] = f"Could not connect to Jamf. Response code: {e.code}" +except URLError as e: + response["succeeded"] = False + response["error"] = f"Could not connect to Jamf. {e.reason}" +except Exception as e: + logging.exception(e) + response["succeeded"] = False + response["error"] = f"Could not connect to Jamf. {str(e)}" diff --git a/jamf/jamf 1.2.2/system.conf b/jamf/jamf 1.2.2/system.conf new file mode 100644 index 00000000..ad507e2c --- /dev/null +++ b/jamf/jamf 1.2.2/system.conf @@ -0,0 +1,94 @@ +{ + "name": "Jamf", + "version": "1.2.2", + "author": "Forescout", + "testEnable": true, + "panels": [{ + "title": "Jamf Connection", + "description": "Jamf Connection", + "fields": [{ + "display": "URL", + "field ID": "connect_jamf_url", + "type": "shortString", + "mandatory": "true", + "add to column": "true", + "show column": "true", + "tooltip": "URL" + }, + { + "display": "Account Username", + "field ID": "connect_jamf_username", + "type": "shortString", + "mandatory": "true", + "add to column": "true", + "show column": "false", + "tooltip": "Application ID" + }, + { + "display": "Account Password", + "field ID": "connect_jamf_password", + "type": "encrypted", + "mandatory": "true", + "tooltip": "Application Secret" + }, + { + "certification validation": true + }, + { + "authorization": true, + "display": "Authorization refresh timer (minutes)", + "min": 1, + "max": 100, + "value": 25 + } + ] + }, + { + "focal appliance": true, + "title": "Assign CounterACT Devices", + "description": "Select the connecting CounterACT device that will communicate with the targeted Jamf instance, including requests by other CounterACT devices. Specific CounterACT devices assigned here cannot be assigned to another server elsewhere.

If you do not assign specific devices, by default, all devices will be assigned to one server. This server becomes known as the Default Server." + }, + { + "proxy server": true, + "title": "Proxy Server", + "description": "Select a Proxy Server device to manage all communication between CounterACT and Jamf." + }, + { + "title": "Jamf Options", + "description": "Jamf Options", + "fields": [{ + "rate limiter": true, + "display": "Number of API queries per second", + "unit": 1, + "min": 1, + "max": 10000, + "add to column": "true", + "show column": "false", + "value": 10 + }, + { + "host discovery": true, + "display": "Discovery Frequency (in minutes)", + "max": 300000, + "add to column": "true", + "show column": "false", + "value": 3600 + }, + { + "display": "Computer Poll (must enable Host Discovery) ", + "field ID": "connect_jamf_computer_poll", + "type": "boolean", + "mandatory": "false", + "tooltip": "Enable polling Computers from JAMF" + }, + { + "display": "Mobile Device Poll (must enable Host Discovery)", + "field ID": "connect_jamf_mobiledevice_poll", + "type": "boolean", + "mandatory": "false", + "tooltip": "Enable polling Mobile Devices from JAMF" + } + ] + } + ] +} \ No newline at end of file diff --git a/jamf/jamf 1.2.2/test.py b/jamf/jamf 1.2.2/test.py new file mode 100644 index 00000000..01c6f81b --- /dev/null +++ b/jamf/jamf 1.2.2/test.py @@ -0,0 +1,59 @@ +''' +Copyright © 2020 Forescout Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +''' + +import urllib.request +from urllib.request import HTTPError, URLError +import logging + +url = params["connect_jamf_url"] +username = params["connect_jamf_username"] +password = params["connect_jamf_password"] + +# Proxy support +jamf_proxy_enabled = params.get("connect_proxy_enable") +jamf_proxy_basic_auth_ip = params.get("connect_proxy_ip") +jamf_proxy_port = params.get("connect_proxy_port") +jamf_proxy_username = params.get("connect_proxy_username") +jamf_proxy_password = params.get("connect_proxy_password") + +test_url = f'{url}/JSSResource/computers' +response = {} + +try: + token = params.get('connect_authorization_token') + + if token is not "" and token is not None: + response["succeeded"] = True + response["result_msg"] = "Successfully connected to Jamf." + else: + response["succeeded"] = False + response["error"] = "Unable to obtain Token.\n See logs for details:\n /usr/local/forescout/plugin/connect_module/python_logs/python_server.log" +except HTTPError as e: + response["succeeded"] = False + response["error"] = f"Could not connect to Jamf. Response code: {e.code}" +except URLError as e: + response["succeeded"] = False + response["error"] = f"Could not connect to Jamf. {e.reason}" +except Exception as e: + logging.exception(e) + response["succeeded"] = False + response["error"] = f"Could not connect to Jamf. {str(e)}"