Esempio n. 1
0
def generate_keys():
    """
    Generates the public and private keys necessary for encryption and signing.
    """
    sodium_client = SodiumClient()

    [sign_pk, sign_sk] = sodium_client.sign_generate_keypair()
    [box_pk, box_sk] = sodium_client.box_generate_keypair()

    return EncryptionKeys(sign_pk, sign_sk, box_pk, box_sk)
    def get(self, request):
        auth_token = request['system_authtoken']
        friendly_name = get_deployment_friendly_name(auth_token)
        encryption_context = SplunkEncryptionContext(
            auth_token, constants.SPACEBRIDGE_APP_NAME, SodiumClient(LOGGER))
        mdm_sign_public_key = get_mdm_public_signing_key(auth_token)
        mdm_keypair_generation_time = get_mdm_update_timestamp(
            request, auth_token)

        return {
            'payload': {
                constants.DEPLOYMENT_FRIENDLY_NAME:
                friendly_name,
                constants.SIGN_PUBLIC_KEY:
                py23.b64encode_to_str(encryption_context.sign_public_key()),
                constants.DEPLOYMENT_ID:
                encryption_context.sign_public_key(
                    transform=encryption_context.generichash_hex),
                constants.ENCRYPT_PUBLIC_KEY:
                py23.b64encode_to_str(encryption_context.encrypt_public_key()),
                constants.SERVER_VERSION:
                str(app_version()),
                constants.MDM_SIGN_PUBLIC_KEY:
                mdm_sign_public_key,
                constants.MDM_KEYPAIR_GENERATION_TIME:
                mdm_keypair_generation_time
            },
            'status': 200
        }
Esempio n. 3
0
    def __init__(self, encryption_keys, sodium_client=None):
        """
        Args:
            encryption_keys ([EncryptionKeys]): User must generate encryption keys using the
            generate_keys method and provide keys in the context. It's up the user to 
            persist the keys themselves between sessions. 
            sodium_client ([type], optional): [description]. Defaults to None.
        """

        self.mode = SdkMode.STANDALONE
        if sodium_client:
            self.sodium_client = sodium_client
        else:
            self.sodium_client = SodiumClient()

        self._key_cache = encryption_keys.__dict__
Esempio n. 4
0
    def __init__(self, session_key, app_name, sodium_client=None):
        """
        Pass a session token and create a KV Store Handler to be able to write and fetch public keys from KV Store.
        The session token itself is not exposed, only the handler.
        """

        self.mode = SdkMode.SPLUNK
        if sodium_client:
            self.sodium_client = sodium_client
        else:
            self.sodium_client = SodiumClient()

        self._key_cache = {}
        self.session_key = session_key
        self.app_name = app_name

        self.generate_keys()
