def set_last_contact_time(self, *args, **kwargs): """ Restricts the devices that this query is performed on to the specified last contact time. Args: *args (list): Not used, retained for compatibility. **kwargs (dict): Keyword arguments to this function. The critical ones are "start" (the start time), "end" (the end time), and "range" (the range value). Returns: DeviceSearchQuery: This instance. Raises: ApiError: If an invalid combination of keyword parameters are specified. """ if kwargs.get("start", None) and kwargs.get("end", None): if kwargs.get("range", None): raise ApiError( "cannot specify range= in addition to start= and end=") stime = kwargs["start"] if not isinstance(stime, str): stime = stime.isoformat() etime = kwargs["end"] if not isinstance(etime, str): etime = etime.isoformat() self._time_filter = {"start": stime, "end": etime} elif kwargs.get("range", None): if kwargs.get("start", None) or kwargs.get("end", None): raise ApiError( "cannot specify start= or end= in addition to range=") self._time_filter = {"range": kwargs["range"]} else: raise ApiError("must specify either start= and end= or range=") return self
def _lr_post_command(self, data): retries = self.MAX_RETRY_COUNT if "name" in data and data["name"] not in self.session_data[ "supported_commands"]: raise ApiError("Command {0} not supported by this device".format( data["name"])) while retries: try: data["session_id"] = self.session_id resp = self._cb.post_object( "{cblr_base}/sessions/{0}/commands".format( self.session_id, cblr_base=self.cblr_base), data) except ObjectNotFoundError as e: try: error_message = json.loads(e.message) if error_message["error_code"] == "NOT_FOUND": self.session_id, self.session_data = \ self._cblr_manager._get_or_create_session(self.device_id) retries -= 1 continue except Exception: pass raise ApiError("Received 404 error from server: {0}".format( e.message)) else: return resp raise TimeoutError( message="Command {0} failed after {1} retries".format( data["name"], self.MAX_RETRY_COUNT))
def set_create_time(self, *args, **kwargs): """ Restricts the alerts that this query is performed on to the specified creation time. The time may either be specified as a start and end point or as a range. Args: *args (list): Not used. **kwargs (dict): Used to specify start= for start time, end= for end time, and range= for range. Returns: BaseAlertSearchQuery: This instance. """ if kwargs.get("start", None) and kwargs.get("end", None): if kwargs.get("range", None): raise ApiError("cannot specify range= in addition to start= and end=") stime = kwargs["start"] if not isinstance(stime, str): stime = stime.isoformat() etime = kwargs["end"] if not isinstance(etime, str): etime = etime.isoformat() self._time_filters["create_time"] = {"start": stime, "end": etime} elif kwargs.get("range", None): if kwargs.get("start", None) or kwargs.get("end", None): raise ApiError("cannot specify start= or end= in addition to range=") self._time_filters["create_time"] = {"range": kwargs["range"]} else: raise ApiError("must specify either start= and end= or range=") return self
def set_time_range(self, key, **kwargs): """ Restricts the alerts that this query is performed on to the specified time range. The time may either be specified as a start and end point or as a range. Args: key (str): The key to use for criteria one of create_time, first_event_time, last_event_time, or last_update_time **kwargs (dict): Used to specify start= for start time, end= for end time, and range= for range. Returns: BaseAlertSearchQuery: This instance. """ if key not in ["create_time", "first_event_time", "last_event_time", "last_update_time"]: raise ApiError("key must be one of create_time, first_event_time, last_event_time, or last_update_time") if kwargs.get("start", None) and kwargs.get("end", None): if kwargs.get("range", None): raise ApiError("cannot specify range= in addition to start= and end=") stime = kwargs["start"] if not isinstance(stime, str): stime = stime.isoformat() etime = kwargs["end"] if not isinstance(etime, str): etime = etime.isoformat() self._time_filters[key] = {"start": stime, "end": etime} elif kwargs.get("range", None): if kwargs.get("start", None) or kwargs.get("end", None): raise ApiError("cannot specify start= or end= in addition to range=") self._time_filters[key] = {"range": kwargs["range"]} else: raise ApiError("must specify either start= and end= or range=") return self
def create(cls, cb, template=None, **kwargs): """ Returns either a new Grant, or a GrantBuilder to begin the process of creating a new grant. Args: cb (CBCloudAPI): A reference to the CBCloudAPI object. template (dict): Optional template to use for creating the grant object. kwargs (dict): Additional arguments to be used to specify the principal, if template is None. The arguments to be used are 'org_key' and 'userid' for the two parts of the ID. Returns: Grant: The new grant object, if the template is specified. GrantBuilder: If template was None, returns a GrantBuilder object. Call methods on it to set up the new grant, and then call build() to create the new grant. Raises: ApiError: If the principal is inadequately specified (whether for the Grant or GrantBuilder). """ if template: if not template.get('principal', None): raise ApiError('principal must be specified in Grant template') t = copy.deepcopy(template) grant = Grant(cb, t['principal'], t) grant._update_object() return grant if not all([key in kwargs for key in ['org_key', 'userid']]): raise ApiError( 'orgid and userid must be specified as keyword arguments to create' ) return Grant.GrantBuilder( cb, f"psc:user:{kwargs['org_key']}:{kwargs['userid']}")
def set_rows(self, rows): """ Sets the 'rows' query body parameter to the 'start search' API call, determining how many rows to request. Args: rows (int): How many rows to request. """ if not isinstance(rows, int): raise ApiError( f"Rows must be an integer. {rows} is a {type(rows)}.") if rows > 10000: raise ApiError("Maximum allowed value for rows is 10000") super(EnrichedEventQuery, self).set_rows(rows) return self
def change_role(self, role_urn, org=None): """ Add the specified role to the user (either to the grant or the profiles). Args: role_urn (str): URN of the role to be added. org (str): If specified, only profiles that match this organization will have the role added. Organization may be specified as either an org key or a URN. Raises: ApiError: If the user is a "legacy" user that has no grant. """ my_org = None if org is None else normalize_org(org) grant = self.grant() if grant: prof_list = grant.profiles_ if len(prof_list) > 0: for profile in prof_list: add_role = True if my_org and my_org not in profile.allowed_orgs: add_role = False if add_role and role_urn not in profile.roles: profile.roles += [role_urn] grant.touch() elif role_urn not in grant.roles: grant.roles += [role_urn] grant.touch() grant.save() else: raise ApiError("legacy user has no grant")
def download(self): """ Uses the query parameters that have been set to download all device listings in CSV format. Example: >>> cb.select(Device).set_status(["ALL"]).download() Returns: str: The CSV raw data as returned from the server. Raises: ApiError: If status values have not been set before calling this function. """ tmp = self._criteria.get("status", []) if not tmp: raise ApiError("at least one status must be specified to download") query_params = {"status": ",".join(tmp)} tmp = self._criteria.get("ad_group_id", []) if tmp: query_params["ad_group_id"] = ",".join([str(t) for t in tmp]) tmp = self._criteria.get("policy_id", []) if tmp: query_params["policy_id"] = ",".join([str(t) for t in tmp]) tmp = self._criteria.get("target_priority", []) if tmp: query_params["target_priority"] = ",".join(tmp) tmp = self._query_builder._collapse() if tmp: query_params["query_string"] = tmp if self._sortcriteria: query_params["sort_field"] = self._sortcriteria["field"] query_params["sort_order"] = self._sortcriteria["order"] url = self._build_url("/_search/download") return self._cb.get_raw_data(url, query_params)
def get_vulnerability_summary(self, category=None): """ Get the vulnerabilities associated with this device Args: category (string): (optional) vulnerabilty category (OS, APP) Returns: dict: summary for the vulnerabilities for this device """ VALID_CATEGORY = ["OS", "APP"] query_params = {} url = '/vulnerability/assessment/api/v1/orgs/{}' if category and category not in VALID_CATEGORY: raise ApiError("Invalid category provided") elif category: query_params["category"] = category req_url = url.format(self._cb.credentials.org_key ) + '/devices/{}/vulnerabilities/summary'.format( self.id) return self._cb.get_object(req_url, query_params)
def stop(self): """Stop a running query. Returns: (bool): True if query was stopped successfully, False otherwise. Raises: ServerError: If the server response cannot be parsed as JSON. """ if self._is_deleted: raise ApiError("cannot stop a deleted query") url = self.urlobject_single.format(self._cb.credentials.org_key, self.id) + "/status" result = self._cb.put_object(url, {'status': 'CANCELLED'}) if (result.status_code == 200): try: self._info = result.json() self._last_refresh_time = time.time() return True except Exception: raise ServerError( result.status_code, "Cannot parse response as JSON: {0:s}".format( result.content)) return False
def update(self, **kwargs): """Updates this watchlist with the given arguments. Arguments: **kwargs (dict(str, str)): The fields to update. Raises: InvalidObjectError: If `id` is missing or Watchlist.validate() fails. ApiError: If `report_ids` is given and is empty. Example: >>> watchlist.update(name="New Name") """ if not self.id: raise InvalidObjectError("missing Watchlist ID") # NOTE(ww): Special case, according to the docs. if "report_ids" in kwargs and not kwargs["report_ids"]: raise ApiError( "can't update a watchlist to have an empty report list") for key, value in kwargs.items(): if key in self._info: self._info[key] = value self.validate() url = "/threathunter/watchlistmgr/v3/orgs/{}/watchlists/{}".format( self._cb.credentials.org_key, self.id) new_info = self._cb.put_object(url, self._info).json() self._info.update(new_info)
def _perform_query(self, cls, **kwargs): if hasattr(cls, "_query_implementation"): return cls._query_implementation(self, **kwargs) else: raise ApiError( "All Carbon Black Cloud models must provide _query_implementation" )
def _perform_query(self, start=0, rows=0): if self._run_id is None: raise ApiError("Can't retrieve results without a run ID") url = self._doc_class.urlobject.format( self._cb.credentials.org_key, self._run_id ) current = start numrows = 0 still_querying = True while still_querying: request = self._build_request(start, rows) resp = self._cb.post_object(url, body=request) result = resp.json() self._total_results = result["num_found"] if self._total_results > MAX_RESULTS_LIMIT: self._total_results = MAX_RESULTS_LIMIT self._count_valid = True results = result.get("results", []) for item in results: yield self._doc_class(self._cb, item) current += 1 numrows += 1 if rows and numrows == rows: still_querying = False break start = current if current >= self._total_results: still_querying = False break
def or_(self, **kwargs): """Unsupported. Will raise if called. Raises: ApiError: .or_() cannot be called on Endpoint Standard queries. """ raise ApiError(".or_() cannot be called on Endpoint Standard queries.")
def _get_query_parameters(self): args = self._default_args.copy() if not (self._facet_fields or self._ranges): raise ApiError( "Event Facet Queries require at least one field or range to be requested. " "Use add_facet_field(['my_facet_field']) to add fields to the request, " "or use add_range({}) to add ranges to the request.") terms = {} if self._facet_fields: terms["fields"] = self._facet_fields if self._facet_rows: terms["rows"] = self._facet_rows args["terms"] = terms if self._ranges: args["ranges"] = self._ranges if self._criteria: args["criteria"] = self._criteria if self._exclusions: args["exclusions"] = self._exclusions if self._time_range: args["time_range"] = self._time_range args['query'] = self._query_builder._collapse() if self._query_builder._process_guid is not None: args["process_guid"] = self._query_builder._process_guid if 'process_guid:' in args['query']: q = args['query'].split('process_guid:', 1)[1].split(' ', 1)[0] args["process_guid"] = q return args
def _bulk_threat_update_status(self, threat_ids, status, remediation, comment): """ Update the status of alerts associated with multiple threat IDs, past and future. Args: threat_ids (list): List of string threat IDs. status (str): The status to set for all alerts, either "OPEN" or "DISMISSED". remediation (str): The remediation state to set for all alerts. comment (str): The comment to set for all alerts. Returns: str: The request ID of the pending request, which may be used to select a WorkflowStatus object. """ if not all(isinstance(t, str) for t in threat_ids): raise ApiError("One or more invalid threat ID values") request = {"state": status, "threat_id": threat_ids} if remediation is not None: request["remediation_state"] = remediation if comment is not None: request["comment"] = comment url = "/appservices/v6/orgs/{0}/threat/workflow/_criteria".format( self.credentials.org_key) resp = self.post_object(url, body=request) output = resp.json() return output["request_id"]
def set_rows(self, rows): """ Sets the 'rows' query body parameter to the 'start search' API call, determining how many rows to request. Args: rows (int): How many rows to request. """ if not isinstance(rows, int): raise ApiError( f"Rows must be an integer. {rows} is a {type(rows)}.") if rows > 10000: raise ApiError("Maximum allowed value for rows is 10000") self._rows = rows self._default_args["rows"] = self._rows return self
def or_(self, **kwargs): """Unsupported. Will raise if called. Raises: APIError: TreeQueries do not support _or() filters. """ raise ApiError(".or_() cannot be called on Tree queries")
def _refresh(self): if self._is_deleted: raise ApiError("cannot refresh a deleted query") url = self.urlobject_single.format(self._cb.credentials.org_key, self.id) resp = self._cb.get_object(url) self._info = resp self._last_refresh_time = time.time() return True
def _refresh(self): """ Throws an error, since Profile data cannot be refreshed. Raises: ApiError: Always. """ raise ApiError("Profile cannot be refreshed")
def disable_all_access(self): """ Disables all access profiles held by ths user. Raises: ApiError: If the user is a "legacy" user that has no grant. """ if self._disable_all_access(): raise ApiError("legacy user has no grant")
def _get_sensor_type(self): """ Calculates the sensor type that should be installed on this compute resource. May also raise errors if the compute resource is ineligible or has an invalid type. Returns: str: The sensor type to be used for this compute resource. Raises: ApiError: If the compute node is not eligible or is of an invalid type. """ if self.eligibility != 'ELIGIBLE': raise ApiError(f"device {self.name} does not allow sensor installation ({self.eligibility})") my_type = self.os_type if my_type in SensorKit.COMPUTE_RESOURCE_MAP: my_type = SensorKit.COMPUTE_RESOURCE_MAP[my_type] if my_type not in SensorKit.VALID_TYPES: raise ApiError(f"device {self.name} type {self.os_type} not supported for sensor installation") return my_type
def __init__(self, cb, initial_data=None): if not initial_data: raise ApiError( "ReportSeverity can only be initialized from initial_data") super(ReportSeverity, self).__init__(cb, model_unique_id=initial_data.get( self.primary_key), initial_data=initial_data, force_init=False, full_doc=True)
def _perform_query(self, rows=0): if self._run_id is None: raise ApiError("Can't retrieve results without a run ID") url = self._doc_class.urlobject.format(self._cb.credentials.org_key, self._run_id) request = self._build_request(rows) resp = self._cb.post_object(url, body=request) result = resp.json() results = result.get("terms", []) for item in results: yield self._doc_class(self._cb, item)
def set_policy_names(self, policy_names): """Sets the device.policy_name criteria. Arguments: policy_names ([str]): Device policy names to filter on. Returns: The FacetQuery object with specified policy_names. """ if not all(isinstance(name, str) for name in policy_names): raise ApiError("policy_names must be a list of strings.") self._update_criteria("device.policy_name", policy_names) return self
def set_policy_ids(self, policy_ids): """Sets the device.policy_id criteria. Arguments: policy_ids ([int]): Device policy ID's to filter on. Returns: The FacetQuery object with specified policy_ids. """ if not all(isinstance(id, int) for id in policy_ids): raise ApiError("policy_ids must be a list of integers.") self._update_criteria("device.policy_id", policy_ids) return self
def set_device_names(self, device_names): """Sets the device.name criteria filter. Arguments: device_names ([str]): Device names to filter on. Returns: The FacetQuery with specified device.name. """ if not all(isinstance(name, str) for name in device_names): raise ApiError("One or more invalid device names") self._update_criteria("device.name", device_names) return self
def set_device_ids(self, device_ids): """Sets the device.id criteria filter. Arguments: device_ids ([int]): Device IDs to filter on. Returns: The FacetQuery with specified device.id. """ if not all(isinstance(device_id, int) for device_id in device_ids): raise ApiError("One or more invalid device IDs") self._update_criteria("device.id", device_ids) return self
def set_statuses(self, statuses): """Sets the status criteria. Arguments: statuses ([str]): Query statuses to filter on. Returns: The ResultQuery object with specified statuses. """ if not all(isinstance(status, str) for status in statuses): raise ApiError("statuses must be a list of strings.") self._update_criteria("status", statuses) return self
def set_template_ids(self, template_ids): """Sets the template_id criteria filter. Arguments: template_ids ([str]): Template IDs to filter on. Returns: The ResultQuery with specified template_id. """ if not all(isinstance(template_id, str) for template_id in template_ids): raise ApiError("One or more invalid template IDs") self._update_criteria("template_id", template_ids) return self