def test_premature_put(): LOG_STREAM.truncate(0) LOG_STREAM.seek(0) ContextLog.clear() with pytest.raises(RuntimeError) as excinfo: ContextLog.put_request_id('xyz') assert "Don't call put before get_logger!" in str(excinfo.value)
def test_premature_get_map(): LOG_STREAM.truncate(0) LOG_STREAM.seek(0) ContextLog.clear() with pytest.raises(RuntimeError) as ex: ContextLog.get_map() assert "Don't call get_map before get_logger!" in str(ex.value)
def user(): """ The guest entry point """ log = ContextLog.get_logger('user') log.info('User request') return jsonify(status=200, message='OK')
def test_sub_module(): LOG_STREAM.truncate(0) LOG_STREAM.seek(0) log = ContextLog.get_logger('test_sub_module', True) sub_module_function() log.info('sub_module_function') entry = json.loads(LOG_STREAM.getvalue()) assert entry['message'] == 'sub_module_function' assert 'contextMap' in entry assert entry['contextMap']['request-id'] == 'modulexyz'
def delete_user_pool_domain(domain): """ Function to delete user pool domain """ log = ContextLog.get_logger('delete_user_pool_domain') response = COGNITO_IDP_CLIENT.describe_user_pool_domain(Domain=domain) if 'UserPoolId' in response['DomainDescription']: log.info('domain exists - deleting: %s', domain) COGNITO_IDP_CLIENT.delete_user_pool_domain( Domain=domain, UserPoolId=response['DomainDescription']['UserPoolId'])
def check_email_valid(email): """ Check if email address is lower-case and valid as per the username_validator package """ log = ContextLog.get_logger('check_email_valid') if email.islower(): log.info('check_email_valid: email address is lower-case') else: log.warning('check_email_valid: email address is not all lower-case') raise Exception('Email must be lowercase') # Will raise and exception if invalid USERNAME_VALIDATOR.validate_all(email)
def check_email_domain_valid(email): """ Check if email domain is in the exposable email domain list """ log = ContextLog.get_logger('check_email_domain_valid') address = email.strip().split('@') if address[1] not in EMAIL_DOMAIN_BLACKLIST: log.info( 'check_email_domain_valid: email domain is not in disposable email domain list' ) else: log.warning( 'check_email_domain_valid: email domain is in disposable email domain list' ) raise Exception('Email domain is disposable')
def send(event, context, responseStatus, response_data, physicalResourceId=None, noEcho=False): """ From: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-lambda-function-code-cfnresponsemodule.html """ log = ContextLog.get_logger('send') responseUrl = event['ResponseURL'] responseBody = {} responseBody['Status'] = responseStatus responseBody[ 'Reason'] = 'See the details in CloudWatch Log Stream: ' + context.log_stream_name responseBody[ 'PhysicalResourceId'] = physicalResourceId or context.log_stream_name responseBody['StackId'] = event['StackId'] responseBody['RequestId'] = event['RequestId'] responseBody['LogicalResourceId'] = event['LogicalResourceId'] responseBody['NoEcho'] = noEcho responseBody['Data'] = response_data json_responseBody = json.dumps(responseBody) log.info('Response body: %s', json_responseBody) headers = { 'content-type': '', 'content-length': str(len(json_responseBody)) } try: response = requests.put(responseUrl, data=json_responseBody, headers=headers) log.info('Status code: ' + response.reason) except Exception as ex: log.error('send(..) failed executing requests.put(..): %s', str(ex))
def lambda_handler(event, context): log = ContextLog.get_logger('lambda_handler', True) ContextLog.put_start_time() ContextLog.put_request_id(context.aws_request_id) log.info('start') output = [] try: for record in event['records']: payload = base64.b64decode(record['data']).decode() log.info('Processing request id: %s', record['recordId']) # Transform time to isoformat and add to record d = json.loads(payload) t = datetime.strptime(d['datetime'], "%d/%b/%Y:%H:%M:%S %z") d['datetimeiso'] = t.isoformat() payload = json.dumps(d) + '\n' output_record = { 'recordId': record['recordId'], 'result': 'Ok', 'data': base64.b64encode(payload.encode()).decode() } output.append(output_record) log.info('Successfully processed records: %d', len(event['records'])) except: log.exception('Unexpected error') raise finally: ContextLog.put_end_time() log.info('Request: end') return {'records': output}
def handler(event, context): """ The Lambda handler entry point """ log = ContextLog.get_logger('handler', True) log.setLevel(os.environ.get('LOG_LEVEL', 'DEBUG')) ContextLog.put_start_time() ContextLog.put_request_id(context.aws_request_id) log.info('Request: start - event %s', json.dumps(event)) try: log.info('Request: running') return awsgi.response(app, event, context) except: log.exception('Unexpected exception') raise finally: ContextLog.put_end_time() log.info('Request: end')
def test_thread_context(): LOG_STREAM.truncate(0) LOG_STREAM.seek(0) log = ContextLog.get_logger('test_thread_context', True) ContextLog.put_start_time() sleep(0.1) # Short delay to test for sane duration range ContextLog.put('arbitrary', 'value') ContextLog.put_request_id('xyz') ContextLog.put_request_method('POST') ContextLog.put_request_path('/arbitrary') ContextLog.put_response_status('200') ContextLog.put_end_time() ContextLog.put_request_user_id('myuser') ContextLog.put_request_client_id('clientid') ContextLog.put_request_primary_ip('5.3.2.1') ContextLog.put_request_client_ip('1.2.3.5') ContextLog.put_request_viewer_country('dk') ContextLog.put_trigger_source('Event') log.debug('thread_context') entry = json.loads(LOG_STREAM.getvalue()) assert entry['message'] == 'thread_context' assert 'contextMap' in entry assert entry['contextMap']['arbitrary'] == 'value' assert entry['contextMap']['request-id'] == 'xyz' assert entry['contextMap']['request-method'] == 'POST' assert entry['contextMap']['request-path'] == '/arbitrary' assert entry['contextMap']['response-status'] == '200' assert entry['contextMap']['start-time'] assert entry['contextMap']['epoch-start-time'] assert entry['contextMap']['end-time'] assert entry['contextMap']['epoch-end-time'] assert 100 <= entry['contextMap']['duration'] <= 500 assert entry['contextMap']['user-id'] == 'myuser' assert entry['contextMap']['client-id'] == 'clientid' assert entry['contextMap']['primary-ip'] == '5.3.2.1' assert entry['contextMap']['client-ip'] == '1.2.3.5' assert entry['contextMap']['viewer-country'] == 'dk' assert entry['contextMap']['trigger-source'] == 'Event' assert ContextLog.get('trigger-source') == 'Event' assert ContextLog.get_map()
def sub_function(): ContextLog.put_request_id('xyz123')
def handler(event, context): """ Lambda main handler """ log = ContextLog.get_logger('handler', True) ContextLog.put_start_time() ContextLog.put_request_id(context.aws_request_id) try: log.info('Request: start - event %s', json.dumps(event)) user_pool_id = event['ResourceProperties']['UserPoolId'] domain = event['ResourceProperties']['Domain'] custom_domain_config = None if 'CustomDomainConfig' in event['ResourceProperties']: custom_domain_config = event['ResourceProperties'][ 'CustomDomainConfig'] log.info('Contains custom domain config found: %s', json.dumps(custom_domain_config)) certificate_arn = custom_domain_config['CertificateArn'] # Hack to support Secrets Manager as Cloudformation custom # resources do not presently support parameter resolution res = re.match( '{{resolve:secretsmanager:(.+):SecretString:([a-zA-Z0-9-_:]+)}}', certificate_arn) if res: secret_id = res[1] attrib_str = res[2].split(':') secret_attribute = attrib_str[0] if len( attrib_str) > 0 else None args = {} if len(attrib_str) > 1: args['VersionId'] = attrib_str[1] if len(attrib_str) > 2: args['VersionStage'] = attrib_str[2] secret_response = SECRETSMANAGER_CLIENT.get_secret_value( SecretId=secret_id, **args) secret_obj = json.loads(secret_response['SecretString']) attribute_value = secret_obj[secret_attribute] log.info('Replacing certificate arn with SecretString: ...%s', attribute_value[-5:]) custom_domain_config['CertificateArn'] = attribute_value response = None response_data = dict() if event['RequestType'] == 'Create': response = COGNITO_IDP_CLIENT.create_user_pool_domain( Domain=domain, UserPoolId=user_pool_id, CustomDomainConfig=custom_domain_config) response_data['Data'] = 'Created' elif event['RequestType'] == 'Update': # If domain name needs to be updated then we need to # delete the old domain and then create the new domain if 'OldResourceProperties' in event and event[ 'OldResourceProperties']['Domain'] != event[ 'ResourceProperties']['Domain']: delete_user_pool_domain( event['OldResourceProperties']['Domain']) response = COGNITO_IDP_CLIENT.create_user_pool_domain( Domain=domain, UserPoolId=user_pool_id, CustomDomainConfig=custom_domain_config) response_data['Data'] = 'Updated' elif event['RequestType'] == 'Delete': delete_user_pool_domain(event['ResourceProperties']['Domain']) response_data['Data'] = 'Deleted' else: send(event, context, FAILED, {'Data': 'Unexpected: {}'.format(event['RequestType'])}) return if response and 'CloudFrontDomain' in response: log.info( 'create_user_pool_domain response includes CloudFrontDomain: %s', response['CloudFrontDomain']) response_data['CloudFrontDomain'] = response['CloudFrontDomain'] send(event, context, SUCCESS, response_data) log.info('CognitoUserPoolDomain Success for request type %s', event['RequestType']) except Exception as ex: log.exception('Unexpected exception') send(event, context, FAILED, {'Data': str(ex)}) raise finally: ContextLog.put_end_time() log.info('Request: end')
def handler(event, context): """ The Lambda handler entry point Called when a user is confirmed (link or email verification) """ log = ContextLog.get_logger('handler', True) log.setLevel(os.environ.get('LOG_LEVEL', "DEBUG")) ContextLog.put_start_time() ContextLog.put_request_id(context.aws_request_id) ContextLog.put_request_user_id(event['request']['userAttributes']['email']) ContextLog.put_request_client_id(event['callerContext']['clientId']) ContextLog.put_trigger_source(event['triggerSource']) # Log event - but remove any sensitive information user_event = deepcopy(event) log.info('Request: start - event %s', json.dumps(user_event)) try: log.info('Processing: %s', event['triggerSource']) if event['triggerSource'] == 'PostConfirmation_ConfirmSignUp': user_event['result'] = 'CONFIRM_SIGNUP_SUCCESS' elif event[ 'triggerSource'] == 'PostConfirmation_ConfirmForgotPassword': user_event['result'] = 'CONFIRM_FORGOT_PASSWORD_SUCCESS' except: log.exception('Unexpected exception') user_event['result'] = 'ERROR' raise finally: publish_user_event(user_event) ContextLog.put_end_time() log.info('Request: end') return event
def sub_module_function(): ContextLog.put_request_id('modulexyz')
def handler(event, context): """ The Lambda handler entry point Called when a user is confirmed (link or email verification) """ log = ContextLog.get_logger('handler', True) log.setLevel(os.environ.get('LOG_LEVEL', "DEBUG")) ContextLog.put_start_time() ContextLog.put_request_id(context.aws_request_id) ContextLog.put_request_user_id(event['request']['userAttributes']['email']) ContextLog.put_request_client_id(event['callerContext']['clientId']) ContextLog.put_trigger_source(event['triggerSource']) # Log event but remove any sensitive information user_event = deepcopy(event) log.info('Request: start - event %s', json.dumps(user_event)) try: email = event['request']['userAttributes']['email'] check_email_valid(email) # We don't validate the email domain if it's an Admin call. # The Admin call is made during user migration and we allow # them through. if event['triggerSource'] == 'PreSignUp_AdminCreateUser': log.info('handler: Admin call does not validate domain') else: check_email_domain_valid(email) except: log.exception('Invalid email') raise finally: ContextLog.put_end_time() log.info('Request: end') return event