Esempio n. 5
0
def delete_device_from_spacebridge(device_id,
                                   system_authtoken,
                                   key_bundle=None):
    """
    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'
    }

    with requests_ssl_context(key_bundle):
        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 process_subscriptions(request_context,
                          async_client_factory,
                          encryption_context=None,
                          tv_device_ids=None,
                          tv_subscriptions=None,
                          ipad_subscriptions=None):
    """
    Function to send subscription updates to the appropriate tv
    and all ipads registered to the user
    :param tv_config: tv config to send
    :param request_context: request context used to make kvstore request
    :param async_client_factory: used to create spacebridge and kvstore client
    """
    async_kvstore_client = async_client_factory.kvstore_client()
    if not tv_subscriptions:
        tv_subscriptions = yield fetch_subscriptions(
            request_context.auth_header,
            async_kvstore_client,
            user_list=[request_context.current_user],
            subscription_type=constants.DRONE_MODE_TV,
            device_ids=tv_device_ids)

    LOGGER.debug(
        'Active subscriptions%s: %s',
        ' for tv device_ids={}'.format(tv_device_ids) if tv_device_ids else '',
        tv_subscriptions)
    if tv_subscriptions:
        # create encryption context if not passed in
        if not encryption_context:
            sodium_client = SodiumClient()
            encryption_context = SplunkEncryptionContext(
                request_context.system_auth_header.session_token,
                constants.SPACEBRIDGE_APP_NAME, sodium_client)

        tv_subscription_tuples = yield build_tv_subscription_updates(
            tv_subscriptions, request_context,
            async_client_factory.kvstore_client())
        yield send_updates(tv_subscription_tuples, encryption_context,
                           async_client_factory, request_context,
                           constants.DRONE_MODE_TV)

    # Fetch active iPad subscriptions
    if not ipad_subscriptions:
        ipad_subscriptions = yield fetch_subscriptions(
            request_context.auth_header,
            async_kvstore_client,
            user_list=[request_context.current_user],
            subscription_type=constants.DRONE_MODE_IPAD)
    if ipad_subscriptions:
        ipad_subscription_tuples = yield build_ipad_subscription_updates(
            ipad_subscriptions, request_context,
            async_client_factory.kvstore_client())
        yield send_updates(ipad_subscription_tuples, encryption_context,
                           async_client_factory, request_context,
                           constants.DRONE_MODE_IPAD)
Esempio n. 7
0
    def do_run(self, input_config):
        """ Spins up a websocket connection Spacebridge and begins
        the reactor loops
        """
        shard_id = default_shard_id()

        self.logger.info("Starting libsodium child process")
        sodium_logger = self.logger.getChild('sodium_client')
        sodium_logger.setLevel(logging.WARN)

        sodium_client = SodiumClient(sodium_logger)
        encryption_context = SplunkEncryptionContext(
            self.session_key, constants.SPACEBRIDGE_APP_NAME, sodium_client)

        self.logger.info(
            "Running Splunk Cloud Gateway modular input on search head, shard_id=%s",
            shard_id)

        # Fetch load balancer address if configured, otherwise use default URI
        try:
            uri = get_uri(self.session_key)
            self.logger.debug(
                "Successfully verified load_balancer_address={}".format(uri))
        except Exception as e:
            self.logger.exception(
                "Failed to verify load_balancer_address. {}".format(e))

        if not uri:
            return

        try:
            ensure_deployment_friendly_name(self.session_key)
            async_client_factory = AsyncClientFactory(uri)
            cloudgateway_message_handler = CloudgatewayMessageHandler(
                SplunkAuthHeader(self.session_key),
                logger=self.logger,
                encryption_context=encryption_context,
                async_client_factory=async_client_factory,
                shard_id=shard_id)

            client = CloudGatewayWsClient(
                encryption_context,
                message_handler=cloudgateway_message_handler,
                mode=WebsocketMode.ASYNC,
                logger=self.logger,
                config=config,
                shard_id=shard_id)

            client.connect()
        except Exception as e:
            self.logger.exception(
                "Exception connecting to cloud gateway={0}".format(e))
def process_mpc_broadcast_request(request_context,
                                  client_single_request,
                                  single_server_response,
                                  async_client_factory):
    """
    This Method will process a MPC Broadcast Request

    :param request_context: Used to authenticate kvstore requests
    :param client_single_request: client request object protobuf
    :param single_server_response: server response object protobuf
    :param async_client factory: factory class used to generate kvstore and spacebridge clients
    """

    async_kvstore_client = async_client_factory.kvstore_client()
    device_id = client_single_request.startMPCBroadcastRequest.deviceId

    subscriptions = yield fetch_subscriptions(request_context.auth_header,
                                              async_kvstore_client,
                                              user_list=[request_context.current_user],
                                              subscription_type=constants.DRONE_MODE_TV,
                                              device_ids=[device_id])
    if not subscriptions:
        raise SpacebridgeApiRequestError('No active subscriptions for device_id={}'
                                         .format(device_id),
                                         status_code=http.BAD_REQUEST)

    async_spacebridge_client = async_client_factory.spacebridge_client()
    active_subscription = subscriptions[0]
    sodium_client = SodiumClient()
    encryption_context = SplunkEncryptionContext(request_context.system_auth_header.session_token,
                                                 constants.SPACEBRIDGE_APP_NAME,
                                                 sodium_client)
    start_broadcast = StartMPCBroadcast(device_id=device_id)
    subscription_update = DroneModeTVEvent(data_object=start_broadcast,
                                           event_type=TVEventType.MPC_BROADCAST)
    response, subscription_key = yield send_drone_mode_subscription_update(request_context.system_auth_header,
                                                                           active_subscription,
                                                                           subscription_update,
                                                                           encryption_context,
                                                                           async_spacebridge_client,
                                                                           async_kvstore_client)
    yield check_and_raise_error(response, request_context, 'MPC Broadcast Request')
    single_server_response.startMPCBroadcastResponse.SetInParent()

    LOGGER.info('Successfully sent mpc broadcast message to device_id=%s with subscription_key=%s',
                device_id,
                subscription_key)
Esempio n. 9
0
    def do_run(self, input_config):
        """
        This will spin up a drone mode subscription manager and begins the reactor loop
        :param input_config:
        :return:
        """

        try:
            sodium_client = SodiumClient(self.logger.getChild('sodium_client'))
            encryption_context = SplunkEncryptionContext(
                self.session_key, SPACEBRIDGE_APP_NAME, sodium_client)

            self.logger.debug(
                "Running Drone Mode Subscription Manager modular input")

            # Fetch load balancer address if configured, otherwise use default URI
            try:
                uri = get_uri(self.session_key)
                self.logger.debug(
                    "Successfully verified load_balancer_address=%s", uri)
            except Exception:
                self.logger.exception(
                    "Failed to verify load_balancer_address.")

            if not uri:
                return

            subscription_manager = DroneModeSubscriptionManager(
                input_config=input_config,
                encryption_context=encryption_context,
                session_key=self.session_key,
                async_splunk_client=AsyncSplunkClient(uri),
                parent_process_monitor=ParentProcessMonitor(),
                cluster_monitor=ClusterMonitor(
                    self.logger,
                    interval=config.get_cluster_monitor_interval()))
            subscription_manager.run()

        except Exception as e:
            self.logger.exception(
                'An error occurred running the drone mode subscription modular input'
            )
            raise e
Esempio n. 10
0
class EncryptionContext(object):
    """Base class for encryption which provides utilities such as  
    getters for the public and private keys for encryption and signing. Does not offer
    any out of the box mechanism for persisting keys. Sub classes such as
    SplunkEncryptionContext can provide specific implementations for persistence
    depending on the persistence mechanism. 
    """
    def __init__(self, encryption_keys, sodium_client=None):
        """
        Args:
            encryption_keys ([EncryptionKeys]): User must generate encryption keys using the
            generate_keys method and provide keys in the context. It's up the user to 
            persist the keys themselves between sessions. 
            sodium_client ([type], optional): [description]. Defaults to None.
        """

        self.mode = SdkMode.STANDALONE
        if sodium_client:
            self.sodium_client = sodium_client
        else:
            self.sodium_client = SodiumClient()

        self._key_cache = encryption_keys.__dict__

    def get_encryption_keys(self):
        """Getter for encryption keys

        Returns:
            [EncryptionKeys]: Wrapper object. Also the same object that the constructor expects. 
            The user should persist this object and load it into the constructor on future sessions. 
        """
        return EncryptionKeys(self._key_cache[SIGN_PUBLIC_KEY],
                              self._key_cache[SIGN_PRIVATE_KEY],
                              self._key_cache[ENCRYPT_PUBLIC_KEY],
                              self._key_cache[ENCRYPT_PRIVATE_KEY])

    def __noop(input):
        return input

    def generichash_hex(self, input):
        raw = self.generichash_raw(input)
        if sys.version_info < (3, 0):
            return raw.encode('hex')
        else:
            return raw.hex()

    def generichash_raw(self, input):
        return self.sodium_client.hash_generic(input)

    def sign_public_key(self, transform=__noop):
        """
        :param transform: a function to transform the public key into another representation, default noop
        :return: Signing public key from KV Store. Requires key to have been generated.  This is the splApp's
        :raises splunk.RESTException: The key cache hasn't been initialized
        "identity" key.
        """

        key = self._key_cache[SIGN_PUBLIC_KEY]
        return transform(key)

    def sign_private_key(self):
        """
        Fetch signing private key from KV Store. Requires key to have been generated
        """

        key = self._key_cache[SIGN_PRIVATE_KEY]
        return key

    def encrypt_public_key(self):
        """
        Fetch encryption public key from KV Store. Requires key to have been generated
        """

        key = self._key_cache[ENCRYPT_PUBLIC_KEY]
        return key

    def encrypt_private_key(self):
        """
        Fetch encryption public key from KV Store. Requires key to have been generated
        """

        key = self._key_cache[ENCRYPT_PRIVATE_KEY]
        return key

    def secure_session_token(self, session_token):
        """
        :param session_token: string representing session token
        :return: base64 encoded encrypted session token
        """
        public_key = self.encrypt_public_key()
        private_key = self.encrypt_private_key()

        ciphertext = encrypt_session_token(self.sodium_client, session_token,
                                           public_key, private_key)
        return base64.b64encode(ciphertext)
Esempio n. 11
0
def send_mdm_signing_key_to_spacebridge(authtoken,
                                        mdm_public_signing_key,
                                        key_bundle=None):
    """ 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'
    }

    def call_grants():
        with requests_ssl_context(key_bundle) as cert:
            return requests.post('{}/api/mdm/grants'.format(
                config.get_spacebridge_domain()),
                                 headers=headers,
                                 data=request_proto.SerializeToString(),
                                 proxies=config.get_proxies(),
                                 timeout=constants.TIMEOUT_SECONDS,
                                 cert=cert.name)

    try:
        response = call_grants()
    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 = call_grants()
        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)
