def generate(self): spacebridge_server = config.get_spacebridge_domain() url = "{}/health_check".format(spacebridge_server) proxies = config.get_proxies() # Unset proxy, if unsetProxy = True if not self.useProxy: proxies = {} # Load data from REST API try: response = requests.get(url, proxies=proxies, timeout=15) response.raise_for_status() healthy = {'https_sync': True} except requests.exceptions.HTTPError as err: healthy = {'https_sync': False, 'message': err.message} except ProxyError as err: healthy = {'https_sync': False, 'message': err.message} except requests.ConnectionError as err: healthy = {'https_sync': False, 'message': err.message} yield healthy
def delete_device_from_spacebridge(device_id, system_authtoken): """ Deletes device from spacebridge :param device_id: :param system_authtoken: :return: response from spacebridge """ sodium_client = SodiumClient(LOGGER.getChild("sodium_client")) encryption_context = SplunkEncryptionContext( system_authtoken, constants.SPACEBRIDGE_APP_NAME, sodium_client) public_key_hash = encryption_context.sign_public_key( transform=encryption_context.generichash_hex) unregister_proto = http_pb2.DeviceUnregistrationRequest() unregister_proto.deviceId = b64decode(device_id) unregister_proto.deploymentId = encryption_context.sign_public_key( transform=encryption_context.generichash_raw) headers = { 'Authorization': public_key_hash, 'Content-Type': 'application/x-protobuf' } try: response = requests.delete("%s/api/session" % config.get_spacebridge_domain(), headers=headers, proxies=config.get_proxies(), data=unregister_proto.SerializeToString()) except Exception: LOGGER.exception( "Exception attempting sending delete device request to Spacebridge" ) raise Errors.SpacebridgeServerError('Unable to reach Spacebridge', 503) LOGGER.info( "Received response=%s on delete device from Spacebridge request" % response.status_code) spacebridge_response = http_pb2.DeviceUnregistrationResponse() spacebridge_response.ParseFromString(response.content) LOGGER.info('Spacebridge response: %s' % str(spacebridge_response)) if spacebridge_response.HasField( 'error' ) and spacebridge_response.error.code != http_pb2.HttpError.Code.Value( 'ERROR_ROUTING_UNDELIVERABLE'): raise Errors.SpacebridgeServerError( "Spacebridge error on delete device request=%s" % spacebridge_response.error.message) return response
def generate(self): url = self.url proxies = config.get_proxies() # Unset proxy, if unsetProxy = True if not self.useProxy: proxies = {} # Load data from REST API try: response = requests.get(url, proxies=proxies, timeout=15) response.raise_for_status() healthy = {'connected': True} except requests.exceptions.HTTPError as err: healthy = {'connected': False, 'message': err.message} except ProxyError as err: healthy = {'connected': False, 'message': err.message} yield healthy
def handle_confirmation(user, authtoken, system_authtoken, body): """ Handler for the NLP service registration call. This function: 1. Gets an auth code from the NLP server 2. Call the reqistration/query endpoint to get a temp key 3. Use that temp key to register (registration/confirmation) :param auth_code: User-entered authorization code to be returned to Spacebridge :param user: User running the query :param body: Parsed JSON body of the incoming POST request :param system_authtoken: System-level access token for writing to the kvstore :return: Success message """ if nlp_utils.is_nlp_enabled(system_authtoken, user): return { 'payload': 'NLP feature flag is not enabled', 'status': 500, } LOGGER.info( 'Received NLP service registration request by user={}'.format(user)) device_name = NLP_DEVICE_NAME_PREFIX + str(uuid.uuid4()) nlp_auth_url = get_nlp_auth_url(user, system_authtoken) deployment_info = nlp_utils.get_deployment_info(authtoken) deployment_id = deployment_info['deployment_id'] auth_code_url = '{}/registration/authcode/{}'.format( nlp_auth_url, deployment_id) LOGGER.info('authCode URL={}'.format(auth_code_url)) proxies = config.get_proxies() r = requests.get(auth_code_url, proxies=proxies, verify=True) if not r.status_code == requests.codes.ok: return { 'payload': 'Failed to get Auth Code', 'status': 500, } auth_code = r.content # enable NLP APP set_state_of_app(constants.NLP, authtoken, system_authtoken, True) delete_all_nlp_devices(authtoken, system_authtoken) params = {AUTH_CODE_LABEL: auth_code, DEVICE_NAME_LABEL: device_name} serverResponse, serverContent = rest.simpleRequest("registration/query", getargs=params, sessionKey=authtoken) reg_query_result = json.loads(serverContent) if not KVSTORE_TEMPORARY_ID_LABEL in reg_query_result: return { 'payload': 'Invalid registration data', 'status': 500, } temp_key = reg_query_result[KVSTORE_TEMPORARY_ID_LABEL] LOGGER.info( 'NLP service registration confirmed, device_name=\"{}\" temp_key=\"{}\"' .format(device_name, temp_key)) return register_device(auth_code, user, system_authtoken, temp_key)
def authentication_query_request(auth_code, encryption_context): """ Abstraction layer for the spacebridge request. This function: 1. Makes the registration query GET request to the spacebridge endpoint 2. Parses the protobuf response 3. Packs the response values into a response object. Binary objects are encoded to ensure kvstore compatibility :param auth_code: Authorization code of the device being registered :return: response object containing "public_key", "device_id", and "conf_code" """ # Makes the registration query GET request to the spacebridge endpoint try: headers = { 'Authorization': encryption_context.sign_public_key( transform=encryption_context.generichash_hex) } response = requests.get('%s/api/registrations/%s' % (config.get_spacebridge_domain(), auth_code), headers=headers, proxies=config.get_proxies()) except Exception: LOGGER.exception("Exception contacting spacebridge") raise Errors.SpacebridgeServerError('Unable to reach Spacebridge', 503) # Parses the protobuf response spacebridge_response = http_pb2.AuthenticationQueryResponse() spacebridge_response.ParseFromString(response.content) if spacebridge_response.HasField('error'): if response.status_code == 500: raise Errors.SpacebridgeServerError( 'Spacebridge encountered an internal error: %s' % spacebridge_response.error.message, 500) raise Errors.SpacebridgeServerError( 'Spacebridge request error: %s' % spacebridge_response.error.message, response.status_code) if not str(response.status_code).startswith('2'): raise Errors.SpacebridgeServerError( "Spacebridge error: %s" % str(response.content), response.status_code) # Packs the response values into a response object. Binary objects are encoded to ensure kvstore compatibility encrypt_public_key = spacebridge_response.payload.publicKeyForEncryption sign_public_key = spacebridge_response.payload.publicKeyForSigning response = { 'encrypt_public_key': py23.b64encode_to_str(encrypt_public_key), 'sign_public_key': py23.b64encode_to_str(sign_public_key), 'device_id': py23.b64encode_to_str(spacebridge_response.payload.deviceId), 'conf_code': encryption_context.generichash_hex(sign_public_key).upper()[:8], } try: response[APP_TYPE_LABEL] = translate_app_name( http_pb2.AppType.Name(spacebridge_response.payload.appType)) except ValueError as err: # When app_type is 'APPTYPE_INVALID' raise Errors.SpacebridgeRestError('Registration Error: %s' % str(err), 501) return response
def send_mdm_signing_key_to_spacebridge(authtoken, mdm_public_signing_key): """ Send the mdm public signing key to spacebridge Abstraction layer for the spacebridge request. This function: 1. Creates the mdm_credentials_bundle 2. Serializes the bundle to bytes 3. Signs the serialized bundle with the splapps private signing key 4. Creates a proto object with the serialized bundle + signature and sends to spacebridge 5. Parses the protobuf response, checking for error objects """ sodium_client = SodiumClient(LOGGER.getChild("sodium_client")) encryption_context = SplunkEncryptionContext( authtoken, constants.SPACEBRIDGE_APP_NAME, sodium_client) sign_func = partial(sign_detached, sodium_client, encryption_context.sign_private_key()) client_id = encryption_context.sign_public_key( transform=encryption_context.generichash_raw) request_proto = http_pb2.MdmAuthenticationGrantRequest() client_mdm_permission = request_proto.ClientMdmPermission() client_mdm_permission.clientId = client_id client_mdm_permission.mdmPublicKeyForSigning = mdm_public_signing_key serialized = client_mdm_permission.SerializeToString() signature = sign_func(serialized) request_proto.clientMdmPermission = serialized request_proto.signature = signature headers = { 'Authorization': encryption_context.sign_public_key( transform=encryption_context.generichash_hex), 'Content-Type': 'application/x-protobuf' } try: response = requests.post('{}/api/mdm/grants'.format( config.get_spacebridge_domain()), headers=headers, data=request_proto.SerializeToString(), proxies=config.get_proxies(), timeout=constants.TIMEOUT_SECONDS) except requests.exceptions.Timeout as e: raise Errors.SpacebridgeServerError( 'Failed to receive a response from spacebridge', 500) sb_response_proto = http_pb2.MdmAuthenticationGrantResponse() sb_response_proto.ParseFromString(response.content) retries = 0 while 500 <= response.status_code < 600 and retries < 3: wait = 2**retries retries = retries + 1 time.sleep(wait) try: response = requests.post('{}/api/mdm/grants'.format( config.get_spacebridge_domain()), headers=headers, data=request_proto.SerializeToString(), proxies=config.get_proxies(), timeout=constants.TIMEOUT_SECONDS) except requests.exceptions.Timeout as e: raise Errors.SpacebridgeServerError( 'Failed to receive a response from spacebridge', 500) sb_response_proto = http_pb2.MdmAuthenticationGrantResponse() sb_response_proto.ParseFromString(response.content) if sb_response_proto.HasField('error'): if response.status_code == 500: raise Errors.SpacebridgeServerError( 'Spacebridge encountered an internal error:{}'.format( sb_response_proto.error.message), 500) raise Errors.SpacebridgeServerError( 'Spacebridge request error: {}'.format( sb_response_proto.error.message, response.status_code)) if not (200 <= response.status_code < 300): raise Errors.SpacebridgeServerError( "Spacebridge error: {}".format(response.content), response.status_code)
def device_pairing_confirmation_request(auth_header, auth_code, username, device_id, encrypt_public_key, sign_public_key, session_token_encrypted, encrypt, deployment_friendly_name): """ Abstraction layer for the spacebridge request. This function: 1. Creates the encrypted_credentials_bundle 2. Generates a protobuf object from the supplied dictionary, proto_object 3. Makes the registration confirmation PUT request to the spacebridge endpoint 4. Parses the protobuf response, checking for error objects :param auth_code: Authorization code of the device being registered :param proto_object: Dict containing protobuf values :param username: User username for the encrypted_credentials_bundle :param password: User password for the encrypted_credentials_bundle :return: None """ # Creates the encrypted_credentials_bundle credentials_bundle_proto = http_pb2.CredentialsBundle() credentials_bundle_proto.sessionToken = session_token_encrypted credentials_bundle_proto.userName = username credentials_bundle_proto.deploymentName = deployment_friendly_name credentials_bundle = credentials_bundle_proto.SerializeToString() encrypted_credentials_bundle = encrypt(credentials_bundle) # Generates a protobuf object from the supplied dictionary, proto_object sb_request_proto = http_pb2.DevicePairingConfirmationRequest() sb_request_proto.authenticationCode = auth_code sb_request_proto.deviceId = device_id sb_request_proto.deploymentPublicKeyForEncryption = encrypt_public_key sb_request_proto.deploymentPublicKeyForSigning = sign_public_key sb_request_proto.encryptedCredentialsBundle = encrypted_credentials_bundle LOGGER.info("Registration Bundle deploymentPublicKey= %s" % str(sb_request_proto.deploymentPublicKey)) LOGGER.info("Registration Bundle deviceId= %s" % str(sb_request_proto.deviceId)) # Makes the registration confirmation PUT request to the spacebridge endpoint try: response = requests.put('%s/api/registrations/%s' % (config.get_spacebridge_domain(), auth_code), headers={ 'Content-Type': 'application/x-protobuf', 'Authorization': str(auth_header) }, data=sb_request_proto.SerializeToString(), proxies=config.get_proxies()) except Exception: raise Errors.SpacebridgeServerError('Unable to reach Spacebridge', 503) # Parses the protobuf response, checking for error objects sb_response_proto = http_pb2.DevicePairingConfirmationResponse() sb_response_proto.ParseFromString(response.content) if sb_response_proto.HasField('error'): if response.status_code == 500: raise Errors.SpacebridgeServerError( 'Spacebridge encountered an internal error: %s' % sb_response_proto.error.message, 500) raise Errors.SpacebridgeServerError( 'Spacebridge request error: %s' % sb_response_proto.error.message, response.status_code) if not (200 <= response.status_code < 300): raise Errors.SpacebridgeServerError( "Spacebridge error: %s" % str(response.content), response.status_code)
def forward_request(self, request): method = request['method'] path = request['path_info'] params = request['query'] request_headers = request['headers'] data = None if 'payload' in request: data = request['payload'] user = request['session']['user'] user_token = request['session']['authtoken'] system_authtoken = request['system_authtoken'] resp_status = () resp_body = {} url = None try: resp_status = (None, None) url = build_url(self, user, user_token, path) headers = get_headers(self, user, user_token, system_authtoken, request_headers) proxies = config.get_proxies() LOGGER.info("Request method={} url={} data={} params={}".format( method, url, data, params)) if data is not None: data = data.encode('utf-8') response = requests.request(method=method, url=url, data=data, params=params, headers=headers, proxies=proxies, timeout=self.timeout, verify=True) resp_status = (response.status_code, response.content) resp_body = json.loads(response.content) LOGGER.info("Response method={} url={} response_status={}".format( method, url, data, resp_status)) if not is_valid_response(self, user, user_token, response.headers): LOGGER.info( "Response sign verification failed for method={} url={} response_status={}" .format(method, url, data, resp_status)) return { 'payload': { "status": "signature verification failed" }, 'status': 401, } else: LOGGER.info( "Response sign verification success for method={} url={} response_status={}" .format(method, url, data, resp_status)) return { 'payload': resp_body, 'status': resp_status[0], } except Exception as e: resp_body = { 'url': url, 'code': resp_status[0], 'message': resp_status[1], 'detail': str(e) } LOGGER.error( "FORWARDING REQUEST EXCEPTION {} for response status {} resp_body {}" .format(e, resp_status[0], resp_body)) return { 'payload': resp_body, 'status': resp_status[0], }