laceworksdk.http_session
HttpSession class for package HTTP functions.
1# -*- coding: utf-8 -*- 2""" 3HttpSession class for package HTTP functions. 4""" 5 6import json 7import logging 8import requests 9 10from datetime import datetime, timezone 11from requests.adapters import HTTPAdapter 12from urllib3.util.retry import Retry 13 14from laceworksdk import version 15from laceworksdk.config import ( 16 DEFAULT_BASE_DOMAIN, 17 DEFAULT_ACCESS_TOKEN_EXPIRATION, 18 DEFAULT_SUCCESS_RESPONSE_CODES, 19 RATE_LIMIT_RESPONSE_CODE 20) 21from laceworksdk.exceptions import ApiError, MalformedResponse, RateLimitError 22 23logger = logging.getLogger(__name__) 24 25 26class HttpSession: 27 """ 28 Package HttpSession class. 29 """ 30 31 _access_token = None 32 _access_token_expiry = None 33 34 def __init__(self, account, subaccount, api_key, api_secret, base_domain): 35 """ 36 Initializes the HttpSession object. 37 38 :param account: a Lacework Account name 39 :param subaccount: a Lacework Sub-account name 40 :param api_key: a Lacework API Key 41 :param api_secret: a Lacework API Secret 42 :param base_domain: a Lacework Domain (defaults to "lacework.net") 43 44 :return HttpSession object. 45 """ 46 47 super().__init__() 48 49 # Create a requests session 50 self._session = self._retry_session() 51 52 # Set the base parameters 53 self._api_key = api_key 54 self._api_secret = api_secret 55 self._base_domain = base_domain or DEFAULT_BASE_DOMAIN 56 57 domain_string = f".{self._base_domain}" 58 if account.endswith(domain_string): 59 account = account[:-len(domain_string)] 60 61 self._base_url = f"https://{account}.{self._base_domain}" 62 self._subaccount = subaccount 63 self._org_level_access = False 64 65 # Get an access token 66 self._check_access_token() 67 68 def _retry_session(self, 69 retries=3, 70 backoff_factor=0.3, 71 status_forcelist=(500, 502, 503, 504), 72 allowed_methods=None): 73 """ 74 A method to set up automatic retries on HTTP requests that fail. 75 """ 76 77 # Create a new requests session 78 session = requests.Session() 79 80 # Establish the retry criteria 81 retry_strategy = Retry( 82 total=retries, 83 backoff_factor=backoff_factor, 84 status_forcelist=status_forcelist, 85 allowed_methods=allowed_methods, 86 raise_on_status=False 87 ) 88 89 # Build the adapter with the retry criteria 90 adapter = HTTPAdapter(max_retries=retry_strategy) 91 92 # Bind the adapter to HTTP/HTTPS calls 93 session.mount("http://", adapter) 94 session.mount("https://", adapter) 95 96 return session 97 98 def _check_access_token(self): 99 """ 100 A method to check the validity of the access token. 101 """ 102 103 if self._access_token is None or self._access_token_expiry < datetime.now(timezone.utc): 104 105 response = self._get_access_token() 106 107 # Parse and restructure the returned date (necessary for Python 3.6) 108 expiry_date = response.json()["expiresAt"].replace("Z", "+0000") 109 110 # Update the access token and expiration 111 self._access_token_expiry = datetime.strptime(expiry_date, "%Y-%m-%dT%H:%M:%S.%f%z") 112 self._access_token = response.json()["token"] 113 114 def _check_response_code(self, response, expected_response_codes): 115 """ 116 Check the requests.response.status_code to make sure it's one that we expected. 117 """ 118 if response.status_code in expected_response_codes: 119 pass 120 elif response.status_code == RATE_LIMIT_RESPONSE_CODE: 121 raise RateLimitError(response) 122 else: 123 raise ApiError(response) 124 125 def _print_debug_response(self, response): 126 """ 127 Print the debug logging, based on the returned content type. 128 """ 129 130 logger.debug(response.headers) 131 132 # If it's supposed to be a JSON response, parse and log, otherwise, log the raw text 133 if "application/json" in response.headers.get("Content-Type", "").lower(): 134 try: 135 if response.status_code != 204: 136 logger.debug(json.dumps(response.json(), indent=2)) 137 else: 138 logger.debug("204 No Content Returned") 139 except ValueError: 140 logger.warning("Error parsing JSON response body") 141 else: 142 logger.debug(response.text) 143 144 def _get_access_token(self): 145 """ 146 A method to fetch a new access token from Lacework. 147 148 :return requests response 149 """ 150 151 logger.info("Creating Access Token in Lacework...") 152 153 uri = f"{self._base_url}/api/v2/access/tokens" 154 155 # Build the access token request headers 156 headers = { 157 "X-LW-UAKS": self._api_secret, 158 "Content-Type": "application/json", 159 "User-Agent": f"laceworksdk-python-client/{version}" 160 } 161 162 # Build the access token request data 163 data = { 164 "keyId": self._api_key, 165 "expiryTime": DEFAULT_ACCESS_TOKEN_EXPIRATION 166 } 167 168 response = None 169 170 try: 171 response = self._session.post(uri, json=data, headers=headers) 172 173 # Validate the response 174 self._check_response_code(response, DEFAULT_SUCCESS_RESPONSE_CODES) 175 176 self._print_debug_response(response) 177 178 except Exception: 179 if response: 180 raise ApiError(response) 181 182 logger.error("Call to _get_access_token() returned no response.") 183 raise 184 185 return response 186 187 def _get_request_headers(self, org_access=False): 188 """ 189 A method to build the HTTP request headers for Lacework. 190 191 :param org_access: boolean representing whether the request should be performed at the Organization level 192 """ 193 194 # Build the request headers 195 headers = self._session.headers 196 197 headers["Authorization"] = f"Bearer {self._access_token}" 198 headers["Org-Access"] = "true" if self._org_level_access or org_access else "false" 199 headers["User-Agent"] = f"laceworksdk-python-client/{version}" 200 201 if self._subaccount: 202 headers["Account-Name"] = self._subaccount 203 204 logger.debug("Request headers: \n" + json.dumps(dict(headers), indent=2)) 205 206 return headers 207 208 def _request(self, method, uri, **kwargs): 209 """ 210 A method to abstract building requests to Lacework. 211 212 :param method: string representing the HTTP request method ("GET", "POST", ...) 213 :param uri: string representing the URI of the API endpoint 214 :param kwargs: passed on to the requests package 215 216 :return: response json 217 218 :raises: ApiError if anything but expected response code is returned 219 """ 220 221 self._check_access_token() 222 223 # Strip the protocol/host if provided 224 domain_begin = uri.find(self._base_domain) 225 if domain_begin >= 0: 226 domain_end = domain_begin + len(self._base_domain) 227 uri = uri[domain_end:] 228 229 uri = f"{self._base_url}{uri}" 230 231 logger.info(f"{method} request to URI: {uri}") 232 233 # Check for 'org' - if True, make an organization-level API call 234 # TODO: Remove this on v1.0 release - this is done for back compat 235 org = kwargs.pop("org", None) 236 headers = self._get_request_headers(org_access=org) 237 238 # Check for 'data' or 'json' 239 data = kwargs.get("data", "") 240 json = kwargs.get("json", "") 241 if data or json: 242 logger.debug(f"{method} request data:\nData: {data}\nJSON: {json}") 243 244 # TODO: Remove this on v1.0 release - this is done for back compat 245 if data and not json: 246 kwargs["json"] = data 247 kwargs.pop("data") 248 249 # Make the HTTP request to the API endpoint 250 response = self._session.request(method, uri, headers=headers, **kwargs) 251 252 # Validate the response 253 self._check_response_code(response, DEFAULT_SUCCESS_RESPONSE_CODES) 254 255 self._print_debug_response(response) 256 257 # Fix for when Lacework returns a 204 with no data on searches 258 if method != "DELETE" and response.status_code == 204: 259 try: 260 response.json() 261 except Exception: 262 response._content = b'{"data": []}' 263 264 return response 265 266 def get(self, uri, params=None, **kwargs): 267 """ 268 A method to build a GET request to interact with Lacework. 269 270 :param uri: uri to send the HTTP GET request to 271 :param params: dict of parameters for the HTTP request 272 :param kwargs: passed on to the requests package 273 274 :return: response json 275 276 :raises: ApiError if anything but expected response code is returned 277 """ 278 279 # Perform a GET request 280 response = self._request("GET", uri, params=params, **kwargs) 281 282 return response 283 284 def get_pages(self, uri, params=None, **kwargs): 285 """ 286 A method to build a GET request that yields pages of data returned by Lacework. 287 288 :param uri: uri to send the initial HTTP GET request to 289 :param params: dict of parameters for the HTTP request 290 :param kwargs: passed on to the requests package 291 292 :return: a generator that yields pages of data 293 294 :raises: ApiError if anything but expected response code is returned 295 """ 296 297 response = self.get(uri, params=params, **kwargs) 298 299 while True: 300 yield response 301 302 try: 303 response_json = response.json() 304 next_page = response_json.get("paging", {}).get("urls", {}).get("nextPage") 305 except json.JSONDecodeError: 306 logger.error("Failed to decode response from Lacework as JSON.", exc_info=True) 307 logger.debug(f"Response text: {response.text}") 308 next_page = None 309 310 if next_page: 311 response = self.get(next_page, params=params, **kwargs) 312 else: 313 break 314 315 def get_data_items(self, uri, params=None, **kwargs): 316 """ 317 A method to build a GET request that yields individual objects as returned by Lacework. 318 319 :param uri: uri to send the initial HTTP GET request to 320 :param params: dict of parameters for the HTTP request 321 :param kwargs: passed on to the requests package 322 323 :return: a generator that yields individual objects from pages of data 324 325 :raises: ApiError if anything but expected response code is returned 326 :raises: MalformedResponse if the returned response does not contain a 327 top-level dictionary with an "data" key. 328 """ 329 330 # Get generator for pages of JSON data 331 pages = self.get_pages(uri, params=params, **kwargs) 332 333 for page in pages: 334 page = page.json() 335 assert isinstance(page, dict) 336 337 items = page.get("data") 338 339 if items is None: 340 error_message = f"'data' key not found in JSON data:\n{page}" 341 raise MalformedResponse(error_message) 342 343 for item in items: 344 yield item 345 346 def patch(self, uri, data=None, json=None, **kwargs): 347 """ 348 A method to build a PATCH request to interact with Lacework. 349 350 :param uri: uri to send the HTTP POST request to 351 :param data: data to be sent in the body of the request 352 :param json: data to be sent in JSON format in the body of the request 353 :param kwargs: passed on to the requests package 354 355 :return: response json 356 357 :raises: ApiError if anything but expected response code is returned 358 """ 359 360 # Perform a PATCH request 361 response = self._request("PATCH", uri, data=data, json=json, **kwargs) 362 363 return response 364 365 def post(self, uri, data=None, json=None, **kwargs): 366 """ 367 A method to build a POST request to interact with Lacework. 368 369 :param uri: uri to send the HTTP POST request to 370 :param data: data to be sent in the body of the request 371 :param json: data to be sent in JSON format in the body of the request 372 :param kwargs: passed on to the requests package 373 374 :return: response json 375 376 :raises: ApiError if anything but expected response code is returned 377 """ 378 379 # Perform a POST request 380 response = self._request("POST", uri, data=data, json=json, **kwargs) 381 382 return response 383 384 def put(self, uri, data=None, json=None, **kwargs): 385 """ 386 A method to build a PUT request to interact with Lacework. 387 388 :param uri: uri to send the HTTP POST request to 389 :param data: data to be sent in the body of the request 390 :param json: data to be sent in JSON format in the body of the request 391 :param kwargs: passed on to the requests package 392 393 :return: response json 394 395 :raises: ApiError if anything but expected response code is returned 396 """ 397 398 # Perform a PUT request 399 response = self._request("PUT", uri, data=data, json=json, **kwargs) 400 401 return response 402 403 def delete(self, uri, data=None, json=None, **kwargs): 404 """ 405 A method to build a DELETE request to interact with Lacework. 406 407 :param uri: uri to send the http DELETE request to 408 :param data: data to be sent in the body of the request 409 :param json: data to be sent in JSON format in the body of the request 410 :param kwargs: passed on to the requests package 411 412 :response: reponse json 413 414 :raises: ApiError if anything but expected response code is returned 415 """ 416 417 # Perform a DELETE request 418 response = self._request("DELETE", uri, data=data, json=json, **kwargs) 419 420 return response
27class HttpSession: 28 """ 29 Package HttpSession class. 30 """ 31 32 _access_token = None 33 _access_token_expiry = None 34 35 def __init__(self, account, subaccount, api_key, api_secret, base_domain): 36 """ 37 Initializes the HttpSession object. 38 39 :param account: a Lacework Account name 40 :param subaccount: a Lacework Sub-account name 41 :param api_key: a Lacework API Key 42 :param api_secret: a Lacework API Secret 43 :param base_domain: a Lacework Domain (defaults to "lacework.net") 44 45 :return HttpSession object. 46 """ 47 48 super().__init__() 49 50 # Create a requests session 51 self._session = self._retry_session() 52 53 # Set the base parameters 54 self._api_key = api_key 55 self._api_secret = api_secret 56 self._base_domain = base_domain or DEFAULT_BASE_DOMAIN 57 58 domain_string = f".{self._base_domain}" 59 if account.endswith(domain_string): 60 account = account[:-len(domain_string)] 61 62 self._base_url = f"https://{account}.{self._base_domain}" 63 self._subaccount = subaccount 64 self._org_level_access = False 65 66 # Get an access token 67 self._check_access_token() 68 69 def _retry_session(self, 70 retries=3, 71 backoff_factor=0.3, 72 status_forcelist=(500, 502, 503, 504), 73 allowed_methods=None): 74 """ 75 A method to set up automatic retries on HTTP requests that fail. 76 """ 77 78 # Create a new requests session 79 session = requests.Session() 80 81 # Establish the retry criteria 82 retry_strategy = Retry( 83 total=retries, 84 backoff_factor=backoff_factor, 85 status_forcelist=status_forcelist, 86 allowed_methods=allowed_methods, 87 raise_on_status=False 88 ) 89 90 # Build the adapter with the retry criteria 91 adapter = HTTPAdapter(max_retries=retry_strategy) 92 93 # Bind the adapter to HTTP/HTTPS calls 94 session.mount("http://", adapter) 95 session.mount("https://", adapter) 96 97 return session 98 99 def _check_access_token(self): 100 """ 101 A method to check the validity of the access token. 102 """ 103 104 if self._access_token is None or self._access_token_expiry < datetime.now(timezone.utc): 105 106 response = self._get_access_token() 107 108 # Parse and restructure the returned date (necessary for Python 3.6) 109 expiry_date = response.json()["expiresAt"].replace("Z", "+0000") 110 111 # Update the access token and expiration 112 self._access_token_expiry = datetime.strptime(expiry_date, "%Y-%m-%dT%H:%M:%S.%f%z") 113 self._access_token = response.json()["token"] 114 115 def _check_response_code(self, response, expected_response_codes): 116 """ 117 Check the requests.response.status_code to make sure it's one that we expected. 118 """ 119 if response.status_code in expected_response_codes: 120 pass 121 elif response.status_code == RATE_LIMIT_RESPONSE_CODE: 122 raise RateLimitError(response) 123 else: 124 raise ApiError(response) 125 126 def _print_debug_response(self, response): 127 """ 128 Print the debug logging, based on the returned content type. 129 """ 130 131 logger.debug(response.headers) 132 133 # If it's supposed to be a JSON response, parse and log, otherwise, log the raw text 134 if "application/json" in response.headers.get("Content-Type", "").lower(): 135 try: 136 if response.status_code != 204: 137 logger.debug(json.dumps(response.json(), indent=2)) 138 else: 139 logger.debug("204 No Content Returned") 140 except ValueError: 141 logger.warning("Error parsing JSON response body") 142 else: 143 logger.debug(response.text) 144 145 def _get_access_token(self): 146 """ 147 A method to fetch a new access token from Lacework. 148 149 :return requests response 150 """ 151 152 logger.info("Creating Access Token in Lacework...") 153 154 uri = f"{self._base_url}/api/v2/access/tokens" 155 156 # Build the access token request headers 157 headers = { 158 "X-LW-UAKS": self._api_secret, 159 "Content-Type": "application/json", 160 "User-Agent": f"laceworksdk-python-client/{version}" 161 } 162 163 # Build the access token request data 164 data = { 165 "keyId": self._api_key, 166 "expiryTime": DEFAULT_ACCESS_TOKEN_EXPIRATION 167 } 168 169 response = None 170 171 try: 172 response = self._session.post(uri, json=data, headers=headers) 173 174 # Validate the response 175 self._check_response_code(response, DEFAULT_SUCCESS_RESPONSE_CODES) 176 177 self._print_debug_response(response) 178 179 except Exception: 180 if response: 181 raise ApiError(response) 182 183 logger.error("Call to _get_access_token() returned no response.") 184 raise 185 186 return response 187 188 def _get_request_headers(self, org_access=False): 189 """ 190 A method to build the HTTP request headers for Lacework. 191 192 :param org_access: boolean representing whether the request should be performed at the Organization level 193 """ 194 195 # Build the request headers 196 headers = self._session.headers 197 198 headers["Authorization"] = f"Bearer {self._access_token}" 199 headers["Org-Access"] = "true" if self._org_level_access or org_access else "false" 200 headers["User-Agent"] = f"laceworksdk-python-client/{version}" 201 202 if self._subaccount: 203 headers["Account-Name"] = self._subaccount 204 205 logger.debug("Request headers: \n" + json.dumps(dict(headers), indent=2)) 206 207 return headers 208 209 def _request(self, method, uri, **kwargs): 210 """ 211 A method to abstract building requests to Lacework. 212 213 :param method: string representing the HTTP request method ("GET", "POST", ...) 214 :param uri: string representing the URI of the API endpoint 215 :param kwargs: passed on to the requests package 216 217 :return: response json 218 219 :raises: ApiError if anything but expected response code is returned 220 """ 221 222 self._check_access_token() 223 224 # Strip the protocol/host if provided 225 domain_begin = uri.find(self._base_domain) 226 if domain_begin >= 0: 227 domain_end = domain_begin + len(self._base_domain) 228 uri = uri[domain_end:] 229 230 uri = f"{self._base_url}{uri}" 231 232 logger.info(f"{method} request to URI: {uri}") 233 234 # Check for 'org' - if True, make an organization-level API call 235 # TODO: Remove this on v1.0 release - this is done for back compat 236 org = kwargs.pop("org", None) 237 headers = self._get_request_headers(org_access=org) 238 239 # Check for 'data' or 'json' 240 data = kwargs.get("data", "") 241 json = kwargs.get("json", "") 242 if data or json: 243 logger.debug(f"{method} request data:\nData: {data}\nJSON: {json}") 244 245 # TODO: Remove this on v1.0 release - this is done for back compat 246 if data and not json: 247 kwargs["json"] = data 248 kwargs.pop("data") 249 250 # Make the HTTP request to the API endpoint 251 response = self._session.request(method, uri, headers=headers, **kwargs) 252 253 # Validate the response 254 self._check_response_code(response, DEFAULT_SUCCESS_RESPONSE_CODES) 255 256 self._print_debug_response(response) 257 258 # Fix for when Lacework returns a 204 with no data on searches 259 if method != "DELETE" and response.status_code == 204: 260 try: 261 response.json() 262 except Exception: 263 response._content = b'{"data": []}' 264 265 return response 266 267 def get(self, uri, params=None, **kwargs): 268 """ 269 A method to build a GET request to interact with Lacework. 270 271 :param uri: uri to send the HTTP GET request to 272 :param params: dict of parameters for the HTTP request 273 :param kwargs: passed on to the requests package 274 275 :return: response json 276 277 :raises: ApiError if anything but expected response code is returned 278 """ 279 280 # Perform a GET request 281 response = self._request("GET", uri, params=params, **kwargs) 282 283 return response 284 285 def get_pages(self, uri, params=None, **kwargs): 286 """ 287 A method to build a GET request that yields pages of data returned by Lacework. 288 289 :param uri: uri to send the initial HTTP GET request to 290 :param params: dict of parameters for the HTTP request 291 :param kwargs: passed on to the requests package 292 293 :return: a generator that yields pages of data 294 295 :raises: ApiError if anything but expected response code is returned 296 """ 297 298 response = self.get(uri, params=params, **kwargs) 299 300 while True: 301 yield response 302 303 try: 304 response_json = response.json() 305 next_page = response_json.get("paging", {}).get("urls", {}).get("nextPage") 306 except json.JSONDecodeError: 307 logger.error("Failed to decode response from Lacework as JSON.", exc_info=True) 308 logger.debug(f"Response text: {response.text}") 309 next_page = None 310 311 if next_page: 312 response = self.get(next_page, params=params, **kwargs) 313 else: 314 break 315 316 def get_data_items(self, uri, params=None, **kwargs): 317 """ 318 A method to build a GET request that yields individual objects as returned by Lacework. 319 320 :param uri: uri to send the initial HTTP GET request to 321 :param params: dict of parameters for the HTTP request 322 :param kwargs: passed on to the requests package 323 324 :return: a generator that yields individual objects from pages of data 325 326 :raises: ApiError if anything but expected response code is returned 327 :raises: MalformedResponse if the returned response does not contain a 328 top-level dictionary with an "data" key. 329 """ 330 331 # Get generator for pages of JSON data 332 pages = self.get_pages(uri, params=params, **kwargs) 333 334 for page in pages: 335 page = page.json() 336 assert isinstance(page, dict) 337 338 items = page.get("data") 339 340 if items is None: 341 error_message = f"'data' key not found in JSON data:\n{page}" 342 raise MalformedResponse(error_message) 343 344 for item in items: 345 yield item 346 347 def patch(self, uri, data=None, json=None, **kwargs): 348 """ 349 A method to build a PATCH request to interact with Lacework. 350 351 :param uri: uri to send the HTTP POST request to 352 :param data: data to be sent in the body of the request 353 :param json: data to be sent in JSON format in the body of the request 354 :param kwargs: passed on to the requests package 355 356 :return: response json 357 358 :raises: ApiError if anything but expected response code is returned 359 """ 360 361 # Perform a PATCH request 362 response = self._request("PATCH", uri, data=data, json=json, **kwargs) 363 364 return response 365 366 def post(self, uri, data=None, json=None, **kwargs): 367 """ 368 A method to build a POST request to interact with Lacework. 369 370 :param uri: uri to send the HTTP POST request to 371 :param data: data to be sent in the body of the request 372 :param json: data to be sent in JSON format in the body of the request 373 :param kwargs: passed on to the requests package 374 375 :return: response json 376 377 :raises: ApiError if anything but expected response code is returned 378 """ 379 380 # Perform a POST request 381 response = self._request("POST", uri, data=data, json=json, **kwargs) 382 383 return response 384 385 def put(self, uri, data=None, json=None, **kwargs): 386 """ 387 A method to build a PUT request to interact with Lacework. 388 389 :param uri: uri to send the HTTP POST request to 390 :param data: data to be sent in the body of the request 391 :param json: data to be sent in JSON format in the body of the request 392 :param kwargs: passed on to the requests package 393 394 :return: response json 395 396 :raises: ApiError if anything but expected response code is returned 397 """ 398 399 # Perform a PUT request 400 response = self._request("PUT", uri, data=data, json=json, **kwargs) 401 402 return response 403 404 def delete(self, uri, data=None, json=None, **kwargs): 405 """ 406 A method to build a DELETE request to interact with Lacework. 407 408 :param uri: uri to send the http DELETE request to 409 :param data: data to be sent in the body of the request 410 :param json: data to be sent in JSON format in the body of the request 411 :param kwargs: passed on to the requests package 412 413 :response: reponse json 414 415 :raises: ApiError if anything but expected response code is returned 416 """ 417 418 # Perform a DELETE request 419 response = self._request("DELETE", uri, data=data, json=json, **kwargs) 420 421 return response
Package HttpSession class.
35 def __init__(self, account, subaccount, api_key, api_secret, base_domain): 36 """ 37 Initializes the HttpSession object. 38 39 :param account: a Lacework Account name 40 :param subaccount: a Lacework Sub-account name 41 :param api_key: a Lacework API Key 42 :param api_secret: a Lacework API Secret 43 :param base_domain: a Lacework Domain (defaults to "lacework.net") 44 45 :return HttpSession object. 46 """ 47 48 super().__init__() 49 50 # Create a requests session 51 self._session = self._retry_session() 52 53 # Set the base parameters 54 self._api_key = api_key 55 self._api_secret = api_secret 56 self._base_domain = base_domain or DEFAULT_BASE_DOMAIN 57 58 domain_string = f".{self._base_domain}" 59 if account.endswith(domain_string): 60 account = account[:-len(domain_string)] 61 62 self._base_url = f"https://{account}.{self._base_domain}" 63 self._subaccount = subaccount 64 self._org_level_access = False 65 66 # Get an access token 67 self._check_access_token()
Initializes the HttpSession object.
Parameters
- account: a Lacework Account name
- subaccount: a Lacework Sub-account name
- api_key: a Lacework API Key
- api_secret: a Lacework API Secret
- base_domain: a Lacework Domain (defaults to "lacework.net")
:return HttpSession object.
267 def get(self, uri, params=None, **kwargs): 268 """ 269 A method to build a GET request to interact with Lacework. 270 271 :param uri: uri to send the HTTP GET request to 272 :param params: dict of parameters for the HTTP request 273 :param kwargs: passed on to the requests package 274 275 :return: response json 276 277 :raises: ApiError if anything but expected response code is returned 278 """ 279 280 # Perform a GET request 281 response = self._request("GET", uri, params=params, **kwargs) 282 283 return response
A method to build a GET request to interact with Lacework.
Parameters
- uri: uri to send the HTTP GET request to
- params: dict of parameters for the HTTP request
- kwargs: passed on to the requests package
Returns
response json
Raises
- ApiError if anything but expected response code is returned
285 def get_pages(self, uri, params=None, **kwargs): 286 """ 287 A method to build a GET request that yields pages of data returned by Lacework. 288 289 :param uri: uri to send the initial HTTP GET request to 290 :param params: dict of parameters for the HTTP request 291 :param kwargs: passed on to the requests package 292 293 :return: a generator that yields pages of data 294 295 :raises: ApiError if anything but expected response code is returned 296 """ 297 298 response = self.get(uri, params=params, **kwargs) 299 300 while True: 301 yield response 302 303 try: 304 response_json = response.json() 305 next_page = response_json.get("paging", {}).get("urls", {}).get("nextPage") 306 except json.JSONDecodeError: 307 logger.error("Failed to decode response from Lacework as JSON.", exc_info=True) 308 logger.debug(f"Response text: {response.text}") 309 next_page = None 310 311 if next_page: 312 response = self.get(next_page, params=params, **kwargs) 313 else: 314 break
A method to build a GET request that yields pages of data returned by Lacework.
Parameters
- uri: uri to send the initial HTTP GET request to
- params: dict of parameters for the HTTP request
- kwargs: passed on to the requests package
Returns
a generator that yields pages of data
Raises
- ApiError if anything but expected response code is returned
316 def get_data_items(self, uri, params=None, **kwargs): 317 """ 318 A method to build a GET request that yields individual objects as returned by Lacework. 319 320 :param uri: uri to send the initial HTTP GET request to 321 :param params: dict of parameters for the HTTP request 322 :param kwargs: passed on to the requests package 323 324 :return: a generator that yields individual objects from pages of data 325 326 :raises: ApiError if anything but expected response code is returned 327 :raises: MalformedResponse if the returned response does not contain a 328 top-level dictionary with an "data" key. 329 """ 330 331 # Get generator for pages of JSON data 332 pages = self.get_pages(uri, params=params, **kwargs) 333 334 for page in pages: 335 page = page.json() 336 assert isinstance(page, dict) 337 338 items = page.get("data") 339 340 if items is None: 341 error_message = f"'data' key not found in JSON data:\n{page}" 342 raise MalformedResponse(error_message) 343 344 for item in items: 345 yield item
A method to build a GET request that yields individual objects as returned by Lacework.
Parameters
- uri: uri to send the initial HTTP GET request to
- params: dict of parameters for the HTTP request
- kwargs: passed on to the requests package
Returns
a generator that yields individual objects from pages of data
Raises
- ApiError if anything but expected response code is returned
- MalformedResponse if the returned response does not contain a top-level dictionary with an "data" key.
347 def patch(self, uri, data=None, json=None, **kwargs): 348 """ 349 A method to build a PATCH request to interact with Lacework. 350 351 :param uri: uri to send the HTTP POST request to 352 :param data: data to be sent in the body of the request 353 :param json: data to be sent in JSON format in the body of the request 354 :param kwargs: passed on to the requests package 355 356 :return: response json 357 358 :raises: ApiError if anything but expected response code is returned 359 """ 360 361 # Perform a PATCH request 362 response = self._request("PATCH", uri, data=data, json=json, **kwargs) 363 364 return response
A method to build a PATCH request to interact with Lacework.
Parameters
- uri: uri to send the HTTP POST request to
- data: data to be sent in the body of the request
- json: data to be sent in JSON format in the body of the request
- kwargs: passed on to the requests package
Returns
response json
Raises
- ApiError if anything but expected response code is returned
366 def post(self, uri, data=None, json=None, **kwargs): 367 """ 368 A method to build a POST request to interact with Lacework. 369 370 :param uri: uri to send the HTTP POST request to 371 :param data: data to be sent in the body of the request 372 :param json: data to be sent in JSON format in the body of the request 373 :param kwargs: passed on to the requests package 374 375 :return: response json 376 377 :raises: ApiError if anything but expected response code is returned 378 """ 379 380 # Perform a POST request 381 response = self._request("POST", uri, data=data, json=json, **kwargs) 382 383 return response
A method to build a POST request to interact with Lacework.
Parameters
- uri: uri to send the HTTP POST request to
- data: data to be sent in the body of the request
- json: data to be sent in JSON format in the body of the request
- kwargs: passed on to the requests package
Returns
response json
Raises
- ApiError if anything but expected response code is returned
385 def put(self, uri, data=None, json=None, **kwargs): 386 """ 387 A method to build a PUT request to interact with Lacework. 388 389 :param uri: uri to send the HTTP POST request to 390 :param data: data to be sent in the body of the request 391 :param json: data to be sent in JSON format in the body of the request 392 :param kwargs: passed on to the requests package 393 394 :return: response json 395 396 :raises: ApiError if anything but expected response code is returned 397 """ 398 399 # Perform a PUT request 400 response = self._request("PUT", uri, data=data, json=json, **kwargs) 401 402 return response
A method to build a PUT request to interact with Lacework.
Parameters
- uri: uri to send the HTTP POST request to
- data: data to be sent in the body of the request
- json: data to be sent in JSON format in the body of the request
- kwargs: passed on to the requests package
Returns
response json
Raises
- ApiError if anything but expected response code is returned
404 def delete(self, uri, data=None, json=None, **kwargs): 405 """ 406 A method to build a DELETE request to interact with Lacework. 407 408 :param uri: uri to send the http DELETE request to 409 :param data: data to be sent in the body of the request 410 :param json: data to be sent in JSON format in the body of the request 411 :param kwargs: passed on to the requests package 412 413 :response: reponse json 414 415 :raises: ApiError if anything but expected response code is returned 416 """ 417 418 # Perform a DELETE request 419 response = self._request("DELETE", uri, data=data, json=json, **kwargs) 420 421 return response
A method to build a DELETE request to interact with Lacework.
Parameters
- uri: uri to send the http DELETE request to
- data: data to be sent in the body of the request
- json: data to be sent in JSON format in the body of the request
- kwargs: passed on to the requests package
:response: reponse json
Raises
- ApiError if anything but expected response code is returned