Esempio n. 12
0
class SplunkEncryptionContext(EncryptionContext):
    """
    Context object for handling generating and fetching public and private keys. (Currently only public key is
    supported)
    """

    def __init__(self, session_key, app_name, sodium_client=None):
        """
        Pass a session token and create a KV Store Handler to be able to write and fetch public keys from KV Store.
        The session token itself is not exposed, only the handler.
        """

        self.mode = SdkMode.SPLUNK
        if sodium_client:
            self.sodium_client = sodium_client
        else:
            self.sodium_client = SodiumClient()

        self._key_cache = {}
        self.session_key = session_key
        self.app_name = app_name

        self.generate_keys()

    def set_encryption_keys(self, keys_dict):
        self._key_cache = keys_dict

    def _cache_keys(self):
        result = False
        raw_data = splunk_client.fetch_sensitive_data(self.session_key, ENCRYPTION_KEYS, self.app_name)
        try:
            self._key_cache = EncryptionKeys.from_json(json.loads(raw_data)).__dict__
        except Exception:
            self._key_cache = {}

        if SIGN_PUBLIC_KEY in self._key_cache:
            result = True

        return result

    def _create_key_bucket(self):
        try:
            splunk_client.create_sensitive_data(self.session_key, ENCRYPTION_KEYS, '{}', self.app_name)
        except RESTException as e:
            if e.statusCode != 409:
                raise e

    def generate_keys(self):
        """
        Stores the required signing and encryption keys to KV Store in the meta collection. If the
        public key already exists, then this is a no op.

        There is a synchronization issue where when you call generate keys, KV Store might not yet have been
        initialized. The way we handle this is if we get a 503 HTTP error back from KV Store, this means the store
        is not yet available, in which case we callback generate keys to run in 5 seconds.
        """

        self._create_key_bucket()

        keys_cached = self._cache_keys()

        if not keys_cached:
            [sign_pk, sign_sk] = [k for k in self.sodium_client.sign_generate_keypair()]
            [box_pk, box_sk] = [k for k in self.sodium_client.box_generate_keypair()]

            encryption_keys = EncryptionKeys(sign_pk, sign_sk, box_pk, box_sk)

            key_data = json.dumps(encryption_keys.to_json())
            splunk_client.update_sensitive_data(self.session_key, ENCRYPTION_KEYS, key_data, self.app_name)
            self._key_cache = encryption_keys.__dict__
