예제 #1
0
 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()
예제 #2
0
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
예제 #3
0
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))
예제 #4
0
    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'])
예제 #5
0
    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'
예제 #6
0
    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'
예제 #7
0
    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'
예제 #8
0
    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'
            }
        }
예제 #9
0
 def __init__(self):
     self.exclusion_types = config_table.get_config(config_table.EXCLUSIONS)
     self._add_exclusion_headers()
예제 #10
0
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,
    }
예제 #11
0
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,
        }
    }
예제 #12
0
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