def __init__(self): # These configs must be fetched at runtime self.severity_colors = config_table.get_config(config_table.SEVERITYCOLORS) self.severity_weights = config_table.get_config(config_table.SEVERITYWEIGHTS) self.version = config_table.get_config(config_table.VERSION) self.severity_formatting = self._create_severity_formatting() self.error_formatting = self._create_error_formatting()
def user_status_handler(event, _context): user = event.get('userRecord') if not user: return {'isAuthenticated': False} scan_id = event['scanId'] if not scan_id: raise exceptions.HttpNotFoundException('No scan found') s3_prefix = '{}/by-user/{}.xlsx'.format(PREFIX, user.get('email', '')) spreadsheet_url = create_presigned_url(BUCKET, s3_prefix) response = { 'isAuthenticated': True, 'scan': { 'lastScanDate': scan_id.split('#')[0], }, 'spreadsheetUrl': spreadsheet_url, } for key in ['email', 'firstName', 'lastName']: if key in user: response[key] = user[key] if 'accounts' in user: response['accountList'] = [{ 'accountId': account } for account in user['accounts'].keys()] requirements = scan_requirements() remediation_types = config_table.get_config(config_table.REMEDIATIONS) for requirement in requirements: if isinstance(requirement.get('remediation'), dict): remediation_id = requirement['remediation'].get('remediationId') remediation = remediation_types.get(remediation_id) if remediation: requirement['remediation']['parameters'] = remediation[ 'parameters'] response['requirements'] = requirements exclusion_types = config_table.get_config('exclusions') if exclusion_types: response['exclusionTypes'] = exclusion_types severity_colors = config_table.get_config('severityColors') if severity_colors: response['severityColors'] = severity_colors response['isAdmin'] = user.get('isAdmin', False) if response['isAdmin']: all_accounts = { account['accountId']: account for account in accounts_table.scan_all() } response.update({ 'isAdmin': True, 'usersList': list_all_users(all_accounts), 'accountList': create_account_list(all_accounts), 'payerAccounts': get_payer_accounts(all_accounts), }) return response
def exclude_handler(event, context): """ Find and apply matching exclusion for all NCRs Expected input event format { "scanId": scan_id, } """ exclusion_types = config_table.get_config(config_table.EXCLUSIONS) ncrs = ncr_table.query_all(KeyConditionExpression=Key('scanId').eq(event['openScan']['scanId'])) all_exclusions = exclusions_table.scan_all() grouped_exclusions = group_exclusions(all_exclusions) logger.info('Found %s exclusions', len(all_exclusions)) ncr_updated_count = 0 partial_exclusion_prioritizer = partial(exclusion_prioritizer, exclusion_types) records = [] for ncr in ncrs: matched_exclusions = match_exclusions(ncr, grouped_exclusions) ncr_exclusion = pick_exclusion(matched_exclusions, partial_exclusion_prioritizer) if ncr_exclusion: updated_ncr = update_ncr_exclusion(ncr, ncr_exclusion, exclusion_types) ncr_updated_count += 1 records.append(updated_ncr) ncr_table.batch_put_records(records) logger.info('Updated %s NCRs out of %s', ncr_updated_count, len(ncrs))
def test_get_config(self): test_config = { 'configId': config_table.EXCLUSIONS, 'config': 'configurations', } config_table.put_item(Item=test_config) assert test_config['config'] == config_table.get_config( test_config['configId'])
def test_update_version(self): test_version = { 'configId': config_table.VERSION, 'config': 'version50' } config_table.put_item(Item=test_version) load.update_version(test_version['config']) config_from_dynamodb = config_table.get_config( test_version['configId']) assert config_from_dynamodb == 'version50'
def test_update_exclusion_types(self): test_config = { 'configId': config_table.EXCLUSIONS, 'config': 'configurations2', } config_table.put_item(Item=test_config) load.update_exclusion_types(test_config['config']) config_from_dynamodb = config_table.get_config(test_config['configId']) assert config_from_dynamodb == 'configurations2'
def test_set_config(self): test_config = { 'configId': config_table.EXCLUSIONS, 'config': 'configurations', } config_table.put_item(Item=test_config) config_table.set_config(test_config['configId'], 'configuration2') config_from_dynamodb = config_table.get_config(test_config['configId']) assert config_from_dynamodb == 'configuration2'
def test_update_severity_colors(self): test_version = { 'configId': config_table.SEVERITYCOLORS, 'config': { 'ok': { 'background': 'FFFFFF', 'text': '11111' } } } config_table.put_item(Item=test_version) load.update_severity_colors(test_version['config']) config_from_dynamodb = config_table.get_config( test_version['configId']) assert config_from_dynamodb == { 'ok': { 'background': 'FFFFFF', 'text': '11111' } }
def __init__(self): self.exclusion_types = config_table.get_config(config_table.EXCLUSIONS) self._add_exclusion_headers()
def put_exclusions_handler(event, context): user_record = event.get('userRecord', {}) authz.require_is_admin(user_record) body = event.get('body', {}) current_exclusion_id = body.get('exclusionId', '') update_request = body.get('exclusion', {}) if current_exclusion_id: _, requirement_id, _ = split_exclusion_id(current_exclusion_id) else: requirement_id = update_request.get('requirementId', '') # input validation if not update_request: raise exceptions.HttpInvalidException('Must supply exclusion to put') # data validation current_exclusion = get_current_exclusion(current_exclusion_id) prospective_exclusion = dict_merge(current_exclusion, update_request) target_account_id = prospective_exclusion.get('accountId') delete_exclusion = current_exclusion if update_requires_replacement( current_exclusion, update_request) else {} requirement = requirements_table.get(requirement_id) exclusion_type = requirement.get('exclusionType') exclusion_config = config_table.get_config( config_table.EXCLUSIONS).get(exclusion_type) logger.debug( '%s', json.dumps( { 'current_exclusion': current_exclusion, 'prospective_exclusion': prospective_exclusion, 'target_account_id': target_account_id, 'delete_exclusion': delete_exclusion, 'requirement': requirement, 'exclusion_type': exclusion_type, 'exclusion_config': exclusion_config, }, default=str)) if not requirement: raise exceptions.HttpNotFoundException( f'Requirement not found: {requirement_id}') if not exclusion_config: raise exceptions.HttpInvalidException( f'Cannot find exclusion type: {exclusion_type}') # authorization authz.require_can_request_exclusion(user_record, target_account_id) new_exclusion = exclusions.update_exclusion(current_exclusion, update_request, exclusion_config, True) if new_exclusion: new_exclusion['exclusionId'] = exclusions_table.get_exclusion_id( new_exclusion) new_exclusion['lastModifiedByAdmin'] = user_record.get('email') new_exclusion['rqrmntId_rsrceRegex'] = '#'.join( [requirement_id, new_exclusion['resourceId']]) new_exclusion['type'] = exclusion_type if delete_exclusion: delete_exclusion['exclusionId'] = exclusions_table.get_exclusion_id( delete_exclusion) exclusions_table.update_exclusion(new_exclusion, delete_exclusion) if user_record.get('email'): audit_table.put_audit_trail( user_record['email'], audit_table.PUT_EXCLUSION_ADMIN, { 'updateRequest': update_request, 'newExclusion': new_exclusion, 'deleteExclusion': delete_exclusion, }) return { 'newExclusion': new_exclusion, 'deleteExclusion': delete_exclusion, }
def put_exclusions_for_user_handler(event, context): latest_scan_id = event.get('scanId', '') user_record = event.get('userRecord', {}) body = event.get('body', {}) ncr_id = body.get('ncrId', '') update_request = body.get('exclusion', {}) scan_id, account_id, resource_id, requirement_id = split_ncr_id(ncr_id) # input validation if scan_id != latest_scan_id: raise exceptions.HttpInvalidException( 'Can only exclude ncrs from latest scans') if not update_request: raise exceptions.HttpInvalidException('Must supply exclusion to put') # data validation requirement = requirements_table.get(requirement_id) ncr = ncr_table.get_ncr(scan_id, account_id, resource_id, requirement_id) current_exclusion = exclusions_table.get_exclusion( account_id=account_id, requirement_id=requirement_id, resource_id=resource_id) exclusion_type = requirement.get('exclusionType') exclusion_types = config_table.get_config(config_table.EXCLUSIONS) exclusion_config = exclusion_types.get(exclusion_type, {}) if not requirement: raise exceptions.HttpNotFoundException( f'Requirement not found: {requirement_id}') if not ncr: raise exceptions.HttpNotFoundException(f'NCR does not exist: {ncr_id}') if not exclusion_config: raise exceptions.HttpInvalidException( f'Cannot find exclusion type: {exclusion_type}') # authorization if exclusions.is_wildcard_exclusion(current_exclusion): raise exceptions.HttpForbiddenException( 'Wildcard exclusion applied to ncr') allowed_actions = ncr_util.get_allowed_actions(user_record, account_id, requirement, current_exclusion) prospective_exclusion = dict_merge(current_exclusion, update_request) prospective_state = exclusions.get_state(prospective_exclusion) if prospective_state in exclusions.REQUEST_EXCLUSION_STATES: if not allowed_actions['requestExclusion']: raise exceptions.HttpForbiddenException('Cannot requestExclusion') if prospective_state in exclusions.REQUEST_EXCLUSION_CHANGE_STATES: if not allowed_actions['requestExclusionChange']: raise exceptions.HttpForbiddenException( 'Cannot requestExclusionChange') # update new_exclusion = exclusions.update_exclusion(current_exclusion, update_request, exclusion_config, False) new_exclusion['accountId'] = account_id new_exclusion['resourceId'] = resource_id new_exclusion['requirementId'] = requirement_id new_exclusion['type'] = exclusion_type new_exclusion['exclusionId'] = exclusions_table.get_exclusion_id( new_exclusion) new_exclusion['lastModifiedByUser'] = user_record.get('email') new_exclusion[ 'rqrmntId_rsrceRegex'] = f'{new_exclusion["requirementId"]}#{new_exclusion["resourceId"]}' exclusions_table.update_exclusion(new_exclusion, {}) if user_record.get('email'): audit_table.put_audit_trail( user_record['email'], audit_table.PUT_EXCLUSION_USER, { 'updateRequest': update_request, 'newExclusion': new_exclusion, 'deleteExclusion': {}, }) new_allowed_actions = ncr_util.get_allowed_actions(user_record, account_id, requirement, new_exclusion) updated_ncr = update_ncr_exclusion(ncr, new_exclusion, exclusion_types) logger.debug('Updated ncr: %s', json.dumps(updated_ncr, default=str)) ncr_table.put_item(Item=updated_ncr) return { 'newExclusion': new_exclusion, 'newNcr': { 'ncrId': ncr_id, 'resource': updated_ncr, 'allowedActions': new_allowed_actions, } }
def remediate_manager_handler(event, context): # pylint: disable=R1710 """ This handler is the managing function, which invokes the various remediation functions. :param event: { "body": { "remediationParameters": dict, "ncrId": string, "overrideIacWarning": bool, } } """ user = event['userRecord'] email = user['email'] ## Pre remediation checks # ensure that ncrId is valid ncr_id = event['body']['ncrId'] ncr = check_ncr(ncr_id) # TODO check that ncr belongs to the latest scan requirement = requirements_table.get(ncr['requirementId']) remediation_parameters = event['body']['remediationParameters'] remediations = config_table.get_config(config_table.REMEDIATIONS) remediation = remediations[requirement.get('remediation', {}).get('remediationId', {})] # check that requirement has remediation if 'remediation' not in requirement: raise HttpInvalidException( 'No remediation available for this resource type') # Validate the input based on the remediation validate_input(remediation_parameters, remediation) # check that use can request remediated authz.can_remediate(user, ncr['accountId']) # Validate the both roles are deployed in the target account. readonly_role_arn, remediation_role_arn = get_roles_arns(ncr['accountId']) require_remediation_roles(email, readonly_role_arn, remediation_role_arn) # Update NCR record to indicate remediation in progress updated = ncr_table.update_remediation_status( { 'scanId': ncr['scanId'], 'accntId_rsrceId_rqrmntId': ncr['accntId_rsrceId_rqrmntId'], }, ncr_table.REMEDIATION_IN_PROGRESS, check_remediation_started=True) if not updated: return { 'status': RemediationStatus.ERROR, 'message': f'remediation for ncrId {event["body"]["ncrId"]} failed as remediation in progress' } # Add an audit trail record indicating remediation started audit_table.put_audit_trail(email, audit_table.REMEDIATION_STARTED, event['body']['remediationParameters']) # Invoke the remediation function worker_event: WorkerEvent = { 'remediationParameters': remediation_parameters, 'requirementBasedParameters': requirement['remediation'].get('requirementBasedParameters', {}), 'overrideIacWarning': event['body'].get('overrideIacWarning', False), 'readonlyRoleArn': readonly_role_arn, 'remediationRoleArn': remediation_role_arn, 'userEmail': email, 'ncr': ncr } status, message = invoke_worker(remediation['lambdaFunctionName'], worker_event) if status == RemediationStatus.ERROR: audit_status = audit_table.REMEDIATION_ERRORED update_remediation_status = ncr_table.REMEDIATION_ERROR elif status == RemediationStatus.VALIDATION_ERROR: audit_status = audit_table.REMEDIATION_INVALID_INPUT update_remediation_status = None # reset remediation status so it can be triggered again elif status == RemediationStatus.IAC_OVERRIDE_REQUIRED: audit_status = audit_table.REMEDIATION_IAC_OVERRIDE update_remediation_status = None # reset remediation status so it can be triggered again elif status == RemediationStatus.SUCCESS: audit_status = audit_table.REMEDIATION_COMPLETED update_remediation_status = ncr_table.REMEDIATION_SUCCESS audit_table.put_audit_trail(email, audit_status, remediation_parameters) # Publish remediation to an SNS topic if env variable has been set if SNS_ARN: publish_to_sns(status, message, user, ncr, requirement, remediation_parameters) # Update the NCR record updated_ncr = ncr_table.update_remediation_status( { 'scanId': ncr['scanId'], 'accntId_rsrceId_rqrmntId': ncr['accntId_rsrceId_rqrmntId'], }, update_remediation_status, check_remediation_started=False) response = { 'status': status, 'message': message, } if status == RemediationStatus.SUCCESS: response['updatedNcr'] = updated_ncr.get('Attributes', {}) return response