Esempio n. 13
0
def handle_query(auth_code, device_name, user, system_authtoken):
    """
    Handler for the initial AuthenticationQueryRequest call. This function:
        1. Makes the AuthenticationQueryRequest request to the server
        2. Checks if app_type has been disabled
        3. Stores a temporary record in the kvstore

    :param auth_code: User-entered authorization code to be returned to Spacebridge
    :param device_name: Name of the new device
    :return: Confirmation code to be displayed to user, and id of temporary kvstore record to be returned later
    """

    LOGGER.info('Received new registration query request by user=%s' % user)

    # Makes the AuthenticationQueryRequest request to the server
    sodium_client = SodiumClient(LOGGER.getChild('sodium_client'))
    encryption_context = SplunkEncryptionContext(system_authtoken,
                                                 SPACEBRIDGE_APP_NAME,
                                                 sodium_client)
    client_device_info = authenticate_code(auth_code,
                                           encryption_context,
                                           resolve_app_name,
                                           config=config)
    app_name = client_device_info.app_name
    app_id = client_device_info.app_id

    platform = client_device_info.platform

    # if platform not set and we know platform based on app id, use that.
    if not platform and app_id in APP_ID_TO_PLATFORM_MAP:
        platform = get_app_platform(app_id)

    LOGGER.info("client_device_info={}".format(client_device_info))

    user_devices = get_devices_for_user(user, system_authtoken)
    LOGGER.info("user_devices=%s" % user_devices)

    if any(device[DEVICE_NAME_LABEL] == device_name
           and device['device_type'] == app_name for device in user_devices):
        err_msg = (
            'Registration Error: user={} device_name={} of app_type={} already exists'
            .format(user, device_name, app_name))
        LOGGER.info(err_msg)
        raise Errors.SpacebridgeRestError(err_msg, HTTPStatus.CONFLICT)

    # Stores a temporary record in the kvstore
    kvstore_unconfirmed = KvStore(UNCONFIRMED_DEVICES_COLLECTION_NAME,
                                  system_authtoken,
                                  owner=user)
    kvstore_payload = client_device_info.to_json()
    kvstore_payload['device_name'] = device_name
    kvstore_payload['device_type'] = app_name
    kvstore_payload['app_name'] = app_name
    kvstore_payload['app_id'] = app_id
    kvstore_payload['platform'] = platform
    _, content = kvstore_unconfirmed.insert_single_item(kvstore_payload)

    return {
        'payload': {
            'temp_key': json.loads(content)['_key'],
            'conf_code': client_device_info.confirmation_code
        },
        'status': HTTPStatus.OK,
    }
    def do_run(self, input_config):
        shard_id = default_shard_id()

        self.logger.info("Starting libsodium child process")
        sodium_logger = self.logger.getChild('sodium_client')
        sodium_logger.setLevel(logging.WARN)
        sodium_client = SodiumClient(sodium_logger)
        self.logger.info("Loading encryption context")
        encryption_context = SplunkEncryptionContext(self.session_key,
                                                     SPACEBRIDGE_APP_NAME,
                                                     sodium_client)

        self.logger.info(
            "Running Subscription Manager modular input on search head")

        # Fetch load balancer address if configured, otherwise use default URI
        try:
            uri = get_uri(self.session_key)
            self.logger.debug(
                "Successfully verified load_balancer_address={}".format(uri))
        except Exception as e:
            self.logger.exception(
                "Failed to verify load_balancer_address. {}".format(e))

        if not uri:
            return

        try:
            minimum_iteration_time_seconds = float(input_config[
                self.input_config_key][self.minimum_iteration_time_seconds])
            warn_threshold_seconds = float(input_config[self.input_config_key][
                self.warn_threshold_seconds])
            subscription_processor_parallelism_str = input_config[
                self.input_config_key][self.subscription_processor_parallelism]
            subscription_parallelism = self._resolve_parallelism(
                subscription_processor_parallelism_str)
        except:
            self.logger.exception(
                "Failed to load required configuration values")
            return

        try:
            self.logger.info("Processing subscriptions with parallelism=%s",
                             subscription_parallelism)
            auth_header = SplunkAuthHeader(self.session_key)

            self.logger.debug(
                "Using search processor python=%s, script=%s, args=%s",
                self.python_path, self.script_path, self.subprocess_args)
            start_job = start_job_using_subprocess(self.python_path,
                                                   self.subprocess_args)

            if subscription_parallelism == self.CONFIG_VALUE_SINGLE_PROCESS:
                start_job = start_job_single_process(sodium_client,
                                                     encryption_context)

            process_manager = ProcessManager(subscription_parallelism,
                                             start_job)
            job_context = JobContext(
                auth_header, uri,
                encryption_context.get_encryption_keys().to_json())

            subscription_manager = SubscriptionManager(
                input_config=input_config,
                encryption_context=encryption_context,
                auth_header=auth_header,
                shard_id=shard_id,
                job_context=job_context,
                search_loader=loader.load_search_bundle,
                parent_process_monitor=ParentProcessMonitor(),
                minimum_iteration_time_seconds=minimum_iteration_time_seconds,
                warn_threshold_seconds=warn_threshold_seconds,
                process_manager=process_manager)

            subscription_manager.run()
        except:
            self.logger.exception(
                "Unhandled exception during subscription processing")
