You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

193 lines
9.8 KiB

"""
(C) 2019 Splunk Inc. All rights reserved.
APIs for device registration to Spacebridge
"""
import collections
import sys
from cloudgateway import py23
from cloudgateway.key_bundle import KeyBundle
from cloudgateway.private.registration.authenticate import submit_auth_code, parse_spacebridge_response, \
verify_mdm_signature
from cloudgateway.private.registration.pairing import build_encypted_credentials_bundle, pair_device_with_sb
from cloudgateway.private.registration import unregister
from cloudgateway.private.registration.client import make_device_authentication_request, \
parse_device_authentication_response, make_authentication_result_request, parse_authentication_result_response, \
parse_credentials_bundle
from cloudgateway.private.encryption.encryption_handler import encrypt_for_send
from cloudgateway.device import DeviceInfo, CredentialsBundle
from cloudgateway.private.registration.util import sb_auth_header
from cloudgateway.private.exceptions.rest import CloudgatewayServerError, CloudgatewayMaxRetriesError
from cloudgateway.private.util.config import SplunkConfig
from functools import partial
from cloudgateway.private.util.tokens_util import calculate_token_info
from spacebridge_protocol import http_pb2
def authenticate_code(auth_code, encryption_context, resolve_app_name, config=SplunkConfig(), key_bundle=None,
mdm_signing_public_key=None):
"""
Part 1/2 of the registration process
Submit an auth code to space bridge, and retrieve the encryption credentials for the device associated to
that auth code
:param auth_code: auth code shown on mobile device
:param encryption_context: EncryptionContext object. Can be a regular EncryptionContext or a subclass such
as SplunkEncryptionContext depending on whether you want to run in standalone mode or not.
:param resolve_app_name: A function that, given an app id, will return a human friendly app name
:param config: CloudgatewaySdkConfig object
:return: DeviceInfo object
"""
raw_response = submit_auth_code(auth_code, encryption_context, config, key_bundle)
sb_response_proto = parse_spacebridge_response(raw_response)
encrypt_public_key = sb_response_proto.payload.publicKeyForEncryption
sign_public_key = sb_response_proto.payload.publicKeyForSigning
app_friendly_name = sb_response_proto.payload.appFriendlyName
app_name = app_friendly_name if app_friendly_name else resolve_app_name(sb_response_proto.payload.appId)
platform = sb_response_proto.payload.appPlatform
if mdm_signing_public_key:
verify_mdm_signature(sb_response_proto.payload, mdm_signing_public_key, encryption_context)
device_encryption_info = DeviceInfo(encrypt_public_key,
sign_public_key,
sb_response_proto.payload.deviceId,
encryption_context.generichash_hex(sign_public_key).upper()[:8],
sb_response_proto.payload.appId,
app_name=app_name,
platform=platform)
return device_encryption_info
def pair_device(auth_code, user_auth_credentials, device_encryption_info, encryption_context, server_name="",
server_app_id = "",
config=SplunkConfig(),
key_bundle=None,
env_metadata=None):
"""
Part 2/2 of the registration process.
Send splunk app's public key information and encrypted credentials for user to cloudgateway. Upon success, this
will complete the credential swap as now the client has the splunk app's credentials and the splunk app has received
the app's public key
:param auth_code: auth code of the device
:param user_auth_credentials: UserAuthCredentials object interface which captures different forms of session tokens
:param device_encryption_info: DeviceInfo object which was returned in the authenticate_code api call
:param encryption_context: EncryptionContext object. Can be a regular EncryptionContext or a subclass such
as SplunkEncryptionContext depending on whether you want to run in standalone mode or not.
:param server_name: optional parameter for name of server so that device can identify which instance it is paired
with
:param config: CloudgatewaySdkConfig object
:return:
"""
user_auth_credentials.validate()
sodium_client = encryption_context.sodium_client
encrypt_public_key = encryption_context.encrypt_public_key()
sign_public_key = encryption_context.sign_public_key()
encryption_func = partial(encrypt_for_send, sodium_client, device_encryption_info.encrypt_public_key)
auth_header = sb_auth_header(encryption_context)
session_token = user_auth_credentials.get_credentials() if sys.version_info < (3, 0) else str.encode(user_auth_credentials.get_credentials())
token_expires_at = user_auth_credentials.get_expiration()
token_type = user_auth_credentials.get_token_type()
encrypted_session_token = encryption_context.secure_session_token(session_token)
encrypted_credentials_bundle = build_encypted_credentials_bundle(user_auth_credentials.get_username(),
encrypted_session_token, encryption_func,
server_app_id,
deployment_name=server_name, token_type=token_type,
token_expires_at=token_expires_at,
env_metadata=env_metadata)
pair_device_with_sb(auth_code, auth_header, device_encryption_info.device_id, encrypt_public_key, sign_public_key,
encrypted_credentials_bundle, config, key_bundle)
def unregister_device(device_id, encryption_context, config=SplunkConfig(), key_bundle=None):
"""
Unregister ascljk a device from cloud gateway. Initiating this will cause cloud gateway to remove the routing entry
and also force the client device to unregister
:param device_id: device id from DeviceInfo object
:param encryption_context: EncryptionContext object. Can be a regular EncryptionContext or a subclass such
as SplunkEncryptionContext depending on whether you want to run in standalone mode or not.
:return: DeviceUnregistrationResponse proto or throws an exception if the request can't be completed
:param config: CloudgatewaySdkConfig object
"""
unregister_proto = unregister.build_device_unregister_req(device_id, encryption_context)
raw_response = unregister.make_unregister_req(unregister_proto, sb_auth_header(encryption_context), config,
key_bundle)
unregister.parse_sb_response(raw_response)
return raw_response
def request_code(device_info, encryption_context, config=SplunkConfig(), key_bundle=None,
mdm_encryption_context=None):
"""Submits device's public key information to Cloud Gateway and fetches 10 digit
pairing code. First part of the registration process for the client.
Args:
device_info ([DeviceInfo]): device information of the client side
encryption_context ([EncryptionContext]):
Returns:
[String]: 10-digit auth code
"""
r = make_device_authentication_request(device_info, encryption_context, config, key_bundle, mdm_encryption_context)
return parse_device_authentication_response(r)
def fetch_server_credentials(auth_code, encryption_context, num_retries=10, config=SplunkConfig(), key_bundle=None):
"""Fetch server's encryption keys as well as session token. This is the last part of the registration process
for the client side and needs to happen after the server side has finished the registration process on the
server side. If the server side has not completed registration yet, Cloudgateway will wait up to 30s and
return a 204. This api will continue to retry when it receives a 204 until it we exceed num_retries at which
point it will return a max retries exceeded exception.
Args:
auth_code ([string]): 10-digit auth code
encryption_context ([EncryptionContext]):
num_retries ([int]): Number of times to retry. Only retries if Cloud gateway has not received registration
from the server side.
Returns:
[(DeviceInfo, CredentialsBundle)]: Tuple where first element is a DeviceInfo object containing encryption
information of the server side. The second element is a CredentialsBundle object which contains a session token
and username which is used for authenticating with the server side and running splunk queries.
"""
for i in range(num_retries):
r = make_authentication_result_request(auth_code, encryption_context, config, key_bundle)
try:
payload = parse_authentication_result_response(r)
break
except CloudgatewayServerError as e:
if e.status != 204:
raise e
if e.status == 204 and i == num_retries - 1:
raise CloudgatewayMaxRetriesError("Server side device has not completed registration. Please try again",
204)
signing_public_key = payload.deploymentPublicKeyForSigning
encrypt_public_key = payload.deploymentPublicKeyForEncryption
encrypted_credentials_bundle = payload.encryptedCredentialsBundle
server_id = encryption_context.generichash_raw(signing_public_key)
server_info = DeviceInfo(encrypt_public_key, signing_public_key, device_id=server_id)
credentials_bundle = parse_credentials_bundle(encrypted_credentials_bundle, encryption_context)
return server_info, credentials_bundle