├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md └── bit9PlatformAPI ├── README.md ├── docs └── Bit9 Platform API v1.pdf └── examples ├── README.md ├── VirusTotalConnector.py ├── bit9_list_of_comps_and_enf_levels.py ├── common ├── __init__.py └── bit9api.py ├── connectors ├── LastLine.py ├── VirusTotal.py └── __init__.py ├── samples ├── ApproveFiles.py ├── CBSensorReport.py ├── LocalApproval.py ├── MoveComputers.py ├── Templating.py ├── TranslateSyncFlags.py ├── change_diagnostics.py └── initiate_cache_check.py └── virustotal.ini.example /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.py text diff=python 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Unit test / coverage reports 30 | htmlcov/ 31 | .tox/ 32 | .coverage 33 | .cache 34 | nosetests.xml 35 | coverage.xml 36 | 37 | # Translations 38 | *.mo 39 | *.pot 40 | 41 | # Django stuff: 42 | *.log 43 | 44 | # Sphinx documentation 45 | docs/_build/ 46 | 47 | # PyBuilder 48 | target/ 49 | 50 | # PyCharm 51 | .idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Bit9 + Carbon Black 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bit9platform 2 | 3 | ## Note that this Python module is deprecated and replaced with cbapi - see https://cbapi.readthedocs.io 4 | 5 | This folder contains documentation and examples related to the Carbon Black Enterprise Protection API. 6 | If you want to learn more about Carbon Black Enterprise Protection (formerly Bit9), please visit [www.carbonblack.com](https://www.carbonblack.com/products/carbon-black-enterprise-protection/) 7 | -------------------------------------------------------------------------------- /bit9PlatformAPI/README.md: -------------------------------------------------------------------------------- 1 | ## **Introduction** 2 | 3 | This document is intended for programmers who want to write code to interact with the Bit9 Platform using custom scripts or integrate with other applications. The Bit9 API is a RESTful API that can be consumed over the HTTPS protocol using any language that can issue GET/POST/PUT URI requests and interpret JSON responses. 4 | 5 | ### **Disclaimer** 6 | 7 | By accessing and/or using the API and Documentation provided on this site, you hereby agree to the following terms: 8 | You may access and use the API and Documentation only for your own internal business purposes and in connection with your authorized use of Bit9 software. 9 | 10 | Title to the API and the Documentation, and all intellectual property rights applicable thereto, shall at all times remain solely and exclusively with Bit9 and Bit9�s licensors, and you shall not take any action inconsistent with such title. 11 | 12 | THE API AND RELATED DOCUMENTATION ARE PROVIDED �AS IS� WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. 13 | 14 | IN NO EVENT SHALL BIT9 BE LIABLE FOR SPECIAL, INCIDENTAL, CONSEQUENTIAL, EXEMPLARY OR OTHER INDIRECT DAMAGES OR FOR DIRECT DAMAGES ARISING OUT OF OR RESULTING FROM YOUR ACCESS OR USE OF THE API AND DOCUMENTATION, EVEN IF BIT9 IS ADVISED OF OR AWARE OF THE POSSIBILITY OF SUCH DAMAGES. 15 | 16 | ### **Versioning** 17 | 18 | Current version of Bit9 API is v1. All API calls are based in address https:///api/bit9platform/v1 19 | 20 | ### **Authentication** 21 | 22 | Bit9 APIs are authenticated through the API token. This token has to be placed inside each HTTP request 'X-Auth-Token' header. The API token is tied to the console user. To obtain the API token, ask the Bit9 Server administrator to generate special user and token for you. A good practice is to have separate console users with minimum required access controls for each API client. 23 | 24 | ### **Access Controls** 25 | 26 | An API client has the same access level as its corresponding console user. For example, in order to get access to the 'event' object, the user associated with API token will need permission to view events. Required permissions are listed with each API in this document. If caller lacks the required permissions, HTTP error 401 - Unauthorized will be returned. 27 | 28 | ### **Responses** 29 | 30 | Successful calls will return either HTTP 200 - OK or HTTP 201 - Created, depending if request modified/deleted an existing object or created a new object, respectively. 31 | In case of POST and PUT, the response will contain the body of the modified or created object in the content, and the URI of the created or modified object in URL property of the response. 32 | In the case of GET, the response will contain the body of the searched object(s) in the content. 33 | Failed calls will return errors in the range 400-599. This is usually one of the following: 34 | HTTP 400 - Bad request - Usually means that request contains unexpected parameters. More details about error can be found in the response content. 35 | HTTP 401 - Unauthorized - Either authentication (invalid token) or access control (missing RBAC) error. 36 | HTTP 403 - Forbidden - Specified object cannot be accessed or changed. 37 | HTTP 404 - Not found - Object referenced in the request cannot be found. 38 | HTTP 503 - Service unavailable - Cannot return object at this moment because service is unavailable. This can happen if too many file downloads are happening at the same time. You can try later. 39 | 40 | ### **Searching** 41 | 42 | Searching is done through the GET request, by passing search elements as URL query parts: 43 | v1/computer?q=__&q=__...&group=__&sort=__&offset=__&offset=__ 44 | The following sections describe these query parts. 45 | 46 | #### **Query Condition** 47 | 48 | Multiple conditions can be added, and each has to be satisfied for the result set. 49 | Individual conditions can have one or multiple subconditions, separated with '|' (pipe) symbol. A condition contains three parts: name, operator and value. 50 | 51 | - Name is any valid field in the object that is being queried.  52 | - Operator is any of valid operators (see below). All operators consist of a single character.  53 | - Value is compared with operator and depends on field type.  54 | 55 | Possible operators are: 56 | 57 | - : results in LIKE comparison for strings, and = comparisons for other types. Note that LIKE comparison for strings results in '=' comparison if the string doesn't contain wildchars. String comparison is case insensitive.  58 | - ! results in NOT LIKE comparison for strings, and <> comparison for other types. Note that NOT LIKE comparison for strings results in '<>' comparison if the string doesn't contain wildchars. String comparison is case insensitive.   59 | - < Less than - can be used for both strings and numerical values   60 | - > Greater than - can be used for both strings and numerical values   61 | - + logical AND operation (valid only for numerical values). True if value has all bits set as in operand. This can be used to check existence of given flag in a field   62 | - - logical NAND operation (valid only for numerical values). True if value has none of the bits in the operand. This can be used to check non-existence of given flag in a field   63 | - | separating values with | (pipe) symbol will cause both values to be included in the condition. Example "q=fileName:test1.exe|test2.exe" will match all objects where filename is either test1.exe or test2.exe. Note that negative conditions (- and !) will exclude entries that match either of included values.   64 | 65 | Example of valid filter segment: 66 | 67 | ##### Request: 68 | 69 | [GET] https://myServer/api/bit9platform/v1/Computer?q=ipAddress:fe00\*|ff00\*&q=computerTag!&q=dateCreated>-10h 70 | 71 | ##### Resulting SQL query condition evaluated: 72 | 73 | ... WHERE (ipAddress LIKE 'fe00%' OR ipAddress LIKE 'ff00%') 74 | 75 |     AND computerTag NOT LIKE '' 76 | 77 |     AND dateCreated>DATEADD(HOUR,-10, GETUTCDATE()) 78 | 79 | Note: All string matching will be case insensitive 80 | 81 | #### **Limiting Results and Getting Result Count** 82 | 83 | Attributes: &offset=x&limit=y, where x is offset in data set, and y is maximum number of results to retrieve 84 | 85 | Special values for limit are 0 and -1: 86 | 87 | - If not specified: First 1000 results will be returned. 88 | - If set to -1: Only result count will be returned, without actual results. Offset parameter is ignored in this case. 89 | - If set to 0: All results will be returned. Offset parameter is ignored in this case. 90 | Note that some result sets could be very large, resulting in query timeout. Therefore, unless you know that query will not return more than 1000 results, it is recommended to retrieve data in chunks using offset and limit. 91 | 92 | 93 | Here is example on how to get result count from a query: 94 | 95 | ##### Request: 96 | 97 | [GET] https://myServer/api/bit9platform/computer?limit=-1 98 | 99 | ##### Response: 100 | 101 | {"count":1284} 102 | 103 | #### **Sorting** 104 | 105 | Sorting is optional and can be defined with a single attribute: _&sort=xyz [ASC|DESC]_ 106 | 107 | - There can be only one sorting field  108 | - Default sort order (if omitted) is ASC  109 | - xyz is field name from the result set  110 | 111 | #### **Grouping** 112 | 113 | Grouping is optional and can be defined with a single attribute: _&group=xyz_ 114 | 115 | - There can be only one grouping field  116 | - When grouping is specified, sorting is ignored � output is automatically sorted by grouping field  117 | 118 | Output of grouping is always array of objects with value and count fields. "Value" is group field value, and "count" is number of rows that have that name for the grouped field. Here is example: 119 | 120 | ##### Request: 121 | 122 | [GET] https://myServer/api/bit9platform/v1/Computer?group=osShortName 123 | 124 | ##### Response: 125 | 126 | [ 127 | 128 |     {"value":"CentOS 5","count":53}, 129 | 130 |     {"value":"CentOS 6","count":826}, 131 | 132 |     {"value":"Mac","count":2311}, 133 | 134 |     {"value":"Windows 7","count":1330} 135 | 136 | ] 137 | 138 |   139 | 140 | -------------------------------------------------------------------------------- /bit9PlatformAPI/docs/Bit9 Platform API v1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmware-archive/bit9platform/2ed99c6eea7b2e0f0d8e74f27874fd3fdb27c418/bit9PlatformAPI/docs/Bit9 Platform API v1.pdf -------------------------------------------------------------------------------- /bit9PlatformAPI/examples/README.md: -------------------------------------------------------------------------------- 1 | ## **Introduction** 2 | 3 | This document is intended for programmers who want to write code to interact with Bit9 Platform using custom scripts or from other applications. Bit9 API is a RESTful API that can be consumed over HTTPS protocol using any language that can create get URI requests and post/put JSON requests as well as interpret JSON responses. 4 | By accessing and/or using the API and Documentation provided on this site, you hereby agree to the following terms: 5 | 6 | ### **Disclaimer** 7 | 8 | You may access and use the API and Documentation only for your own internal business purposes and in connection with your authorized use of Bit9 software. 9 | 10 | Title to the API and the Documentation, and all intellectual property rights applicable thereto, shall at all times remain solely and exclusively with Bit9 and Bit9�s licensors, and you shall not take any action inconsistent with such title. 11 | 12 | THE API AND RELATED DOCUMENTATION ARE PROVIDED �AS IS� WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. 13 | 14 | IN NO EVENT SHALL BIT9 BE LIABLE FOR SPECIAL, INCIDENTAL, CONSEQUENTIAL, EXEMPLARY OR OTHER INDIRECT DAMAGES OR FOR DIRECT DAMAGES ARISING OUT OF OR RESULTING FROM YOUR ACCESS OR USE OF THE API AND DOCUMENTATION, EVEN IF BIT9 IS ADVISED OF OR AWARE OF THE POSSIBILITY OF SUCH DAMAGES. 15 | 16 | ### **Versioning** 17 | 18 | Current version of Bit9 API is v1. All API calls are based in address https:///api/bit9platform/v1 19 | 20 | ### **Authentication** 21 | 22 | Bit9 APIs are authenticated through the API token. This token has to be placed inside each HTTP request's 'X-Auth-Token' header. API token is tied to the console user. To obtain the API token, ask Bit9 Server administrator to generate special user and token for you. Best practice is to have separate console user with minimum required access controls for each API client. 23 | 24 | ### **Access Controls** 25 | 26 | API client has same access level as its corresponding console user. For example, in order to get access to the 'event' object, user associated with API token will need permission to view events. Required permissions are listed with each API in this document. If caller lacks required privileges, HTTP error 401 - Unauthorized will be returned. 27 | 28 | ### **Responses** 29 | 30 | Successful calls will return either HTTP 200 - OK or HTTP 201 - Created, depending if request created a new object or just modified/deleted existing one. 31 | In case of POST and PUT, response will contain body of the modified or created object in the content, and also URI of created or modified object in url property of the response. 32 | In case of GET, response will contain body of the searched object(s) in the content. 33 | Failed calls will return errors in range 400-599, most often: 34 | HTTP 400 - Bad request - Usually means that request contains unexpected parameters. More details about error can be found in the response content. 35 | HTTP 401 - Unauthorized - Either authentication (invalid token) or access control (missing RBAC) error. 36 | HTTP 403 - Forbidden - Specified object cannot be accessed or changed. 37 | HTTP 404 - Not found - Object referenced in the request cannot be found. 38 | HTTP 503 - Service unavailable - Cannot return object at this moment because service is unavailable. This can happen if too many file downloads are happening at the same time. You can try later. 39 | 40 | ### **Searching** 41 | 42 | Searching is done through the GET request, by passing search elements as URL query parts: 43 | v1/computer?q=__&q=__...&group=__&sort=__&offset=__&offset=__ 44 | Following sections describe these query parts. 45 | 46 | #### **Query Condition** 47 | 48 | Multiple conditions can be added, and each has to be satisfied for the result set. 49 | Individual condition can have one or multiple subconditions, separated with '|' (pipe) symbol Condition contains three parts: name, operator and value. 50 | 51 | - Name is any valid field in the object that is being queried  52 | - Operator is any of valid operators (see below). All operators consist of a single character  53 | - Value is compared with operator and depends on field type.  54 | 55 | Possible operators are: 56 | 57 | - : results in LIKE comparison for strings, and = comparisons for other types. Note that LIKE comparison for strings results in '=' comparison if string doesn't contain wildchars. String comparison is case insensitive.  58 | - ! results in NOT LIKE comparison for strings, and <> comparison for other types. Note that NOT LIKE comparison for strings results in '<>' comparison if string doesn't contain wildchars. String comparison is case insensitive.   59 | - < Less then - can be used for both strings and numerical values   60 | - > Greater then - can be used for both strings and numerical values   61 | - + logical AND operation (valid only for numerical values). True if value has all bits as in operand. This can be used to check existence of given flag in a field   62 | - - logical NAND operation (valid only for numerical values). True if value has none of the bits in the operand. This can be used to check non-existence of given flag in a field   63 | - | separating values with | (pipe) symbol will cause both values ot be included in the condition. Example "q=fileName:test1.exe|test2.exe" will match all objects where filename is either test1.exe or test2.exe. Note that negative conditions (- and !) will exclude entries that match either of included values.   64 | 65 | Example of valid filter segment: 66 | 67 | ##### Request: 68 | 69 | [GET] https://myServer/api/bit9platform/v1/Computer?q=ipAddress:fe00\*|ff00\*&q=computerTag!&q=dateCreated>-10h 70 | 71 | ##### Resulting SQL query condition evaluated: 72 | 73 | ... WHERE (ipAddress LIKE 'fe00%' OR ipAddress LIKE 'ff00%') 74 | 75 |     AND computerTag NOT LIKE '' 76 | 77 |     AND dateCreated>DATEADD(HOUR,-10, GETUTCDATE()) 78 | 79 | Note: All string matching will be case insensitive 80 | 81 | #### **Limiting Results and Getting Result Count** 82 | 83 | Attributes: &offset=x&limit=y, where x is offset in data set, and y is maximum number of results to retrieve 84 | 85 | Special values for limit are 0 and -1: 86 | 87 | - If not specified: First 1000 results will be returned. 88 | - If set to -1: Only result count will be returned, without actual results. Offset parameter is ignored in this case. 89 | - If set to 0: All results will be returned. Offset parameter is ignored in this case. 90 | Note that some result sets could be very large, resulting in query timeout. Therefore, unless you know that query will not return more than 1000 results, it is recommended to retrieve data in chunks using offset and limit. 91 | 92 | 93 | Here is example on how to get result count from a query: 94 | 95 | ##### Request: 96 | 97 | [GET] https://myServer/api/bit9platform/computer?limit=-1 98 | 99 | ##### Response: 100 | 101 | {"count":1284} 102 | 103 | #### **Sorting** 104 | 105 | Sorting is optional and can be defined with a single attribute: _&sort=xyz [ASC|DESC]_ 106 | 107 | - There can be only one sorting field  108 | - Default sort order (if omitted) is ASC  109 | - xyz is field name from the result set  110 | 111 | #### **Grouping** 112 | 113 | Grouping is optional and can be defined with a single attribute: _&group=xyz_ 114 | 115 | - There can be only one grouping field  116 | - When grouping is specified, sorting is ignored � output is automatically sorted by grouping field  117 | 118 | Output of grouping is always array of objects with value and count fields. "Value" is group field value, and "count" is number of rows that have that name for the grouped field. Here is example: 119 | 120 | ##### Request: 121 | 122 | [GET] https://myServer/api/bit9platform/v1/Computer?group=osShortName 123 | 124 | ##### Response: 125 | 126 | [ 127 | 128 |     {"value":"CentOS 5","count":53}, 129 | 130 |     {"value":"CentOS 6","count":826}, 131 | 132 |     {"value":"Mac","count":2311}, 133 | 134 |     {"value":"Windows 7","count":1330} 135 | 136 | ] 137 | 138 |   139 | 140 | -------------------------------------------------------------------------------- /bit9PlatformAPI/examples/VirusTotalConnector.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from common import bit9api 3 | from connectors import VirusTotal 4 | from ConfigParser import RawConfigParser 5 | import sys 6 | import requests 7 | 8 | # logging 9 | logging.basicConfig(format='%(asctime)s %(levelname)s:%(message)s', level=logging.DEBUG) 10 | log = logging.getLogger(__name__) 11 | logging.getLogger("requests").setLevel(logging.WARNING) 12 | logging.getLogger("urllib3").setLevel(logging.WARNING) 13 | 14 | 15 | def main(): 16 | inifile = RawConfigParser({ 17 | "bit9_server_url": "https://localhost", 18 | "bit9_server_sslverify": False, 19 | "bit9_server_token": None, 20 | "vt_api_key": None, 21 | "retrieve_files": True, 22 | "upload_binaries_to_vt": False, 23 | "download_location": None, 24 | "connector_name": "VirusTotal", 25 | "log_file": None, 26 | }) 27 | inifile.read("virustotal.ini") 28 | 29 | config = {} 30 | 31 | config["bit9_server_url"] = inifile.get("bridge", "bit9_server_url") 32 | config["bit9_server_token"] = inifile.get("bridge", "bit9_server_token") 33 | config["bit9_server_sslverify"] = inifile.getboolean("bridge", "bit9_server_sslverify") 34 | config["vt_api_key"] = inifile.get("bridge", "vt_api_key") 35 | config["retrieve_files"] = inifile.getboolean("bridge", "retrieve_files") 36 | config["download_location"] = inifile.get("bridge", "download_location") 37 | config["connector_name"] = inifile.get("bridge", "connector_name") 38 | config["upload_binaries_to_vt"] = inifile.getboolean("bridge", "upload_binaries_to_vt") 39 | 40 | log_file = inifile.get("bridge", "log_file") 41 | if log_file: 42 | file_handler = logging.FileHandler(log_file) 43 | formatter = logging.Formatter('%(asctime)s %(levelname)s:%(message)s') 44 | file_handler.setFormatter(formatter) 45 | file_handler.setLevel(logging.DEBUG) 46 | logging.getLogger().addHandler(file_handler) 47 | 48 | if not config["vt_api_key"]: 49 | log.fatal("Cannot start without a valid VirusTotal API key, exiting") 50 | return 1 51 | 52 | if not config["bit9_server_token"]: 53 | log.fatal("Cannot start without a valid Bit9 server API token, exiting") 54 | return 1 55 | 56 | if not config["bit9_server_sslverify"]: 57 | requests.packages.urllib3.disable_warnings() 58 | 59 | log.info("Configuration:") 60 | for k,v in config.iteritems(): 61 | log.info(" %-20s: %s" % (k,v)) 62 | 63 | bit9 = bit9api.bit9Api( 64 | config["bit9_server_url"], 65 | token=config["bit9_server_token"], 66 | ssl_verify=config["bit9_server_sslverify"] 67 | ) 68 | 69 | vt = VirusTotal.virusTotalConnector( 70 | bit9, 71 | vt_token=config["vt_api_key"], 72 | allow_uploads=config["upload_binaries_to_vt"], # Allow VT connector to upload binary files to VirusTotal 73 | connector_name=config["connector_name"], 74 | download_location=config["download_location"] 75 | ) 76 | 77 | log.info("Starting VirusTotal processing loop") 78 | vt.start() 79 | 80 | 81 | if __name__ == '__main__': 82 | sys.exit(main()) 83 | 84 | -------------------------------------------------------------------------------- /bit9PlatformAPI/examples/bit9_list_of_comps_and_enf_levels.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from pprint import pprint 4 | import logging, csv, os, requests 5 | from bit9api import bit9Api 6 | 7 | logging.basicConfig() 8 | requests.packages.urllib3.disable_warnings() 9 | userhome = os.path.expanduser('~') 10 | csv_name = userhome + '/Desktop/test.csv' 11 | 12 | server='https://bit9server.bit9se.com/' 13 | api_token= 'AACB5C5F-D9B4-4694-AB9A-8640FF79D401' 14 | 15 | bit9 = bit9Api (server, token=api_token, ssl_verify=False) 16 | search_conditions = [''] 17 | #search_conditions = ['uninstalled:False'] 18 | 19 | comps = bit9.search('v1/computer', search_conditions) 20 | 21 | ''' 22 | Current enforcement level. Can be one of: 23 | 20=High (Block Unapproved) 24 | 30=Medium (Prompt Unapproved) 25 | 35=Local approval 26 | 40=Low (Monitor Unapproved) 27 | 60=None (Visibility) 28 | 80=None (Disabled) 29 | ''' 30 | 31 | enf_dict={20:'high', 30:'medium', 35:'local_approval', 40:'low', 60:'visibility_only', 80:'agent_disabled'} 32 | 33 | # For every found computer, write out the name, enforcement level, ip to a csv 34 | b = open(csv_name, 'wb') 35 | a = csv.writer(b) 36 | a.writerow(['host','enforcement_level', 'ip_address', 'uninstalled_state', 'policyName', 'last_checkin']) 37 | for c in comps: 38 | a.writerow([c['name'], enf_dict[c['enforcementLevel']], c['ipAddress'], c['uninstalled'], c['policyName'],c['lastPollDate']]) 39 | print c['name'], enf_dict[c['enforcementLevel']], c['ipAddress'], c['uninstalled'], c['policyName'], c['lastPollDate'] 40 | b.close() 41 | 42 | 43 | '''Available computer fields as of Bit9 7.2.1.710 44 | CLIPassword 45 | SCEPStatus 46 | agentCacheSize 47 | agentMemoryDumps 48 | agentQueueSize 49 | agentVersion 50 | automaticPolicy 51 | cbSensorFlags 52 | cbSensorId 53 | cbSensorVersion 54 | ccFlags 55 | ccLevel 56 | clVersion 57 | computerTag 58 | connected 59 | dateCreated 60 | daysOffline 61 | debugDuration 62 | debugFlags 63 | debugLevel 64 | deleted 65 | description 66 | disconnectedEnforcementLevel 67 | enforcementLevel 68 | forceUpgrade 69 | hasHealthCheckErrors 70 | id 71 | initializing 72 | ipAddress 73 | kernelDebugLevel 74 | lastPollDate 75 | lastRegisterDate 76 | localApproval 77 | macAddress 78 | machineModel 79 | memorySize 80 | name 81 | osName 82 | osShortName 83 | platformId 84 | policyId 85 | policyName 86 | policyStatusDetails 87 | prioritized 88 | processorCount 89 | processorModel 90 | processorSpeed 91 | refreshFlags 92 | supportedKernel 93 | syncFlags 94 | syncPercent 95 | systemMemoryDumps 96 | tamperProtectionActive 97 | tdCount 98 | template 99 | templateCloneCleanupMode 100 | templateCloneCleanupTime 101 | templateCloneCleanupTimeScale 102 | templateComputerId 103 | templateDate 104 | templateTrackModsOnly 105 | uninstalled 106 | upgradeError 107 | upgradeErrorCount 108 | upgradeErrorTime 109 | upgradeStatus 110 | users 111 | virtualPlatform 112 | virtualized 113 | ''' -------------------------------------------------------------------------------- /bit9PlatformAPI/examples/common/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'droglic' 2 | -------------------------------------------------------------------------------- /bit9PlatformAPI/examples/common/bit9api.py: -------------------------------------------------------------------------------- 1 | """ 2 | Python bindings for Bit9Platform API 3 | 4 | Copyright Bit9, Inc. 2015 5 | support@bit9.com 6 | 7 | Disclaimer 8 | +++++++++++++++++++ 9 | By accessing and/or using the samples scripts provided on this site (the "Scripts"), you hereby agree to the following terms: 10 | The Scripts are examples provided for purposes of illustration only and are not intended to represent specific 11 | recommendations or solutions for API integration activities as use cases can vary widely. 12 | THE SCRIPTS ARE PROVIDED "AS IS" WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. BIT9 MAKES NO REPRESENTATION 13 | OR OTHER AFFIRMATION OF FACT, INCLUDING BUT NOT LIMITED TO STATEMENTS REGARDING THE SCRIPTS' SUITABILITY FOR USE OR PERFORMANCE. 14 | IN NO EVENT SHALL BIT9 BE LIABLE FOR SPECIAL, INCIDENTAL, CONSEQUENTIAL, EXEMPLARY OR OTHER INDIRECT DAMAGES OR FOR DIRECT 15 | DAMAGES ARISING OUT OF OR RESULTING FROM YOUR ACCESS OR USE OF THE SCRIPTS, EVEN IF BIT9 IS ADVISED OF OR AWARE OF THE 16 | POSSIBILITY OF SUCH DAMAGES. 17 | 18 | """ 19 | 20 | import json 21 | import requests 22 | import logging 23 | 24 | log = logging.getLogger(__name__) 25 | 26 | 27 | class bit9Api(object): 28 | def __init__(self, server, ssl_verify=True, token=None): 29 | """ Requires: 30 | server - URL to the Bit9Platform server. Usually the same as 31 | the web GUI. 32 | sslVerify - verify server SSL certificate 33 | token - this is token for API interface provided by Bit9 administrator 34 | """ 35 | 36 | if not server.startswith("https"): 37 | raise TypeError("Server must be URL: e.g, https://bit9server.local") 38 | 39 | if token is None: 40 | raise TypeError("Missing required authentication token.") 41 | 42 | self.server = server.rstrip("/") 43 | if '/api/bit9platform' not in self.server: 44 | self.server = self.server + '/api/bit9platform' 45 | self.sslVerify = ssl_verify 46 | self.tokenHeader = {'X-Auth-Token': token} 47 | self.tokenHeaderJson = {'X-Auth-Token': token, 'content-type': 'application/json'} 48 | 49 | # Private function that downloads file in chunks 50 | def __download_file(self, obj_id, obj_name, local_path, chunk_size_kb=10): 51 | # NOTE the stream=True parameter 52 | url = self.server + '/' + obj_name + '?id=' + str(obj_id) + '&downloadFile=true' 53 | r = requests.get(url, headers=self.tokenHeaderJson, verify=self.sslVerify, stream=True) 54 | r.raise_for_status() 55 | n = 0 56 | with open(local_path, 'wb') as f: 57 | for chunk in r.iter_content(chunk_size=chunk_size_kb*1024): 58 | if chunk: # filter out keep-alive new chunks 59 | f.write(chunk) 60 | f.flush() 61 | n += 1 62 | 63 | def __check_result(self, r): 64 | if 400 <= r.status_code < 500: 65 | log.error('%s Client Error: %s, %s' % (r.status_code, r.reason, r.text)) 66 | elif 500 <= r.status_code < 600: 67 | log.error('%s Server Error: %s, %s' % (r.status_code, r.reason, r.text)) 68 | elif r.text != '': 69 | return r.json() 70 | return False 71 | 72 | # Download file from server to the local file system from fileUpload object 73 | def retrieve_uploaded_file(self, obj_id, local_path): 74 | return self.__download_file(obj_id, 'v1/fileUpload', local_path) 75 | 76 | # Download file from server to the local file system from pendingAnalysis object 77 | def retrieve_analyzed_file(self, obj_id, local_path): 78 | return self.__download_file(obj_id, 'v1/pendingAnalysis', local_path) 79 | 80 | # Retrieve object using HTTP GET request. Note that this function supports searching as well. 81 | # Optional parameters are obj_id that attempts to retrieve specific object, or url_params that can be used 82 | # for searching 83 | def retrieve(self, api_obj, obj_id=0, url_params=''): 84 | if obj_id: 85 | api_obj = api_obj + '/' + str(obj_id) 86 | if url_params: 87 | url_params = '?' + url_params.lstrip("?") 88 | url = self.server + '/' + api_obj + url_params 89 | r = requests.get(url, headers=self.tokenHeaderJson, verify=self.sslVerify) 90 | return self.__check_result(r) 91 | 92 | # Search object for specific conditions. Optionally sort and/or group results 93 | # Offset and limit determines the output window in result set. 94 | # example: 95 | # res = bit9.search('v1/computer', ['policyName:development', 'ipAddress!192.168.0.*'], sort='name') 96 | def search(self, api_obj, search_conditions=[], sort=None, group_by=None, offset=0, limit=1000): 97 | query = '&q='.join(search_conditions) 98 | if len(query)>0: 99 | query = '&q='+query 100 | if sort and len(sort)>0: 101 | query = query + '&sort=' + sort 102 | if group_by and len(group_by)>0: 103 | query = query + '&group=' + group_by 104 | query = query + '&offset=' + str(offset) + '&limit=' + str(limit) 105 | if len(query)>0: 106 | query = '?'+query.lstrip("&") 107 | url = self.server + '/' + api_obj + query 108 | r = requests.get(url, headers=self.tokenHeaderJson, verify=self.sslVerify) 109 | return self.__check_result(r) 110 | 111 | # Create object using HTTP POST request. Note that this can also be used to update existing object 112 | def create(self, api_obj, data, url_params=''): 113 | if not data: 114 | raise TypeError("Missing object data.") 115 | if url_params: 116 | url_params = '?' + url_params.lstrip("?") 117 | url = self.server + '/' + api_obj + url_params 118 | r = requests.post(url, data=json.dumps(data), headers=self.tokenHeaderJson, verify=self.sslVerify) 119 | return self.__check_result(r) 120 | 121 | # Update object using HTTP PUT request 122 | def update(self, api_obj, data, obj_id=0, url_params=''): 123 | if not data: 124 | raise TypeError("Missing object data.") 125 | if url_params: 126 | url_params = '?' + url_params.lstrip("?") 127 | if not obj_id: 128 | obj_id = data['id'] 129 | url = self.server + '/' + api_obj + '/' + str(obj_id) + url_params 130 | r = requests.put(url, data=json.dumps(data), headers=self.tokenHeaderJson, verify=self.sslVerify) 131 | return self.__check_result(r) 132 | 133 | 134 | # Delete object using HTTP DELETE request. 135 | def delete(self, api_obj, data=None, obj_id=0, url_params=''): 136 | if not obj_id and data: 137 | obj_id = data['id'] 138 | if url_params: 139 | url_params = '?' + url_params.lstrip("?") 140 | if not obj_id: 141 | raise TypeError("Missing object data or id.") 142 | url = self.server + '/' + api_obj + '/' + str(obj_id) + url_params 143 | r = requests.delete(url, headers=self.tokenHeaderJson, verify=self.sslVerify) 144 | return self.__check_result(r) 145 | -------------------------------------------------------------------------------- /bit9PlatformAPI/examples/connectors/LastLine.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """ 3 | This is a Python script for the Lastline Analyst Connector for Bit9 Security Platform. 4 | 5 | Copyright Bit9, Inc. 2015 6 | support@bit9.com 7 | 8 | 9 | Disclaimer 10 | +++++++++++++++++++ 11 | By accessing and/or using the samples scripts provided on this site (the Scripts), you hereby agree to the following terms: 12 | The Scripts are exemplars provided for purposes of illustration only and are not intended to represent specific 13 | recommendations or solutions for API integration activities as use cases can vary widely. 14 | THE SCRIPTS ARE PROVIDED AS IS WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. BIT9 MAKES NO REPRESENTATION 15 | OR OTHER AFFIRMATION OF FACT, INCLUDING BUT NOT LIMITED TO STATEMENTS REGARDING THE SCRIPTS SUITABILITY FOR USE OR PERFORMANCE. 16 | IN NO EVENT SHALL BIT9 BE LIABLE FOR SPECIAL, INCIDENTAL, CONSEQUENTIAL, EXEMPLARY OR OTHER INDIRECT DAMAGES OR FOR DIRECT 17 | DAMAGES ARISING OUT OF OR RESULTING FROM YOUR ACCESS OR USE OF THE SCRIPTS, EVEN IF BIT9 IS ADVISED OF OR AWARE OF THE 18 | POSSIBILITY OF SUCH DAMAGES. 19 | 20 | Requirements 21 | +++++++++++++++++++ 22 | 23 | - Python 2.6 or 2.7 24 | - Bit9 API client (included) which requires requests Python module 25 | - Lastline Analysis API client (available at https://analysis.lastline.com/docs/llapi_client/analysis_apiclient.py) 26 | 27 | - Lastline API Key 28 | - Lastline API Token 29 | 30 | - Bit9 Platform Server 7.2.1 or better 31 | - Bit9 API Token (generated in Bit9 Console) 32 | - Bit9 RBAC permission enabled for 'Extend connectors through API' 33 | Required python modules can be installed using tools such as easy_install or pip, e.g. 34 | easy_install requests 35 | 36 | Configuring Connector 37 | +++++++++++++++++++++++ 38 | 39 | Please update the script with appropriate LL_API_KEY, LL_API_TOKEN, B9_API_TOKEN, B9_SERVER with your Lastline and B9 API credentials. 40 | 41 | By default, the client connects to an API instance running in the Lastline cloud at https://analysis.lastline.com 42 | 43 | Starting Connector 44 | +++++++++++++++++++++++ 45 | 46 | Start the script. No paramters are required. Debug and Error logs will be created in the script folder. 47 | """ 48 | 49 | import os 50 | import sys 51 | import datetime 52 | import time 53 | import logging 54 | import json 55 | import analysis_apiclient 56 | import bit9api 57 | 58 | # Lastline API parameters 59 | class LastlineAPI: 60 | def __init__(self, url, key, token, strong_cert, delete_after_analysis = True): 61 | self.url = url 62 | self.key = key 63 | self.token = token 64 | self.strong_cert = strong_cert # should cert be validated 65 | self.delete_after_analysis = delete_after_analysis # should file be deleted after analysis 66 | 67 | # B9 Connector for Lastline 68 | class LastlineConnector: 69 | def __init__(self, b9_api, ll_api, download_file_location, polling_period = 30, report_store_location = False, debug_log_filename = "lastline_debug.log", error_log_filename = "lastline_error.log"): 70 | self.b9_api = b9_api 71 | self.ll_api = ll_api 72 | self.polling_period = polling_period 73 | self.download_file_location = download_file_location 74 | self.report_store_location = report_store_location 75 | self.debug_log_filename = debug_log_filename 76 | self.error_log_filename = error_log_filename 77 | 78 | # Dictionary to map B9 pending analysis requests to Lastline's tasks 79 | # Any pending requests and finished results will be kept here, together with Lastline uuid 80 | self.bit9_pending_analysis = {} 81 | 82 | # Dictionary to track LL tasks waiting for completion 83 | # Any pending results will be kept here, together with their status 84 | self.ll_tasks = {} 85 | 86 | # Track when we last checked for completed tasks 87 | self.last_checked_time = datetime.datetime.utcnow() - datetime.timedelta(days=1) 88 | 89 | def start(self): 90 | self.init_logging() 91 | try: 92 | logging.info("*** LL script starting") 93 | 94 | self.ll = analysis_apiclient.AnalysisClient(self.ll_api.url, key=self.ll_api.key, api_token=self.ll_api.token) 95 | logging.info("Connected to Lastline API [%s]" % self.ll_api.url) 96 | 97 | # Register or update our connector 98 | r = self.b9_api.create('v1/connector', {'name': 'Lastline', 'analysisName': 'Lastline', 99 | 'connectorVersion': '1.0', 'canAnalyze': 'true', 'analysisEnabled': 'true'}) 100 | connectorId = str(r['id']) 101 | logging.info("Connected to B9 API [%s]" % self.b9_api.server) 102 | 103 | except Exception as ex: 104 | logging.error(ex) 105 | return 106 | 107 | # Loop forever (until killed) 108 | while True: 109 | try: 110 | # Check with LL for any pending tasks 111 | self.fetchCompletedTasks() 112 | # Check with Bit9 Platform if we have any analysis still pending 113 | for i in self.b9_api.retrieve("v1/pendingAnalysis", url_params="connectorId=" + connectorId): 114 | # Process all B9 pending analysis requests for LL 115 | self.processOneAnalysisRequest(i) 116 | except: 117 | logging.error(sys.exc_info()[0]) 118 | logging.error("*** Exception processing requests. Will try again in %d seconds." % self.polling_period) 119 | 120 | time.sleep(self.polling_period) 121 | return 122 | 123 | def init_logging(self): 124 | logger = logging.getLogger("analysis") 125 | logger.setLevel(logging.DEBUG) 126 | 127 | #clean up any pre-existing handlers! 128 | handlers = [h for h in logger.handlers] 129 | for h in handlers: 130 | logger.removeHandler(h) 131 | 132 | #create console handler and set log level 133 | ch = logging.StreamHandler() 134 | ch.setLevel(logging.INFO) 135 | #create file handler and set log level 136 | 137 | #we overwrite old log files in each run. 138 | fh = logging.FileHandler(self.debug_log_filename, 'a') 139 | fh.setLevel(logging.DEBUG) 140 | 141 | #create file handler for the error log 142 | #each log file grows up to 1 megabyte, and we keep 4 old ones 143 | eh = logging.FileHandler(self.error_log_filename, 'a') 144 | eh.setLevel(logging.ERROR) 145 | 146 | #create formatter 147 | console_formatter = logging.Formatter("%(message)s") 148 | file_formatter = logging.Formatter("%(asctime)s - %(module)s(%(lineno)d) - %(message)s") 149 | 150 | #add formatter to ch and fh 151 | ch.setFormatter(console_formatter) 152 | fh.setFormatter(file_formatter) 153 | eh.setFormatter(file_formatter) 154 | #add ch and fh to logger 155 | logger.addHandler(ch) 156 | logger.addHandler(fh) 157 | logger.addHandler(eh) 158 | 159 | logging.root = logger 160 | 161 | def uploadFileToLL(self,pa): 162 | uuid = None 163 | isError = False 164 | file = None 165 | downloaded = False 166 | try: 167 | fileName = pa['fileName'].strip() 168 | 169 | if self.download_file_location is not None: 170 | # This is if we want to locally download file from Bit9 171 | # (in the case shared folder is not accessible) 172 | localFilePath = self.download_file_location + pa['fileName'] 173 | self.b9_api.retrieve_analyzed_file(pa['id'], localFilePath) 174 | logging.debug("Downloaded file '%s'" % localFilePath) 175 | downloaded = True 176 | else: 177 | # Easier option, if Bit9 shared folder can be accessed directly 178 | localFilePath = pa['uploadPath'] 179 | 180 | file = open(localFilePath, 'rb') 181 | logging.debug("Submitting '%s' to LL for analysis." % localFilePath) 182 | submit_result = self.ll.submit_file(file, filename=fileName, verify=self.ll_api.strong_cert, delete_after_analysis=self.ll_api.delete_after_analysis) 183 | logging.debug("Submit result: %s" % str(submit_result)[:1024]) 184 | result_data = submit_result.get('data', {}) 185 | 186 | # we got LL uuid. We will need it to check status of the scan at later time 187 | uuid = result_data['task_uuid'] 188 | 189 | # Tell Bit9 that we are waiting for the scan to finish 190 | pa['analysisStatus'] = 1 # (status: Analyzing) 191 | pa['analysisResult'] = 0 # (status: Unknown) 192 | 193 | # Update Bit9 status for this file 194 | self.b9_api.update('v1/pendingAnalysis', pa) 195 | finally: 196 | # Delete downloaded file without exception 197 | if file != None: 198 | file.close() 199 | if (downloaded): 200 | try: 201 | os.remove(localFilePath) 202 | logging.debug("Removed downloaded file '%s'" % localFilePath) 203 | except OSError: 204 | pass 205 | return uuid 206 | 207 | def reportResultToBit9(self, pa, scanResults): 208 | # We have results. Create our Bit9 notification 209 | fileAnalysisId = pa['id'] 210 | md5 = pa['md5'] 211 | sha1 = pa['sha1'] 212 | fileName = pa['fileName'] 213 | 214 | notification = { 215 | 'fileAnalysisId': fileAnalysisId, 216 | 'product': 'Lastline', 217 | 'appliance': self.ll_api.url.replace("https://", "") 218 | } 219 | if 'https://analysis.lastline.com' not in self.ll_api.url: 220 | externalUrl = '%s/malscape/#/task/%s' % ( self.ll_api.url, scanResults['task_uuid']) 221 | notification['externalUrl'] = externalUrl 222 | else: 223 | notification['appliance'] = notification['appliance'].replace("analysis.lastline.com", "user.lastline.com") 224 | 225 | if 'malicious_activity' in scanResults: 226 | notification['anomaly'] = ', '.join(scanResults['malicious_activity']) 227 | 228 | # Let's see if it is malicious. Use some fancy heuristics... 229 | positivesPerc = scanResults['score'] 230 | if positivesPerc > 50: 231 | notification['analysisResult'] = 3 # ...malicious 232 | notification['severity'] = 'critical'; 233 | notification['type'] = 'malicious_file'; 234 | elif positivesPerc > 0: 235 | notification['analysisResult'] = 2 # ...could be malicious 236 | notification['severity'] = 'high'; 237 | notification['type'] = 'potential_risk_file'; 238 | else: 239 | notification['analysisResult'] = 1 # clean! 240 | notification['severity'] = 'low'; 241 | notification['type'] = 'clean_file'; 242 | 243 | files = [] 244 | if ('report' in scanResults): 245 | report = scanResults['report'] 246 | if 'overview' in report: 247 | if 'analysis_engine_version' in report['overview']: 248 | notification['version'] = report['overview']['analysis_engine_version'] 249 | if 'analysis_engine' in report['overview']: 250 | notification['targetOS'] = report['overview']['analysis_engine'] 251 | if ('analysis_metadata' in report): 252 | for element in report['analysis_metadata']: 253 | if 'metadata_type' in element and 'generated_file' == element['metadata_type']: 254 | if 'file' in element: 255 | writtenFile = element['file'] 256 | file = {} 257 | if 'filename' in writtenFile: 258 | file['fileName'] = os.path.basename(writtenFile['filename']) 259 | file['filePath'] = os.path.dirname(writtenFile['filename']) 260 | if 'ext_info' in writtenFile: 261 | if 'sha1' in writtenFile['ext_info']: 262 | file['sha1'] = writtenFile['ext_info']['sha1'] 263 | if 'md5' in writtenFile['ext_info']: 264 | file['md5'] = writtenFile['ext_info']['md5'] 265 | if 'size' in writtenFile['ext_info']: 266 | file['fileSize'] = writtenFile['ext_info']['size'] 267 | file['operation'] = 'created' 268 | files.append(file) 269 | if len(files) > 0: 270 | file = { 'fileName' : os.path.basename(fileName), 271 | 'filePath' : os.path.dirname(fileName), 272 | 'md5' : md5, 273 | 'sha1' : sha1, 274 | 'operation' : 'created' 275 | } 276 | files.insert(0, file) 277 | notification['files'] = files 278 | 279 | self.b9_api.create("v1/notification", notification) 280 | logging.debug("File analysis completed for '%s' [%s]: %s" % (fileName, md5, notification['type'])) 281 | 282 | def fetchTaskResult(self, uuid, fileName): 283 | logging.debug("Querying LL for json result for '%s'" % uuid) 284 | json_result = self.ll.get_result(uuid, raw=True) 285 | logging.debug("Query result: %s" % str(json_result)[:1024]) 286 | result = json.loads(json_result) 287 | success = result['success'] 288 | if not success: 289 | logging.error("\t%s", result) 290 | return False 291 | if self.report_store_location: 292 | result_filename = os.path.join(self.report_store_location, os.path.basename(fileName)) 293 | json_result = json.dumps(result, sort_keys=True, indent=4) 294 | json_fn = result_filename + ".json" 295 | f = open(json_fn, "w") 296 | f.write(json_result) 297 | #first one (in json) was successful. 298 | #Now let's get it in raw XML. 299 | logging.debug("Querying LL for xml result for '%s'" % uuid) 300 | xml_result = self.ll.get_result(uuid, requested_format="xml") 301 | xml_fn = result_filename + ".xml" 302 | f = open(xml_fn, "w") 303 | f.write(xml_result) 304 | return result 305 | 306 | def fetchCompletedTasks(self): 307 | try: 308 | waitingScans = sum(1 for x in self.ll_tasks.values() if x == "pending") 309 | if (waitingScans > 0): 310 | moreResults = 1 311 | while (moreResults == 1): 312 | logging.debug("Querying LL for completed tasks from %s" % str(self.last_checked_time)) 313 | result = self.ll.completed(after=self.last_checked_time, verify=self.ll_api.strong_cert) 314 | logging.debug("Query result: %s" % result) 315 | completed_tasks = result["data"]["tasks"] 316 | self.last_checked_time = result["data"]["before"] 317 | moreResults = result["data"]["more_results_available"] 318 | completedCount = 0 319 | if len(completed_tasks) > 0: 320 | for uuid in completed_tasks: 321 | if uuid in self.ll_tasks.keys(): 322 | self.ll_tasks[uuid] = "completed" 323 | completedCount += 1 324 | if completedCount > 0: 325 | logging.debug("Got %s completed tasks", completedCount) 326 | 327 | except Exception as e: 328 | logging.error(e) 329 | return 330 | 331 | def processOneAnalysisRequest(self,pa): 332 | try: 333 | # Use md5 hash if we have one 334 | md5 = pa['md5'].strip() 335 | fileName = pa['fileName'].strip() 336 | 337 | uuid = None 338 | # Check our cache if we already sent this file for scan 339 | if md5 in self.bit9_pending_analysis.keys(): 340 | uuid = self.bit9_pending_analysis[md5] 341 | if uuid in self.ll_tasks: 342 | task = self.ll_tasks[uuid] 343 | if task == "completed": 344 | # Get our uuid we got from LL last time around 345 | result = self.fetchTaskResult(uuid, fileName) 346 | if result == False: 347 | raise Exception("Error: Result not available") 348 | self.reportResultToBit9(pa, result['data']) 349 | del self.ll_tasks[uuid] 350 | del self.bit9_pending_analysis[md5] 351 | return 352 | else: 353 | # Still waiting for a completed result 354 | return 355 | else: 356 | # we have not asked LL yet. Try with file hash 357 | try: 358 | logging.debug("File analysis requested for '%s' [%s]" % (fileName, md5)) 359 | result = self.ll.submit_file_hash(md5=md5, filename=fileName, verify=self.ll_api.strong_cert) 360 | logging.debug("Query result: %s" % str(result)[:1024]) 361 | if 'error' in result.get('data', {}): 362 | raise Exception(result['data']['error']) 363 | # LL task available 364 | if 'task_uuid' in result.get('data', {}): 365 | uuid = result['data']['task_uuid']; 366 | # LL result available 367 | if 'score' in result.get('data', {}): 368 | result = self.fetchTaskResult(uuid, fileName) 369 | if result == False: 370 | raise Exception("Error: Result not available") 371 | self.reportResultToBit9(pa, result['data']) 372 | if uuid in self.ll_tasks: 373 | del self.ll_tasks[uuid] 374 | return 375 | except analysis_apiclient.FileNotAvailableError: 376 | # file has not already been submitted to the device, need to submit 377 | if pa['uploaded'] == 1: 378 | # We have file and now we will upload it to LL 379 | uuid = self.uploadFileToLL(pa) 380 | else: 381 | # if we end here, it means that LL doesn't have file, and Bit9 hasn't uploaded it yet from the agent 382 | # we will come back again in 30 seconds 383 | uuid = None 384 | if uuid is not None: 385 | # Remember uuid since LL wants use to use it for future references to the file 386 | # We will try again in 1 hour (per LL best practices) 387 | self.bit9_pending_analysis[md5] = uuid 388 | self.ll_tasks[uuid] = "pending" 389 | 390 | except Exception as ex: 391 | logging.error(ex) 392 | # Report to Bit9 that we had error analyzing this file. This means we will not try analysis again. 393 | pa['analysisStatus'] = 4 # (status: Error) 394 | pa['analysisError'] = 'Lastline %s' % str(ex) 395 | 396 | # Update Bit9 status for this file 397 | self.b9_api.update('v1/pendingAnalysis', pa) 398 | 399 | # ------------------------------------------------------------------------------------------------- 400 | # Main body of the script 401 | 402 | b9_api = bit9api.bit9Api( 403 | server = 'https://B9_SERVER', 404 | ssl_verify = False, # Validate cert against CA 405 | token = 'B9_API_TOKEN' # Need to add B9 API token here 406 | ) 407 | 408 | ll_api = LastlineAPI( 409 | url = 'https://analysis.lastline.com', 410 | key = 'LL_API_KEY', # Need to add Lastline API key here 411 | token = 'LL_API_TOKEN', # Need to add Lastline API token here 412 | strong_cert = False) # Validate cert against CA 413 | # Need to specify an existing accessible path here (such as c:\\test\\) 414 | connector = LastlineConnector(b9_api, ll_api, download_file_location="c:\\test\\") 415 | 416 | connector.start() 417 | -------------------------------------------------------------------------------- /bit9PlatformAPI/examples/connectors/VirusTotal.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is a Python script for the VirusTotal Analyst Connector for Bit9 Security Platform. 3 | 4 | Copyright Bit9, Inc. 2015 5 | support@bit9.com 6 | 7 | 8 | Disclaimer 9 | +++++++++++++++++++ 10 | By accessing and/or using the samples scripts provided on this site (the "Scripts"), you hereby agree to the following terms: 11 | The Scripts are exemplars provided for purposes of illustration only and are not intended to represent specific 12 | recommendations or solutions for API integration activities as use cases can vary widely. 13 | THE SCRIPTS ARE PROVIDED "AS IS" WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. BIT9 MAKES NO REPRESENTATION 14 | OR OTHER AFFIRMATION OF FACT, INCLUDING BUT NOT LIMITED TO STATEMENTS REGARDING THE SCRIPTS' SUITABILITY FOR USE OR PERFORMANCE. 15 | IN NO EVENT SHALL BIT9 BE LIABLE FOR SPECIAL, INCIDENTAL, CONSEQUENTIAL, EXEMPLARY OR OTHER INDIRECT DAMAGES OR FOR DIRECT 16 | DAMAGES ARISING OUT OF OR RESULTING FROM YOUR ACCESS OR USE OF THE SCRIPTS, EVEN IF BIT9 IS ADVISED OF OR AWARE OF THE 17 | POSSIBILITY OF SUCH DAMAGES. 18 | 19 | Requirements 20 | +++++++++++++++++++ 21 | 22 | - Bit9 API client (included) which requires requests Python module 23 | 24 | - VirusTotal API Key 25 | 26 | - Bit9 Platform Server 7.2.1 or later 27 | - Bit9 API Token (generated in Bit9 Console) 28 | 29 | Required python modules can be installed using tools such as easy_install or pip, e.g. 30 | easy_install requests 31 | 32 | Configuring Connector 33 | +++++++++++++++++++++++ 34 | 35 | Please update the script with appropriate vt_token, Bit9 server address and Bit9 token at the bottom of the script. 36 | 37 | 38 | Start the script. No parameters are required. It will process analysis requests from the Bit9 Platform as long as it is running. 39 | Script execution can be terminated with ctrl+c. 40 | """ 41 | 42 | import requests 43 | import datetime 44 | import time 45 | import sys 46 | import os 47 | import zipfile 48 | import tempfile 49 | import shutil 50 | 51 | import logging 52 | log = logging.getLogger(__name__) 53 | 54 | 55 | # ------------------------------------------------------------------------------------------------- 56 | # VT connector class. Initialization where keys are specified is done at the bottom of the script 57 | class virusTotalConnector(object): 58 | def __init__(self, bit9, vt_token=None, connector_name='VirusTotal', allow_uploads=True, download_location=None): 59 | """ Description of parameters: 60 | bit9 - bit9api object 61 | vt_token - API token provided by VirusTotal 62 | connector_name - name of the connector. Defaults to 'VirusTotal' 63 | allow_uploads - True to allow uploads of binaries to the VirusTotal servers. If set to False, 64 | only hash lookups will be done to te virusTotal 65 | Note: In case when allow_uploads is set to False AND VirusTotal does not recognize the hash, 66 | associated Bit9 file analysis request will be cancelled 67 | download_location - location that will hold the files uploaded from the Bit9 platform 68 | before they are sent to the VirusTotal. It is required only if allow_uploads is set to True 69 | If set to None, script will attempt to files uploaded from agents on the remote share. If 70 | remote share is unavailable, associated Bit9 file analysis request will end in Error state 71 | 72 | """ 73 | 74 | if vt_token is None: 75 | raise TypeError("Missing required VT authentication token.") 76 | self.vt_token = vt_token 77 | self.vt_url = 'https://www.virustotal.com/vtapi/v2' 78 | self.bit9 = bit9 79 | self.polling_frequency = 30 # seconds 80 | self.connector_name = connector_name 81 | 82 | # Global dictionary to track our VT scheduled scans. We need this since it takes VT a while to process results 83 | # and we don't want to keep polling VT too often 84 | # Any pending results will be kept here, together with next polling time 85 | self.scheduledScans = {} 86 | 87 | # Download location. 88 | self.download_location = None 89 | if download_location: 90 | self.download_location = os.path.realpath(download_location) 91 | if not os.path.exists(download_location): 92 | os.makedirs(download_location) 93 | self.allow_uploads = allow_uploads 94 | 95 | def start(self): 96 | # Register or update our connector (can be done multiple times - will be treated as update on subsequent times) 97 | r = self.bit9.create('v1/connector', {'name': self.connector_name, 'analysisName': self.connector_name, 98 | 'connectorVersion': '1.0', 'canAnalyze': 'true', 'analysisEnabled': 'true'}) 99 | 100 | if not r: 101 | log.fatal("Could not create connector on the Bit9 server. Are the bit9 server URL and API token correct?") 102 | return 103 | 104 | connectorId = str(r['id']) 105 | 106 | # Loop forever (until killed) 107 | while True: 108 | try: 109 | # Check with Bit9 Platform if we have any analysis still pending 110 | for i in self.bit9.retrieve("v1/pendingAnalysis", url_params="connectorId=" + connectorId): 111 | self.processOneAnalysisRequest(i) 112 | except Exception as e: 113 | log.exception("Error during processing. Will try again in %d seconds." % (self.polling_frequency,)) 114 | # Sleep N seconds, and then all over again 115 | time.sleep(self.polling_frequency) 116 | 117 | # This function unregisters the connector and deletes all its data 118 | def unregister(self): 119 | # Unregister our connector 120 | r = self.bit9.search('v1/connector', ['name:'+self.connector_name]) 121 | if len(r)>0: 122 | log.info("Unregistering connector %s and deleting all its data" % self.connector_name) 123 | self.bit9.delete('v1/connector', r[0]) 124 | 125 | def uploadFileToVT(self, pa): 126 | scanId = None 127 | 128 | if self.download_location: 129 | # This is if we want to locally download file from Bit9 130 | # (in the case shared folder is not accessible) 131 | localFilePath = self.download_location + "\\temp.zip" 132 | self.bit9.retrieve_analyzed_file(pa['id'], localFilePath) 133 | else: 134 | # Easier option, if Bit9 shared folder can be accessed directly 135 | localFilePath = pa['uploadPath'] 136 | 137 | try: 138 | # the zip file returned by Bit9 should have only one directory entry in it, 139 | # the file to be analyzed. Extract that file for analysis. This is done since 140 | # Bit9 retains the original file path information in the zip file, which may 141 | # include sensitive/personal information that we don't want to disclose to VT. 142 | z = zipfile.ZipFile(localFilePath) 143 | infp = z.open(z.filelist[0]) 144 | outfp = tempfile.NamedTemporaryFile() 145 | shutil.copyfileobj(infp, outfp) 146 | except Exception as e: 147 | pa['analysisStatus'] = 4 # (status: Error) 148 | pa['analysisError'] = 'Received error when attempting to unzip file from Bit9: %s' % str(e) 149 | # Update Bit9 status for this file 150 | self.bit9.update('v1/pendingAnalysis', pa) 151 | log.exception("Could not unzip file from Bit9 for analysis of %s" % pa) 152 | return scanId 153 | 154 | outfp.seek(0) 155 | files = {'file': outfp} 156 | try: 157 | r = requests.post(self.vt_url + "/file/scan", files=files, params={'apikey': self.vt_token}) 158 | isError = (r.status_code >= 400) 159 | # we got VT scanId. We will need it to check status of the scan at later time 160 | if r.status_code == 200: 161 | scanId = r.json()['scan_id'] 162 | except: 163 | log.exception("Could not send file %s to VirusTotal" % (pa,)) 164 | isError = True 165 | finally: 166 | outfp.close() 167 | 168 | if isError: 169 | # Report to Bit9 that we had error analyzing this file. This means we will not try analysis again. 170 | pa['analysisStatus'] = 4 # (status: Error) 171 | pa['analysisError'] = 'VirusTotal returned error when attempting to send file for scanning' 172 | else: 173 | # Tell Bit9 that we are waiting for the scan to finish 174 | pa['analysisStatus'] = 1 # (status: Analyzing) 175 | 176 | # Update Bit9 status for this file 177 | self.bit9.update('v1/pendingAnalysis', pa) 178 | return scanId 179 | 180 | def reportResultToBit9(self, fileAnalysisId, scanResults): 181 | # We have results. Create our Bit9 notification 182 | notification = { 183 | 'fileAnalysisId': fileAnalysisId, 184 | 'product': 'VirusTotal', 185 | 'malwareName': '', 186 | 'malwareType': '' 187 | } 188 | # Let's see if it is malicious. Use some fancy heuristics... 189 | positivesPerc = 100 * scanResults.get('positives') / scanResults.get('total') 190 | if positivesPerc > 50: 191 | notification['analysisResult'] = 3 # ...malicious 192 | notification['severity'] = 'critical' 193 | notification['type'] = 'malicious_file' 194 | elif positivesPerc > 0: 195 | notification['analysisResult'] = 2 # ...could be malicious 196 | notification['severity'] = 'high' 197 | notification['type'] = 'potential_risk_file' 198 | else: 199 | notification['analysisResult'] = 1 # clean! 200 | notification['severity'] = 'low' 201 | notification['type'] = 'clean_file' 202 | notification['externalUrl'] = scanResults.get('permalink') 203 | 204 | # Enumerate scan results that have detected the issue and build our 205 | # 'malwareName' string for the Bit9 notification 206 | scans = scanResults.get("scans", {}) 207 | n = 0 208 | for key in scans: 209 | s = scans[key] 210 | if s['detected']: 211 | if n > 4: 212 | notification['malwareType'] += '...' 213 | notification['malwareName'] += '...' 214 | break 215 | elif n > 0: 216 | notification['malwareType'] += '; ' 217 | notification['malwareName'] += '; ' 218 | notification['malwareType'] += key + ':' + s['result'] 219 | notification['malwareName'] += s['result'] 220 | n += 1 221 | # Send notification 222 | self.bit9.create("v1/notification", notification) 223 | log.info("VT analysis for fileAnalysis %d completed. VT result is %d%% malware (%s). Reporting status: %s" 224 | % (fileAnalysisId, positivesPerc, notification['malwareName'], notification['type'])) 225 | 226 | def processOneAnalysisRequest(self, pa): 227 | # Use md5 hash if we have one. If not, use Sha256 228 | fileHash = pa['md5'].strip() 229 | if fileHash == '': 230 | fileHash = pa['sha256'].strip() 231 | 232 | scanId = None 233 | # Check our cache if we already sent this file for scan 234 | if fileHash in self.scheduledScans.keys(): 235 | lastAttempt = self.scheduledScans[fileHash] 236 | # Be polite and don't keep asking VT for status too often. If we already tried recently, bail 237 | if lastAttempt['nextCheck'] > datetime.datetime.now(): 238 | return 239 | # Get our scanId we got from VT last time around 240 | scanId = lastAttempt['scanId'] 241 | r = requests.get(self.vt_url + "/file/report", params={'resource': scanId, 'apikey': self.vt_token}) 242 | else: 243 | # we have not asked VT yet. Try with file hash 244 | r = requests.get(self.vt_url + "/file/report", params={'resource': fileHash, 'apikey': self.vt_token}) 245 | 246 | r.raise_for_status() 247 | if r.status_code == 204: 248 | # no results from VT because rate limit was reached. We will try again later. 249 | log.info("VirusTotal API rate limit reached, will try again later") 250 | return 251 | 252 | scanId = None 253 | scanResults = r.json() 254 | # Check if we got results... 255 | if scanResults.get('positives') is not None: 256 | # Yes, VT has them. Report results and we are done with the file 257 | log.info("%s: VirusTotal has a result ready, reporting to Bit9" % fileHash) 258 | self.reportResultToBit9(pa['id'], scanResults) 259 | elif scanResults.get('scan_id') is not None: 260 | # VT already knows about the file, but scan is not complete. We got scanId for future reference 261 | # Let's remember that and try again in 1 hour (per VT best practices) 262 | scanId = scanResults['scan_id'] 263 | elif not self.allow_uploads: 264 | # Uploads are not allowed. Cancel the analysis 265 | pa['analysisStatus'] = 5 # (status: Cancelled) 266 | log.info("%s: VirusTotal has no information and we aren't allowed to upload it. Cancelling the analysis request." % fileHash) 267 | self.bit9.update('v1/pendingAnalysis', pa) 268 | elif pa['uploaded'] == 1: 269 | # We have file and now we will upload it to VT 270 | log.info("%s: VirusTotal has no information on this hash. Uploading the file" % fileHash) 271 | scanId = self.uploadFileToVT(pa) 272 | else: 273 | # if we end here, it means that VT doesn't have file, and Bit9 hasn't uploaded it yet from the agent 274 | # we will come back again when we reach this file next time around 275 | log.info("%s: VirusTotal has no information on this hash. Waiting for Bit9 agent to upload it." % fileHash) 276 | pass 277 | 278 | if scanId: 279 | # Remember scanId since VT wants use to use it for future references to the file 280 | # We will try again in 1 hour (per VT best practices) 281 | next_check = datetime.datetime.now() + datetime.timedelta(0, 3600) 282 | self.scheduledScans[fileHash] = {'scanId': scanId, 'nextCheck': next_check} 283 | log.info("%s: Waiting for analysis to complete. Will check back after %s." % (fileHash, 284 | next_check.strftime("%Y-%m-%d %H:%M:%S"))) 285 | 286 | if __name__ == '__main__': 287 | # ------------------------------------------------------------------------------------------------- 288 | # Main body of the script 289 | 290 | try: 291 | import bit9api 292 | except ImportError: 293 | # Import our common bit9api (assumed to live in common folder, sibling to current folder) 294 | commonPath = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'common') 295 | sys.path.append(commonPath) 296 | import bit9api 297 | 298 | logging.basicConfig(format='%(asctime)s %(levelname)s:%(message)s', level=logging.DEBUG) 299 | logging.getLogger("requests").setLevel(logging.WARNING) 300 | logging.getLogger("urllib3").setLevel(logging.WARNING) 301 | 302 | requests.packages.urllib3.disable_warnings() 303 | 304 | bit9 = bit9api.bit9Api( 305 | "https://localhost", # Replace with actual Bit9 server URL 306 | token="", # Replace with actual Bit9 user token for VT integration 307 | ssl_verify=False # Don't validate server's SSL certificate. Set to True unless using self-signed cert on IIS 308 | ) 309 | 310 | vtConnector = virusTotalConnector( 311 | bit9, 312 | vt_token='', # Replace with your VT key 313 | allow_uploads=True, # Allow VT connector to upload binary files to VirusTotal 314 | connector_name='VirusTotal', 315 | download_location=r'c:\test' # Replace with actual local file location. If not set, 316 | # script will try to access shared folder where this file resides 317 | # Note that you do not want to end your path with a backslash. ie. use 318 | # r'c:\test' *not* r'c:\test\'. 319 | ) 320 | 321 | print("\n*** VT script starting") 322 | vtConnector.start() 323 | -------------------------------------------------------------------------------- /bit9PlatformAPI/examples/connectors/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmware-archive/bit9platform/2ed99c6eea7b2e0f0d8e74f27874fd3fdb27c418/bit9PlatformAPI/examples/connectors/__init__.py -------------------------------------------------------------------------------- /bit9PlatformAPI/examples/samples/ApproveFiles.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is a sample Python script that locally approves all unapproved files for computers in given policy, under the specific condition 3 | 4 | Copyright Bit9, Inc. 2015 5 | support@bit9.com 6 | 7 | 8 | Disclaimer 9 | +++++++++++++++++++ 10 | By accessing and/or using the samples scripts provided on this site (the "Scripts"), you hereby agree to the following terms: 11 | The Scripts are exemplars provided for purposes of illustration only and are not intended to represent specific 12 | recommendations or solutions for API integration activities as use cases can vary widely. 13 | THE SCRIPTS ARE PROVIDED "AS IS" WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. BIT9 MAKES NO REPRESENTATION 14 | OR OTHER AFFIRMATION OF FACT, INCLUDING BUT NOT LIMITED TO STATEMENTS REGARDING THE SCRIPTS' SUITABILITY FOR USE OR PERFORMANCE. 15 | IN NO EVENT SHALL BIT9 BE LIABLE FOR SPECIAL, INCIDENTAL, CONSEQUENTIAL, EXEMPLARY OR OTHER INDIRECT DAMAGES OR FOR DIRECT 16 | DAMAGES ARISING OUT OF OR RESULTING FROM YOUR ACCESS OR USE OF THE SCRIPTS, EVEN IF BIT9 IS ADVISED OF OR AWARE OF THE 17 | POSSIBILITY OF SUCH DAMAGES. 18 | 19 | Requirements 20 | +++++++++++++++++++ 21 | 22 | - Bit9 API client (included) which requires requests Python module 23 | - Bit9 Platform Server 7.2.1 or later 24 | - Bit9 API Token (generated in Bit9 Console) 25 | 26 | Required python modules can be installed using tools such as easy_install or pip, e.g. 27 | easy_install requests 28 | 29 | +++++++++++++++++++++++ 30 | Please update the script with appropriate Bit9 server address and Bit9 token script. 31 | """ 32 | 33 | import sys 34 | import os 35 | 36 | # Include our common folder, presumably peer of current folder 37 | sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'common')) 38 | import bit9api 39 | 40 | bit9 = bit9api.bit9Api( 41 | "https://localhost", # Replace with actual Bit9 server URL 42 | token="", # Replace with actual Bit9 user token for VT integration 43 | ssl_verify=False # Don't validate server's SSL certificate. Set to True unless using self-signed cert on IIS 44 | ) 45 | 46 | # Find all computers in policy 'sales-1' 47 | comps = bit9.search('v1/computer', ['policyName:sales-1', 'deleted:false']) 48 | for c in comps: # For each returned computer, get list of locally unapproved files 49 | files = bit9.search('v1/fileInstance', ['computerId:'+str(c['id']), 'localState:1']) 50 | n = 0 51 | for f in files: # For each returned file... 52 | # Get its file catalog entry to get prevalence 53 | fcat = bit9.retrieve('v1/fileCatalog', f['fileCatalogId']) 54 | if fcat['prevalence'] >= 10: # if prevalent enough... 55 | f['localState'] = 2 # Approve locally 56 | bit9.update('v1/fileInstance', f) 57 | n += 1 58 | if n > 0: 59 | print("Locally approved %d files on computer %s" % (n, c['name'])) 60 | 61 | -------------------------------------------------------------------------------- /bit9PlatformAPI/examples/samples/CBSensorReport.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is a sample Python script that reports on CarbonBlack sensors on computers with Bit9 Platform agent 3 | 4 | Copyright Bit9, Inc. 2015 5 | support@bit9.com 6 | 7 | 8 | Disclaimer 9 | +++++++++++++++++++ 10 | By accessing and/or using the samples scripts provided on this site (the "Scripts"), you hereby agree to the following terms: 11 | The Scripts are exemplars provided for purposes of illustration only and are not intended to represent specific 12 | recommendations or solutions for API integration activities as use cases can vary widely. 13 | THE SCRIPTS ARE PROVIDED "AS IS" WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. BIT9 MAKES NO REPRESENTATION 14 | OR OTHER AFFIRMATION OF FACT, INCLUDING BUT NOT LIMITED TO STATEMENTS REGARDING THE SCRIPTS' SUITABILITY FOR USE OR PERFORMANCE. 15 | IN NO EVENT SHALL BIT9 BE LIABLE FOR SPECIAL, INCIDENTAL, CONSEQUENTIAL, EXEMPLARY OR OTHER INDIRECT DAMAGES OR FOR DIRECT 16 | DAMAGES ARISING OUT OF OR RESULTING FROM YOUR ACCESS OR USE OF THE SCRIPTS, EVEN IF BIT9 IS ADVISED OF OR AWARE OF THE 17 | POSSIBILITY OF SUCH DAMAGES. 18 | 19 | Requirements 20 | +++++++++++++++++++ 21 | 22 | - Bit9 API client (included) which requires requests Python module 23 | - Bit9 Platform Server 7.2.1 or later 24 | - Bit9 API Token (generated in Bit9 Console) 25 | 26 | Required python modules can be installed using tools such as easy_install or pip, e.g. 27 | easy_install requests 28 | 29 | +++++++++++++++++++++++ 30 | Please update the script with appropriate Bit9 server address and Bit9 token script. 31 | """ 32 | import sys 33 | import os 34 | 35 | # Include our common folder, presumably peer of current folder 36 | sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'common')) 37 | import bit9api 38 | 39 | bit9 = bit9api.bit9Api( 40 | "https://localhost", # Replace with actual Bit9 server URL 41 | token="", # Replace with actual Bit9 user token for VT integration 42 | ssl_verify=False # Don't validate server's SSL certificate. Set to True unless using self-signed cert on IIS 43 | ) 44 | 45 | # Get all computers with CB sensor and group them by sensor version 46 | compsWithCB = bit9.search('v1/computer', 47 | [ # This array contains our condition 48 | 'deleted:false', # not deleted 49 | 'uninstalled:false', # not uninstalled 50 | 'lastPollDate>-7d', # connected within last week 51 | 'cbSensorId!0', # with sensor installed (!=0) 52 | 'cbSensorVersion!' # where sensor version is not null (meaning, sensor was initialized) 53 | ], group_by='cbSensorVersion') # group by sensor version 54 | # Get count of all recently connected computers (not deleted or uninstalled) 55 | totalComps = bit9.search('v1/computer', ['deleted:false', 'uninstalled:false', 'lastPollDate>-7d'], limit=-1)["count"] 56 | 57 | print("Report by CB sensor version") 58 | print("-----------------------") 59 | if totalComps > 0: # To avoid division by zero 60 | totalWithSensor = 0 61 | for group in compsWithCB: 62 | print("%-15s : %s" % (group["value"], group["count"])) 63 | totalWithSensor += group["count"] 64 | print("-----------------------") 65 | print("%-15s : %s" % ("Total Computers", totalComps)) 66 | print("%-15s : %s (%2.1f %%)" % ("With Sensor", totalWithSensor, totalWithSensor * 100.0 / totalComps)) 67 | 68 | -------------------------------------------------------------------------------- /bit9PlatformAPI/examples/samples/LocalApproval.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is a sample Python script for moving selected computers to local approval for 10 minutes. 3 | 4 | Copyright Bit9, Inc. 2015 5 | support@bit9.com 6 | 7 | 8 | Disclaimer 9 | +++++++++++++++++++ 10 | By accessing and/or using the samples scripts provided on this site (the "Scripts"), you hereby agree to the following terms: 11 | The Scripts are exemplars provided for purposes of illustration only and are not intended to represent specific 12 | recommendations or solutions for API integration activities as use cases can vary widely. 13 | THE SCRIPTS ARE PROVIDED "AS IS" WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. BIT9 MAKES NO REPRESENTATION 14 | OR OTHER AFFIRMATION OF FACT, INCLUDING BUT NOT LIMITED TO STATEMENTS REGARDING THE SCRIPTS' SUITABILITY FOR USE OR PERFORMANCE. 15 | IN NO EVENT SHALL BIT9 BE LIABLE FOR SPECIAL, INCIDENTAL, CONSEQUENTIAL, EXEMPLARY OR OTHER INDIRECT DAMAGES OR FOR DIRECT 16 | DAMAGES ARISING OUT OF OR RESULTING FROM YOUR ACCESS OR USE OF THE SCRIPTS, EVEN IF BIT9 IS ADVISED OF OR AWARE OF THE 17 | POSSIBILITY OF SUCH DAMAGES. 18 | 19 | Requirements 20 | +++++++++++++++++++ 21 | 22 | - Bit9 API client (included) which requires requests Python module 23 | - Bit9 Platform Server 7.2.1 or later 24 | - Bit9 API Token (generated in Bit9 Console) 25 | 26 | Required python modules can be installed using tools such as easy_install or pip, e.g. 27 | easy_install requests 28 | 29 | +++++++++++++++++++++++ 30 | Please update the script with appropriate Bit9 server address and Bit9 token script. 31 | """ 32 | 33 | import time 34 | import sys 35 | import os 36 | 37 | # Include our common folder, presumably peer of current folder 38 | sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'common')) 39 | import bit9api 40 | 41 | bit9 = bit9api.bit9Api( 42 | "https://localhost", # Replace with actual Bit9 server URL 43 | token="", # Replace with actual Bit9 user token for VT integration 44 | ssl_verify=False # Don't validate server's SSL certificate. Set to True unless using self-signed cert on IIS 45 | ) 46 | 47 | # Our condition is "All non-deleted computers in policy 'sales-1' that have IP address that DOES NOT start with 10.0.1 48 | comps = bit9.search('v1/computer', ['policyName:sales-1', 'ipAddress!10.0.1.*', 'deleted:false']) 49 | for c in comps: # Move each returned computer to the local approval policy 50 | print("Moving computer %s (IP: %s) from policy %s to local approval policy" % (c['name'], c['ipAddress'], c['policyName'])) 51 | c['localApproval'] = True 52 | bit9.update('v1/computer', c) 53 | 54 | # sleep for 10 minutes 55 | time.sleep(10*60) 56 | 57 | for c in comps: # Move all affected computers back to the enforcement policy 58 | print("Restoring computer %s back to its original policy" % (c['name'])) 59 | c['localApproval'] = False 60 | bit9.update('v1/computer', c) 61 | -------------------------------------------------------------------------------- /bit9PlatformAPI/examples/samples/MoveComputers.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is a sample Python script for moving selected computers to different policy at a specified time. 3 | 4 | Copyright Bit9, Inc. 2015 5 | support@bit9.com 6 | 7 | 8 | Disclaimer 9 | +++++++++++++++++++ 10 | By accessing and/or using the samples scripts provided on this site (the "Scripts"), you hereby agree to the following terms: 11 | The Scripts are exemplars provided for purposes of illustration only and are not intended to represent specific 12 | recommendations or solutions for API integration activities as use cases can vary widely. 13 | THE SCRIPTS ARE PROVIDED "AS IS" WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. BIT9 MAKES NO REPRESENTATION 14 | OR OTHER AFFIRMATION OF FACT, INCLUDING BUT NOT LIMITED TO STATEMENTS REGARDING THE SCRIPTS' SUITABILITY FOR USE OR PERFORMANCE. 15 | IN NO EVENT SHALL BIT9 BE LIABLE FOR SPECIAL, INCIDENTAL, CONSEQUENTIAL, EXEMPLARY OR OTHER INDIRECT DAMAGES OR FOR DIRECT 16 | DAMAGES ARISING OUT OF OR RESULTING FROM YOUR ACCESS OR USE OF THE SCRIPTS, EVEN IF BIT9 IS ADVISED OF OR AWARE OF THE 17 | POSSIBILITY OF SUCH DAMAGES. 18 | 19 | Requirements 20 | +++++++++++++++++++ 21 | 22 | - Bit9 API client (included) which requires requests Python module 23 | - Bit9 Platform Server 7.2.1 or later 24 | - Bit9 API Token (generated in Bit9 Console) 25 | 26 | Required python modules can be installed using tools such as easy_install or pip, e.g. 27 | easy_install requests 28 | 29 | +++++++++++++++++++++++ 30 | Please update the script with appropriate Bit9 server address and Bit9 token script. 31 | """ 32 | 33 | import time 34 | from datetime import datetime 35 | import sys 36 | import os 37 | 38 | # Include our common folder, presumably peer of current folder 39 | sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'common')) 40 | import bit9api 41 | 42 | bit9 = bit9api.bit9Api( 43 | "https://localhost", # Replace with actual Bit9 server URL 44 | token="", # Replace with actual Bit9 user token for VT integration 45 | ssl_verify=False # Don't validate server's SSL certificate. Set to True unless using self-signed cert on IIS 46 | ) 47 | 48 | 49 | # Setup our arguments (these could be, for example, passed from the command line) 50 | switchTime = "4/1/2015 8:04AM" # When to switch policies 51 | targetPolicyName = "sales-2" # Target policy name 52 | computerCondition = ['policyName:sales-1', 'ipAddress!10.0.1.*', 'deleted:false'] # Condition for computers to move 53 | 54 | # Sleep until specified time 55 | sleepTime = datetime.strptime(switchTime, '%m/%d/%Y %I:%M%p') - datetime.today() 56 | if sleepTime.total_seconds()>0: 57 | print('Sleeping for %d seconds' % sleepTime.total_seconds()) 58 | time.sleep(sleepTime.total_seconds()) 59 | 60 | # Find our destination policy by name 61 | destPolicies = bit9.search('v1/policy', ['name:'+targetPolicyName]) 62 | if len(destPolicies)==0: 63 | raise ValueError("Cannot find destination policy "+targetPolicyName) 64 | 65 | 66 | # Our condition is "All non-deleted computers in policy 'sales-1' that have IP address that DOES NOT start with 10.0.1 67 | comps = bit9.search('v1/computer', computerCondition) 68 | for c in comps: # Move each returned computer to the local approval policy 69 | print("Moving computer %s (IP: %s) from policy %s to policy %s" % 70 | (c['name'], c['ipAddress'], c['policyName'], targetPolicyName)) 71 | c['policyId'] = destPolicies[0]['id'] 72 | bit9.update('v1/computer', c) 73 | 74 | -------------------------------------------------------------------------------- /bit9PlatformAPI/examples/samples/Templating.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is a sample Python script that demonstrates VDI templating 3 | It waits for specific computer to show online, and then once it is offline it will create a template of it 4 | 5 | Copyright Bit9, Inc. 2015 6 | support@bit9.com 7 | 8 | 9 | Disclaimer 10 | +++++++++++++++++++ 11 | By accessing and/or using the samples scripts provided on this site (the "Scripts"), you hereby agree to the following terms: 12 | The Scripts are exemplars provided for purposes of illustration only and are not intended to represent specific 13 | recommendations or solutions for API integration activities as use cases can vary widely. 14 | THE SCRIPTS ARE PROVIDED "AS IS" WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. BIT9 MAKES NO REPRESENTATION 15 | OR OTHER AFFIRMATION OF FACT, INCLUDING BUT NOT LIMITED TO STATEMENTS REGARDING THE SCRIPTS' SUITABILITY FOR USE OR PERFORMANCE. 16 | IN NO EVENT SHALL BIT9 BE LIABLE FOR SPECIAL, INCIDENTAL, CONSEQUENTIAL, EXEMPLARY OR OTHER INDIRECT DAMAGES OR FOR DIRECT 17 | DAMAGES ARISING OUT OF OR RESULTING FROM YOUR ACCESS OR USE OF THE SCRIPTS, EVEN IF BIT9 IS ADVISED OF OR AWARE OF THE 18 | POSSIBILITY OF SUCH DAMAGES. 19 | 20 | Requirements 21 | +++++++++++++++++++ 22 | 23 | - Bit9 API client (included) which requires requests Python module 24 | - Bit9 Platform Server 7.2.1 or later 25 | - Bit9 API Token (generated in Bit9 Console) 26 | 27 | Required python modules can be installed using tools such as easy_install or pip, e.g. 28 | easy_install requests 29 | 30 | +++++++++++++++++++++++ 31 | Please update the script with appropriate Bit9 server address and Bit9 token script. 32 | """ 33 | 34 | import time 35 | import datetime 36 | import sys 37 | import os 38 | 39 | # Include our common folder, presumably peer of current folder 40 | sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'common')) 41 | import bit9api 42 | 43 | bit9 = bit9api.bit9Api( 44 | "https://localhost", # Replace with actual Bit9 server URL 45 | token="", # Replace with actual Bit9 user token for VT integration 46 | ssl_verify=False # Don't validate server's SSL certificate. Set to True unless using self-signed cert on IIS 47 | ) 48 | 49 | while True: 50 | # Our condition for VM image is policy='virtualized-1' and IP address=10.36.4.3. 51 | # As soon as it is offline, we will template it. Note that computers that are still initializing 52 | # cannot be templated 53 | comps = bit9.search('v1/computer', 54 | ['policyName:virtualized-1', 'deleted:false', 'ipAddress:10.36.4.3', 55 | 'connected:false', 'template:false', 'initializing:false'], 56 | limit=1) 57 | 58 | # If it did go offline, template it! 59 | if len(comps)>0: 60 | c = comps[0] 61 | print("VM image %s went offline. We will now make a template of it." % (c['name'])) 62 | c['template'] = True 63 | c['name'] = 'My template ' + datetime.datetime.now().strftime("%B %d, %Y %I:%M%p") 64 | c['templateCloneCleanupMode'] = 2 # Automatic, by time 65 | c['templateCloneCleanupTimeScale'] = 1 # Hours 66 | c['templateCloneCleanupTime'] = 2 # 2 Hours 67 | c['templateTrackModsOnly'] = True 68 | bit9.update('v1/computer', c, url_params='changeTemplate=true') 69 | 70 | # wait 10 seconds and do another check (we might need to template it again some day) 71 | time.sleep(10) 72 | -------------------------------------------------------------------------------- /bit9PlatformAPI/examples/samples/TranslateSyncFlags.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import argparse 4 | 5 | # Includes the "common" folder that comes from GitHub 6 | sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'common')) 7 | import bit9api 8 | 9 | bit9 = bit9api.bit9Api( 10 | "https://bit9.server.xyz", # Replace with actual Bit9 server URL 11 | token="api_token", # Replace with actual Bit9 user token for VT integration 12 | ssl_verify=False # Don't validate server's SSL certificate. Set to True unless using self-signed cert on IIS 13 | ) 14 | 15 | # This function will perform the lookup of the sync flags and return the translated value 16 | def convert_sync_flags(syncFlag): 17 | # Set the found_flags value back to the base of no flags found 18 | found_flags = [] 19 | 20 | # List of the sync flags and their associated translation 21 | hexSyncFlags = [(0x01, "Agent is going through initialization"), (0x02, "Agent is going through full cache re-synch"), (0x08, "Agent config list is out of date"), (0x10, "Agent Enforcement is out of date"), (0x20, "Kernel is not connected to the agent"), (0x40, "Agent events timestamps indicate that system clock is out of synch"), (0x80, "Agent has failed the health check"), (0x100, "This is clone that is tracking only new files"), (0x200, "This version of kernel is not supported by the agent (Linux only)")] 22 | 23 | # Checks if syncFlag is 0, then does the translation of the sync flags 24 | if syncFlag != 0: 25 | # This is where we compare the syncFlags to the values in hexSyncFlags, then print the matching translations 26 | for flag,trans in hexSyncFlags: 27 | if syncFlag&flag>0: 28 | # Store the sync flag and the translation in the found_flags list 29 | found_flags.append((flag,trans)) 30 | else: 31 | found_flags = [(0,"No sync issues")] 32 | return found_flags 33 | 34 | # This function will parse the command line used when running the script, and does the search of the 35 | def main(argv): 36 | # Create the initial search_conditions list that will be populated 37 | search_conditions=[] 38 | 39 | # Generate the parser object 40 | parser = argparse.ArgumentParser(description='This is a sample to search the API and return sync flags. All searches are done via a LIKE search') 41 | parser.add_argument('-n', action='store', dest='comp_name', help='Computer name to search for') 42 | parser.add_argument('-p', action='store', dest='policy', help='Policy name to search for') 43 | parser.add_argument('-u', action='store', dest='user_name', help='Last logged in user name to search for') 44 | parser.add_argument('-c', action='store', dest='connect_tf', help='Either true or false for connected computers') 45 | 46 | if argv == []: 47 | print("No arguments were provided") 48 | parser.print_help() 49 | sys.exit(1) 50 | 51 | # Store the results from the command line in the 'results' variable 52 | results = parser.parse_args() 53 | 54 | # Add the arguments into ths search_conditions list 55 | if results.comp_name != None: 56 | search_conditions.append('name:*'+results.comp_name+'*') 57 | if results.policy != None: 58 | search_conditions.append('policyName:*'+results.policy+'*') 59 | if results.user_name != None: 60 | search_conditions.append('users:*'+results.user_name+'*') 61 | if results.connect_tf != None: 62 | if results.connect_tf in ("true", "false"): 63 | search_conditions.append('connected:'+results.connect_tf) 64 | elif results.connect_tf not in ("true", "false"): 65 | print("Ignoring connected argument. It MUST be equal to either 'true' or 'false'") 66 | 67 | # Find all computers using the parameters provided at the command line 68 | comps = bit9.search('v1/computer', search_conditions) 69 | 70 | # For every found computer, print out the name, IP, sum of the sync flags, then send the data to the conver_sync_flags function 71 | for c in comps: 72 | print("Computer: %s (IP: %s)" % (c['name'], c['ipAddress'])) 73 | print("Sync Flags: %s" % c['syncFlags']) 74 | translated_syncFlags=convert_sync_flags(c['syncFlags']) 75 | # Go through each result that was translated and print them out 76 | for flag, trans in translated_syncFlags: 77 | print("Sync Flag '%s' translates to '%s'" % (flag, trans)) 78 | 79 | if __name__ == "__main__": 80 | main(sys.argv[1:]) 81 | -------------------------------------------------------------------------------- /bit9PlatformAPI/examples/samples/change_diagnostics.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import time 4 | 5 | # Includes the "common" folder that comes from GitHub 6 | sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'common')) 7 | import bit9api 8 | 9 | bit9 = bit9api.bit9Api( 10 | "https://bit9.server.xyz", # Replace with actual Bit9 server URL 11 | token="api_token", # Replace with actual Bit9 user token for VT integration 12 | ssl_verify=False # Don't validate server's SSL certificate. Set to True unless using self-signed cert on IIS 13 | ) 14 | 15 | # Set the desired debug properties here. Documentation for this can be found here: https://github.com/carbonblack/bit9platform/tree/master/bit9PlatformAPI/docs 16 | kernelTrace = 4 17 | debugLevel = 6 18 | debugDuration = 1 19 | 20 | # Find all computers with the specified name that are connected 21 | comps = bit9.search('v1/computer', ['name:DOMAIN\NAME', 'connected:true']) 22 | 23 | # Iterate through each computer that was found and perform the specified actions 24 | for c in comps: 25 | print("Changing debug level for computer %s (IP: %s)" % (c['name'], c['ipAddress'])) 26 | c['kernelDebugLevel'] = kernelTrace 27 | c['debugLevel'] = debugLevel 28 | c['debugDuration'] = debugDuration 29 | bit9.update('v1/computer', c,'','changeDiagnostics=true') 30 | 31 | # Sleep for the debugDuration 32 | # In order to account for some delay in setting the debugging, multiplying the debugDuration (which is in minutes) by 70 to move it into seconds and add some overhead 33 | debugDurationSeconds = debugDuration * 70 34 | print('Sleeping for %s seconds to let the debugging happen' % debugDurationSeconds) 35 | for i in range(debugDurationSeconds,0,-1): 36 | time.sleep(1) 37 | sys.stdout.write(str(i)+' ') 38 | sys.stdout.flush() 39 | 40 | # # Iterate through each computer again and trigger a diagnostic upload 41 | # comps = bit9.search('v1/computer', ['name:domain\example', 'connected:true']) 42 | for c in comps: 43 | print("Triggering diagnostic upload for computer %s (IP: %s)" % (c['name'], c['ipAddress'])) 44 | c['debugFlags'] = 0x01 45 | bit9.update('v1/computer', c,'','changeDiagnostics=true') 46 | -------------------------------------------------------------------------------- /bit9PlatformAPI/examples/samples/initiate_cache_check.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Example Usage: 4 | # python initiate_cache_check.py -q name:BIT9SE\W7-LOW 5 | 6 | from pprint import pprint 7 | import logging, csv, sys, os, requests, argparse 8 | from bit9api import bit9Api 9 | from logging import handlers 10 | 11 | requests.packages.urllib3.disable_warnings() 12 | logging.getLogger("requests").setLevel(logging.WARNING) 13 | 14 | LOG_FILENAME='initiate_cache_check.log' 15 | logging.basicConfig(level=logging.INFO) 16 | logger = logging.getLogger(__name__) 17 | logger.propagate = False 18 | formatter = logging.Formatter("%(asctime)s - %(funcName)s - %(levelname)s - %(message)s") 19 | handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=1048576,backupCount=5,) 20 | handler.setLevel(logging.INFO) 21 | handler.setFormatter(formatter) 22 | logger.addHandler(handler) 23 | 24 | 25 | def build_cli_parser(): 26 | parser = argparse.ArgumentParser(description="This script initiates Cache Checks using the Bit9 API.") 27 | # for each supported output type, add an option 28 | # 29 | parser.add_argument("-s", "--server", action="store", default='https://192.168.230.4', dest="server", 30 | help="Bit9 server's URL. e.g., https://192.168.230.4") 31 | parser.add_argument("-a", "--apitoken", action="store", default='AACB5C5F-D9B4-4694-AB9A-8640FF79D401', dest="token", 32 | help="API Token for Carbon Black server") 33 | parser.add_argument("-n", "--ssl-verify", action="store", default=False, dest="ssl_verify", 34 | help="Verify server SSL certificate. Defaults to 'False': Do not verify.") 35 | parser.add_argument("-q", "--query", action="append", default=[], dest="query", 36 | help="query to select computers to act upon") 37 | return parser 38 | 39 | def init_cc(bit9, agent_id, ccLevel, refreshFlags): 40 | print "\nWe'll fix it right up! Bit9 Agent ID: %d, Cache Check Level %s and Policy Rule Sync %d" % (agent_id, ccLevel, refreshFlags) 41 | 42 | url_params = 'changeDiagnostics=true' 43 | api_obj = '/v1/computer' 44 | data = {'id': agent_id, 'ccLevel': ccLevel, 'refreshFlags': refreshFlags} 45 | 46 | results = bit9.create(api_obj, data, url_params) 47 | print "CCLevel %s and Policy Refresh %s scheduled for %s" % (str(results['ccLevel']), str(results['refreshFlags']), results['name']) 48 | logger.info("CCLevel %s and Policy Refresh %s scheduled for %s" % (str(results['ccLevel']), str(results['refreshFlags']), results['name'])) 49 | 50 | def main(argv): 51 | parser = build_cli_parser() 52 | args = parser.parse_args() 53 | if not args.server or not args.token or args.query is None: 54 | print "Missing required param; run with --help for usage" 55 | sys.exit(-1) 56 | 57 | print "Computer search criteria: %s" % args.query 58 | logger.info("Computer search criteria: %s" % args.query) 59 | 60 | bit9 = bit9Api (args.server, token=args.token, ssl_verify=args.ssl_verify) 61 | search_conditions = args.query 62 | 63 | comps = bit9.search('v1/computer', search_conditions) 64 | 65 | for comp in comps: 66 | if comp['ccLevel'] != 0: 67 | print "%s already performing a Cache Consistency check. Skipping this computer." % comp['name'] 68 | logger.info("%s already performing a Cache Consistency check. Skipping this computer." % comp['name']) 69 | continue 70 | 71 | agent_id = comp['id'] 72 | 73 | ccLevel = raw_input("\n\nInitiate Cache Check for Computer '%s' in policy '%s'\n" 74 | "Cache consistency check level can be one of:\n" 75 | "0 = None\n" 76 | "1 = Quick verification\n" 77 | "2 = Rescan known files\n" 78 | "3 = Full scan for new files: [0,1,2,3] " % (comp['name'], comp['policyName'])) 79 | 80 | if not ccLevel in ['1', '2','3']: 81 | print "User response was not '1', '2' or '3'. Skipping cache check for %s!" % comp['name'] 82 | logger.info("User response was not '1', '2' or '3'. Skipping cache check for %s!" % comp['name']) 83 | ccLevel = 0 84 | 85 | 86 | refreshFlags = raw_input("\nRequest Resync of Policy Rules for Computer '%s' in policy '%s'\n" 87 | "Resync of policy rules can be one of:\n" 88 | "0 = None\n" 89 | "1 = Refresh confg list\n" 90 | "2 = Refresh config list from the file: [0,1,2] " % (comp['name'], comp['policyName'])) 91 | 92 | if refreshFlags == '1': 93 | refreshFlags = 32 94 | elif refreshFlags == '2': 95 | refreshFlags = 4096 96 | else: 97 | print "User response was not '1' or '2'. Skipping policy rules sync for Computer %s!" % comp['name'] 98 | logger.info("User response was not '1' or '2'. Skipping policy rules sync for Computer %s!" % comp['name']) 99 | refreshFlags = 0 100 | 101 | if ccLevel == 0 and refreshFlags == 0: 102 | print "\nCache Check and Policy Refresh both unset, skipping %s" % comp['name'] 103 | logger.info("Cache Check and Policy Refresh both unset, skipping %s" % comp['name']) 104 | pass 105 | else: 106 | init_cc(bit9, agent_id, ccLevel, refreshFlags) 107 | 108 | if __name__ == "__main__": 109 | sys.exit(main(sys.argv[1:])) 110 | 111 | '''Available computer fields as of Bit9 7.2.1.710 112 | CLIPassword 113 | SCEPStatus 114 | agentCacheSize 115 | agentMemoryDumps 116 | agentQueueSize 117 | agentVersion 118 | automaticPolicy 119 | cbSensorFlags 120 | cbSensorId 121 | cbSensorVersion 122 | ccFlags 123 | ccLevel 124 | clVersion 125 | computerTag 126 | connected 127 | dateCreated 128 | daysOffline 129 | debugDuration 130 | debugFlags 131 | debugLevel 132 | deleted 133 | description 134 | disconnectedEnforcementLevel 135 | enforcementLevel 136 | forceUpgrade 137 | hasHealthCheckErrors 138 | id 139 | initializing 140 | ipAddress 141 | kernelDebugLevel 142 | lastPollDate 143 | lastRegisterDate 144 | localApproval 145 | macAddress 146 | machineModel 147 | memorySize 148 | name 149 | osName 150 | osShortName 151 | platformId 152 | policyId 153 | policyName 154 | policyStatusDetails 155 | prioritized 156 | processorCount 157 | processorModel 158 | processorSpeed 159 | refreshFlags 160 | supportedKernel 161 | syncFlags 162 | syncPercent 163 | systemMemoryDumps 164 | tamperProtectionActive 165 | tdCount 166 | template 167 | templateCloneCleanupMode 168 | templateCloneCleanupTime 169 | templateCloneCleanupTimeScale 170 | templateComputerId 171 | templateDate 172 | templateTrackModsOnly 173 | uninstalled 174 | upgradeError 175 | upgradeErrorCount 176 | upgradeErrorTime 177 | upgradeStatus 178 | users 179 | virtualPlatform 180 | virtualized 181 | ''' -------------------------------------------------------------------------------- /bit9PlatformAPI/examples/virustotal.ini.example: -------------------------------------------------------------------------------- 1 | [bridge] 2 | bit9_server_url=https://localhost 3 | bit9_server_sslverify=False 4 | bit9_server_token=xxxx 5 | vt_api_key=xxxx 6 | retrieve_files=True 7 | upload_binaries_to_vt=True 8 | download_location=c:\test 9 | connector_name=VirusTotal 10 | 11 | ; uncomment to save logs to a file 12 | ; log_file=c:\virustotal.log 13 | --------------------------------------------------------------------------------