def process_tv_interaction_request(request_context,
                                   client_single_request,
                                   single_server_response,
                                   async_client_factory):
    """
    This Method will process a MPC Broadcast Request

    :param request_context: Used to authenticate kvstore requests
    :param client_single_request: client request object protobuf
    :param single_server_response: server response object protobuf
    :param async_client factory: factory class used to generate kvstore and spacebridge clients
    """

    async_kvstore_client = async_client_factory.kvstore_client()
    device_id = client_single_request.tvInteractionRequest.tvInteraction.device_id
    tv_interaction_proto = client_single_request.tvInteractionRequest.tvInteraction
    # determine type of interaction
    interaction_type = TVInteractionType.NONE
    speed = None
    if tv_interaction_proto.HasField(constants.SLIDESHOW_GOTO):
        interaction_type = TVInteractionType.GOTO

    elif tv_interaction_proto.HasField(constants.SLIDESHOW_STOP):
        interaction_type = TVInteractionType.STOP

    elif tv_interaction_proto.HasField(constants.SLIDESHOW_FORWARD):
        interaction_type = TVInteractionType.FORWARD

    elif tv_interaction_proto.HasField(constants.SLIDESHOW_BACK):
        interaction_type = TVInteractionType.BACK

    elif tv_interaction_proto.HasField(constants.SLIDESHOW_SPEED):
        interaction_type = TVInteractionType.SPEED
        speed = tv_interaction_proto.slideshow_speed.speed


    subscriptions = yield fetch_subscriptions(request_context.auth_header,
                                              async_kvstore_client,
                                              user_list=[request_context.current_user],
                                              subscription_type=constants.DRONE_MODE_TV,
                                              device_ids=[device_id])


    if not subscriptions:
        raise SpacebridgeApiRequestError('No active subscriptions for device_id={}'
                                         .format(device_id),
                                         status_code=http.BAD_REQUEST)

    async_spacebridge_client = async_client_factory.spacebridge_client()
    active_subscription = subscriptions[0]
    sodium_client = SodiumClient()
    encryption_context = SplunkEncryptionContext(request_context.system_auth_header.session_token,
                                                 constants.SPACEBRIDGE_APP_NAME,
                                                 sodium_client)
    tv_interaction = TVInteraction(device_id=device_id,
                                   interaction_type=interaction_type,
                                   speed=speed)
    subscription_update = DroneModeTVEvent(data_object=tv_interaction,
                                           event_type=TVEventType.TV_INTERACTION)
    response, subscription_key = yield send_drone_mode_subscription_update(request_context.system_auth_header,
                                                                           active_subscription,
                                                                           subscription_update,
                                                                           encryption_context,
                                                                           async_spacebridge_client,
                                                                           async_kvstore_client)
    yield check_and_raise_error(response, request_context, 'MPC Broadcast Request')
    single_server_response.tvInteractionResponse.SetInParent()

    LOGGER.info('Successfully sent mpc broadcast message to device_id=%s with subscription_key=%s',
                device_id,
                subscription_key)
Esempio n. 16
0
 def __init__(self, command_line, command_arg):
     BaseRestHandler.__init__(self)
     self.sodium_client = SodiumClient(LOGGER.getChild('sodium_client'))
class DeploymentBundle(BaseRestHandler, PersistentServerConnectionApplication):
    """
    Main class for handling the deployment bundle endpoint. Subclasses the spacebridge_app
    BaseRestHandler.

    """
    def __init__(self, command_line, command_arg):
        BaseRestHandler.__init__(self)
        self.sodium_client = SodiumClient()

    def get(self, request):
        """
        Handler which returns mdm signing public key
        """

        response = {}
        try:
            kvstore_service = kvstore(collection=USER_META_COLLECTION_NAME,
                                      session_key=request[SESSION][AUTHTOKEN],
                                      owner=request[SESSION][USER])
            result = json.loads(
                kvstore_service.get_item_by_key(MDM_KEYPAIR_GENERATION_TIME)
                [1])
            response.update({TIMESTAMP: result[TIMESTAMP]})

        except Exception as e:
            # If key not in kvstore
            if hasattr(e,
                       'statusCode') and e.statusCode == HTTPStatus.NOT_FOUND:
                return {
                    'payload': {
                        'message':
                        'Could not find mdm keypair update time in kvstore',
                        'status': HTTPStatus.NOT_FOUND
                    }
                }
            return {
                'payload': {
                    'message': str(e),
                    'status': HTTPStatus.BAD_REQUEST
                }
            }

        try:
            public_key = fetch_sensitive_data(request[SESSION][AUTHTOKEN],
                                              MDM_SIGN_PUBLIC_KEY)
            private_key = fetch_sensitive_data(request[SESSION][AUTHTOKEN],
                                               MDM_SIGN_PRIVATE_KEY)
            response.update({
                'sign_public_key': public_key,
                'sign_private_key': private_key
            })
        except Exception as e:
            # If key not in storage/passwords
            if hasattr(e,
                       'statusCode') and e.statusCode == HTTPStatus.NOT_FOUND:
                return {
                    'payload': {
                        'message':
                        'Could not find one or both of key={} and key={} in /storage/passwords'
                        .format(MDM_SIGN_PUBLIC_KEY, MDM_SIGN_PRIVATE_KEY),
                        'status':
                        HTTPStatus.NOT_FOUND
                    }
                }

            return {
                'payload': {
                    'message': str(e),
                    'status': HTTPStatus.BAD_REQUEST
                }
            }

        return {'payload': response, 'status': HTTPStatus.OK}

    def post(self, request):
        """
        Handler which generates and returns an mdm keypair
        """

        # generate mdm credentials
        LOGGER.info("Generating MDM Credentials")
        system_authtoken = request[SYSTEM_AUTHTOKEN]
        key_bundle = _load_key_bundle(system_authtoken)

        [public_key, private_key] = self.sodium_client.sign_generate_keypair()
        now = int(datetime.now().strftime('%s'))

        response = {}
        response['message'] = []
        status = HTTPStatus.OK

        try:
            # send public signing key to spacebridge
            send_mdm_signing_key_to_spacebridge(request[SESSION][AUTHTOKEN],
                                                public_key, key_bundle)

        except Exception as e:
            status = HTTPStatus.INTERNAL_SERVER_ERROR
            LOGGER.warn(
                "Failed to register mdm keys with spacebridge. error=%s", e)
            return {
                'payload': {
                    'failed_save': True,
                    'message': e
                },
                'status': status,
            }

        # update key generation timestamp
        try:
            kvstore_service = kvstore(collection=USER_META_COLLECTION_NAME,
                                      session_key=request[SESSION][AUTHTOKEN],
                                      owner=request[SESSION][USER])
            entry = {KEY: MDM_KEYPAIR_GENERATION_TIME, TIMESTAMP: now}
            kvstore_service.insert_or_update_item_containing_key(entry)

        except Exception as e:
            status = HTTPStatus.INTERNAL_SERVER_ERROR
            response['failed_timesave'] = True
            response['message'].append(e.message)

        # store to storage/passwords
        try:
            [_, created_public_key] = update_or_create_sensitive_data(
                request[SESSION][AUTHTOKEN], MDM_SIGN_PUBLIC_KEY,
                py23.b64encode_to_str(public_key))

        except Exception as e:
            status = HTTPStatus.INTERNAL_SERVER_ERROR
            response['failed_public_localsave'] = True
            response['message'].append(str(e))

        try:
            [_, created_private_key] = update_or_create_sensitive_data(
                request[SESSION][AUTHTOKEN], MDM_SIGN_PRIVATE_KEY,
                py23.b64encode_to_str(private_key))

        except Exception as e:
            status = HTTPStatus.INTERNAL_SERVER_ERROR
            response['failed_private_localsave'] = True
            response['message'].append(str(e))

        # don't pass back the message if we have no errors
        if not response['message']:
            del response['message']

        response[SIGN_PUBLIC_KEY] = py23.b64encode_to_str(public_key)
        response[SIGN_PRIVATE_KEY] = py23.b64encode_to_str(private_key)
        response[TIMESTAMP] = now

        return {
            'payload': response,
            'status': status,
        }
Esempio n. 18
0
def handle_query(auth_code, device_name, user, system_authtoken):
    """
    Handler for the initial AuthenticationQueryRequest call. This function:
        1. Makes the AuthenticationQueryRequest request to the server
        2. Checks if app_type has been disabled
        3. Stores a temporary record in the kvstore

    :param auth_code: User-entered authorization code to be returned to Spacebridge
    :param device_name: Name of the new device
    :return: Confirmation code to be displayed to user, and id of temporary kvstore record to be returned later
    """

    LOGGER.info('Received new registration query request by user=%s' % user)

    # Makes the AuthenticationQueryRequest request to the server
    sodium_client = SodiumClient(LOGGER.getChild('sodium_client'))
    encryption_context = SplunkEncryptionContext(system_authtoken,
                                                 SPACEBRIDGE_APP_NAME,
                                                 sodium_client)
    client_device_info = authenticate_code(auth_code,
                                           encryption_context,
                                           resolve_app_name,
                                           config=config)
    app_name = client_device_info.app_name
    app_id = client_device_info.app_id

    LOGGER.info("client_device_info={}".format(client_device_info))

    user_devices = get_devices_for_user(user, system_authtoken)
    LOGGER.info("user_devices=%s" % user_devices)

    if any(device[DEVICE_NAME_LABEL] == device_name
           and device['device_type'] == app_name for device in user_devices):
        err_msg = (
            'Registration Error: user={} device_name={} of app_type={} already exists'
            .format(user, device_name, app_name))
        LOGGER.info(err_msg)
        raise Errors.SpacebridgeRestError(err_msg, http.CONFLICT)

    # Checks if app_type has been disabled
    if not retrieve_state_of_app(app_name, system_authtoken):
        disabled_message = 'Registration Error: Application type app_name="{}" is disabled'.format(
            app_name)
        LOGGER.info(disabled_message)
        return {
            'payload': {
                'message': disabled_message,
                'is_admin': user_is_administrator(user, system_authtoken),
                'app_name': app_name,
            },
            'status': 422,
        }

    # Stores a temporary record in the kvstore
    kvstore_unconfirmed = KvStore(UNCONFIRMED_DEVICES_COLLECTION_NAME,
                                  system_authtoken,
                                  owner=user)
    kvstore_payload = client_device_info.to_json()
    kvstore_payload['device_name'] = device_name
    kvstore_payload['device_type'] = app_name
    kvstore_payload['app_id'] = app_id
    _, content = kvstore_unconfirmed.insert_single_item(kvstore_payload)

    return {
        'payload': {
            'temp_key': json.loads(content)['_key'],
            'conf_code': client_device_info.confirmation_code
        },
        'status': http.OK,
    }
Esempio n. 19
0
    def handle_saml_mdm_request(self, user, session_token, system_authtoken,
                                mdm_signing_bundle, body):
        """
        Handles the MDM SAML Registration Request.
        Validates signature sent from client, validates session token, generates a JWT token,
        and sends it encrypted using splapp's keys and the client public key
        :param user: string provided by rest handler
        :param session_token: string
        :param system_authtoken: string
        :param mdm_signing_bundle: Object
        :param body: JSON
        :return: Reponse object with payload and status
        """
        public_key = base64.b64decode(
            extract_parameter(body, PUBLIC_KEY_LABEL, BODY_LABEL))
        mdm_signature = base64.b64decode(
            extract_parameter(body, MDM_SIGNATURE_LABEL, BODY_LABEL))

        client_keys = EncryptionKeys(None, None, public_key, None)
        client_encryption_context = EncryptionContext(client_keys)

        try:
            valid_signature = yield sign_verify(
                SodiumClient(LOGGER.getChild("sodium_client")),
                base64.b64decode(
                    mdm_signing_bundle['sign_public_key'].encode('utf8')),
                client_encryption_context.encrypt_public_key(), mdm_signature)
        except Exception as e:
            LOGGER.exception(
                "Exception verifying signature from client for user={}".format(
                    user))
            defer.returnValue({
                'payload': {
                    'token': "",
                    'user': user,
                    'status': http.UNAUTHORIZED
                },
                'status': http.OK
            })

        async_splunk_client = self.async_client_factory.splunk_client()
        valid_request = yield valid_session_token(user, session_token,
                                                  async_splunk_client)

        LOGGER.info(
            "Received new mdm registration request by user={}".format(user))

        if valid_signature and valid_request:
            try:
                credentials = SplunkJWTCredentials(user)
                credentials.load_jwt_token(SplunkAuthHeader(system_authtoken))
                LOGGER.info("Successfully fetched jwt token")
            except Exception as e:
                LOGGER.exception(
                    "Exception fetching jwt token for user={} with message={}".
                    format(user, e))
                defer.returnValue({
                    'payload': {
                        'token': "",
                        'user': user,
                        'status': 422
                    },
                    'status': http.OK
                })

            splapp_encryption_context = SplunkEncryptionContext(
                system_authtoken, constants.SPACEBRIDGE_APP_NAME,
                SodiumClient(LOGGER.getChild("sodium_client")))

            # Encrypt session token using splapp keys
            secured_session_token = splapp_encryption_context.secure_session_token(
                credentials.get_credentials())
            # Encrypt session token using client's given public key
            encrypted_jwt_token = yield encrypt_for_send(
                SodiumClient(LOGGER.getChild("sodium_client")),
                client_encryption_context.encrypt_public_key(),
                secured_session_token)
            base64_encrypted_jwt_token = base64.b64encode(encrypted_jwt_token)

            defer.returnValue({
                'payload': {
                    'token': base64_encrypted_jwt_token,
                    'user': user,
                    'status': http.OK
                },
                'status': http.OK
            })
        else:
            LOGGER.info(
                "Error: Mismatched user={} and session token".format(user))
            defer.returnValue({
                'payload': {
                    'token': "",
                    'user': user,
                    'status': http.UNAUTHORIZED
                },
                'status': http.OK
            })
 def __init__(self, command_line, command_arg):
     BaseRestHandler.__init__(self)
     self.sodium_client = SodiumClient()
def run_search_process(job_contexts, sodium_client):
    d = task.deferLater(reactor, 0, _run, job_contexts, sodium_client)

    def handle_success(result):
        LOGGER.debug("Search job process finished")
        reactor.stop()

    def handle_error(error):
        LOGGER.error("Search job finished with error=%s", error)
        reactor.stop()

    d.addCallback(handle_success)
    d.addErrback(handle_error)
    LOGGER.debug("Starting reactor")
    reactor.run()
    sys.exit(0)


if __name__ == "__main__":
    # entry point for single search processing
    LOGGER.debug("Starting subscription os process")
    try:
        SODIUM_CLIENT = SodiumClient()
        for line in fileinput.input():
            pickle_format = base64.b64decode(line)
            input_contexts = pickle.loads(pickle_format)
            run_search_process(input_contexts, SODIUM_CLIENT)
    except Exception as e:
        LOGGER.exception("Failed to start subscription os process")
        raise e
Esempio n. 22
0
def send_push_notifications(request_context, notification, recipient_devices,
                            async_kvstore_client, async_spacebridge_client,
                            async_splunk_client):
    """
    Given a notification object and a list of device ids, sends a post request to the Spacebridge notifications
    api for each device id
    :param request_context:
    :param notification: notification object to be sent
    :param recipient_devices: list of device id strings
    :param async_kvstore_client: AsyncKVStoreClient
    :param async_spacebridge_client: AsyncSpacebridgeClient
    :return:
    """

    sodium_client = SodiumClient(LOGGER.getChild('sodium_client'))
    sign_keys_response = yield async_splunk_client.async_get_sign_credentials(
        request_context.auth_header)

    if sign_keys_response[0] == http.OK:

        encryption_keys = EncryptionKeys(
            b64decode(sign_keys_response[1]['sign_public_key']),
            b64decode(sign_keys_response[1]['sign_private_key']), None, None)
        encryption_context = EncryptionContext(encryption_keys, sodium_client)

    else:
        LOGGER.exception(
            "Unable to fetch encryption keys with error_code={}".format(
                sign_keys_response[0]))
        raise EncryptionKeyError(sign_keys_response[1], sign_keys_response[0])

    sender_id = encryption_context.sign_public_key(
        transform=encryption_context.generichash_raw)
    sender_id_hex = py23.encode_hex_str(sender_id)

    headers = {
        'Content-Type': 'application/x-protobuf',
        'Authorization': sender_id_hex
    }
    recipient_devices = [device.encode("utf8") for device in recipient_devices]
    deferred_responses = []

    signer = partial(sign_detached, sodium_client,
                     encryption_context.sign_private_key())

    for device_id in recipient_devices:
        device_id_raw = b64decode(device_id)

        try:
            _, receiver_encrypt_public_key = yield public_keys_for_device(
                device_id_raw, request_context.auth_header,
                async_kvstore_client)

            encryptor = partial(encrypt_for_send, sodium_client,
                                receiver_encrypt_public_key)

            LOGGER.info("Sending notification alert_id=%s, device_id=%s" %
                        (notification.alert_id, device_id))
            notification_request = notifications.build_notification_request(
                device_id, device_id_raw, sender_id, notification, encryptor,
                signer)

            # Send post request asynchronously
            deferred_responses.append(
                async_spacebridge_client.async_send_notification_request(
                    auth_header=SpacebridgeAuthHeader(sender_id),
                    data=notification_request.SerializeToString(),
                    headers=headers))

        except KeyNotFoundError:
            LOGGER.info("Public key not found for device_id=%s" % device_id)
        except SodiumOperationError:
            LOGGER.warn("Sodium operation failed! device_id=%s" % device_id)

    # Wait until all the post requests have returned
    deferred_list = yield defer.DeferredList(deferred_responses)
    responses = yield [response[1] for response in deferred_list]
    results = [(recipient_devices[i], responses[i].code, responses[i].text())
               for i in range(len(responses))]

    LOGGER.info(("Finished sending push notifications with responses=%s" %
                 str(results)))