def failed():
     """
     Send Failed status message
     """
     message['State'] = 'FAILED'
     message['Note'] = ''
     notify(finding, message, LOGGER, cwlogs=APPLOGGER)
def test_notify(mocker):
    test_file = open(test_data + 'CIS_1-6.json')
    event = json.loads(test_file.read())
    test_file.close()

    finding = findings.Finding(event['detail']['findings'][0])

    logger = Logger(loglevel='info')
    logger_obj = mocker.patch('lib.logger.Logger.info', return_value=None)

    applogger = LogHandler('pytest')
    mocker.patch('lib.applogger.LogHandler.add_message', return_value='')

    # mocker.patch('lib.sechub_findings.Finding.resolve', return_value='')

    mocker.patch('lib.sechub_findings.Finding.update_text', return_value='')

    AWS = AWSClient('aws', 'us-east-1')
    mocker.patch('lib.awsapi_helpers.AWSClient.postit', return_value='')

    test_message = {
        'Note': '',
        'State': 'INFO',
        'Account': '111111111111',
        'Remediation': 'Remediate all the things',
        'AffectedObject': 'An AWS Thingy',
        'metrics_data': {
            'status': 'RESOLVED'
        }
    }
    findings.notify(finding,
                    test_message,
                    logger,
                    cwlogs=applogger,
                    sechub=True,
                    sns=AWS)
    logger_obj.assert_called_once_with(
        'INFO: "Remediate all the things" , Account Id: 111111111111, Resource: An AWS Thingy'
    )

    # assert logger_mock('message', mocker.ANY)
    test_message = {}
    findings.notify(finding,
                    test_message,
                    logger,
                    cwlogs=applogger,
                    sechub=True,
                    sns=AWS)
    logger_obj.assert_called_with(
        'INFO: error - missing note, Account Id: error, Resource: error')
def remediate(finding, metrics_data):

    message = {
        'Note': '',
        'State': 'INFO',
        'Account': finding.account_id,
        'Remediation': REMEDIATION,
        'metrics_data': metrics_data
    }

    def failed():
        """
        Send Failed status message
        """
        message['State'] = 'FAILED'
        message['Note'] = ''
        notify(finding, message, LOGGER, cwlogs=APPLOGGER)

    # Make sure it matches - custom action can be initiated for any finding.
    # Ignore if the finding selected and the playbook do not match
    cis_data = finding.is_cis_ruleset()
    if not cis_data:
        # Not an applicable finding - does not match ruleset
        # send an error and exit
        LOGGER.debug('CIS 1.3 - 1.4: incorrect custom action selection')
        APPLOGGER.add_message(
            'CIS 1.3 - 1.4: incorrect custom action selection')
        return

    if (cis_data['ruleid'] not in ['1.3', '1.4']):
        # Not an applicable finding - does not match rule
        # send an error and exit
        LOGGER.debug('CIS 1.3 - 1.4: incorrect custom action selection')
        APPLOGGER.add_message(
            'CIS 1.3 - 1.4: incorrect custom action selection')
        return

    resource_type = str(finding.details['Resources'][0]['Type'])

    if resource_type == 'AwsAccount':
        # This code snippet is invoked when the user selects a finding with type as AwsAccount
        # this finding in security hub is more referring to the account in general and doesn't provide
        # information of the specific remediation, once the specific Resource Type errors are resolved
        # this finding will be resolved as well, therefore there is no specific remediation for this finding.
        LOGGER.debug('for finding type AwsAccount, there is no resolution.')
        APPLOGGER.add_message(
            'AwsAccount is a general finding for the entire account. Once the specific findings are resolved for resource type(s) other than AwsAccount, \
         this will be marked as resolved.')
        message['State'] = 'INITIAL'
        message['Note'] = 'The finding is related to the AWS account.'
        notify(finding, message, LOGGER, cwlogs=APPLOGGER)

        return

    #==========================================================================
    # Import boto3 clients
    try:
        sess = BotoSession(finding.account_id, LAMBDA_ROLE)
        iam = sess.client('iam')
        iam_resource = sess.resource('iam')
    except Exception as e:
        LOGGER.error(e)
        failed()
        return

    try:
        non_rotated_key_user = str(finding.details['Resources'][0]['Id'])[31:]
    except KeyError as key:
        LOGGER.error('Could not find ' + str(key) +
                     ' in Resources data for the finding')
        failed()
        return
    except Exception as e:
        LOGGER.error(e)
        return

    # Mark the finding NOTIFIED while we remediate
    message['State'] = 'INITIAL'
    notify(finding, message, LOGGER, cwlogs=APPLOGGER)

    try:
        todays_date_time = datetime.datetime.now(datetime.timezone.utc)

        paginator = iam.get_paginator('list_access_keys')

        for response in paginator.paginate(UserName=non_rotated_key_user):

            for key_metadata in response['AccessKeyMetadata']:
                access_key_id = str(key_metadata['AccessKeyId'])
                key_age_finder = todays_date_time - datetime.datetime.fromisoformat(
                    str(key_metadata['CreateDate']))

                if key_age_finder <= datetime.timedelta(days=90):
                    LOGGER.debug("Access key: " + access_key_id +
                                 "is compliant.")
                    # APPLOGGER.add_message("Access key: " + access_key_id + "is compliant.")

                else:
                    message[
                        'Note'] = "Access key over 90 days old found: " + access_key_id
                    message['State'] = 'INFO'
                    message[
                        'AffectedObject'] = AFFECTED_OBJECT + ': ' + access_key_id
                    notify(finding,
                           message,
                           LOGGER,
                           cwlogs=APPLOGGER,
                           sechub=False)

                    access_key = iam_resource.AccessKey(
                        non_rotated_key_user, access_key_id)
                    # deactivate the key
                    access_key.deactivate()
                    get_key_status = iam.list_access_keys(
                        UserName=non_rotated_key_user, MaxItems=20)
                    for keys in get_key_status['AccessKeyMetadata']:
                        this_access_key_id = str(keys['AccessKeyId'])
                        this_key_status = str(keys['Status'])
                        # find the key Id that matches the exposed key
                        if this_access_key_id == access_key_id and this_key_status == 'Inactive':
                            message['State'] = 'RESOLVED'
                            message[
                                'Note'] = 'Remediation completed successfully, create new access keys using IAM console.'
                            notify(finding,
                                   message,
                                   LOGGER,
                                   cwlogs=APPLOGGER,
                                   sechub=True,
                                   sns=AWS)

    except Exception as e:
        LOGGER.error(e)
        failed()
        return
def remediate(finding, metrics_data):

    message = {
        'Note': '',
        'State': 'INFO',
        'Account': finding.account_id,
        'Remediation': REMEDIATION,
        'metrics_data': metrics_data
    }

    def failed():
        """
        Send Failed status message
        """
        message['State'] = 'FAILED'
        message['Note'] = ''
        notify(finding, message, LOGGER, cwlogs=APPLOGGER)

    # Make sure it matches - custom action can be initiated for any finding.
    # Ignore if the finding selected and the playbook do not match
    cis_data = finding.is_cis_ruleset()
    if not cis_data:
        # Not an applicable finding - does not match ruleset
        # send an error and exit
        LOGGER.debug('CIS 1.5 - 1.11: incorrect custom action selection')
        APPLOGGER.add_message('CIS 1.5 - 1.11: incorrect custom action selection')
        return

    if (cis_data['ruleid'] not in ['1.5', '1.6', '1.7', '1.8', '1.9', '1.10', '1.11']):
        # Not an applicable finding - does not match rule
        # send an error and exit
        LOGGER.debug('CIS 1.5 - 1.11: incorrect custom action selection')
        APPLOGGER.add_message('CIS 1.5 - 1.11: incorrect custom action selection')
        return

    #==========================================================================
    message['AffectedObject'] = AFFECTED_OBJECT
    try:
        sess = BotoSession(finding.account_id, LAMBDA_ROLE)
        iam = sess.client('iam')
    except Exception as e:
        LOGGER.error(e)
        failed()
        return

    # Mark the finding NOTIFIED while we remediate
    message['State'] = 'INITIAL'
    notify(finding, message, LOGGER, cwlogs=APPLOGGER)

    try:
        response = iam.update_account_password_policy(
            MinimumPasswordLength=14,
            RequireSymbols=True,
            RequireNumbers=True,
            RequireUppercaseCharacters=True,
            RequireLowercaseCharacters=True,
            AllowUsersToChangePassword=True,
            MaxPasswordAge=90,
            PasswordReusePrevention=24,
            HardExpiry=True
        )
        LOGGER.debug(response)

        message['State'] = 'RESOLVED'
        message['Note'] = '' # use default
        notify(finding, message, LOGGER, cwlogs=APPLOGGER, sns=AWS)
        return

    except Exception as e:
        LOGGER.error(e)
        failed()
        return
def remediate(finding, metrics_data):

    message = {
        'Note': '',
        'State': 'INFO',
        'Account': finding.account_id,
        'Remediation': REMEDIATION,
        'metrics_data': metrics_data
    }

    def failed():
        """
        Send Failed status message
        """
        message['State'] = 'FAILED'
        message['Note'] = ''
        notify(finding, message, LOGGER, cwlogs=APPLOGGER)

    # Make sure it matches - custom action can be initiated for any finding.
    # Ignore if the finding selected and the playbook do not match
    cis_data = finding.is_cis_ruleset()
    if not cis_data:
        # Not an applicable finding - does not match ruleset
        # send an error and exit
        LOGGER.debug('CIS 2.3: incorrect custom action selection.')
        APPLOGGER.add_message('CIS 2.3: incorrect custom action selection.')
        return

    if (cis_data['ruleid'] not in ['2.3']):
        # Not an applicable finding - does not match rule
        # send an error and exit
        LOGGER.debug('CIS 2.3: incorrect custom action selection.')
        APPLOGGER.add_message('CIS 2.3: incorrect custom action selection.')
        return

    resource_type = str(finding.details['Resources'][0]["Type"])

    if resource_type == 'AwsAccount':
        # This code snippet is invoked when the user selects a finding with type as AwsAccount
        # this finding in security hub is more referring to the account in general and doesn't provide
        # information of the specific remediation, once the specific Resource Type errors are resolved
        # this finding will be resolved as well, therefore there is no specific remediation for this finding.
        LOGGER.debug('for finding type AwsAccount, there is no resolution.')
        APPLOGGER.add_message(
            'AwsAccount is a general finding for the entire account. Once the specific findings are resolved for resource type(s) other than AwsAccount, \
         this will be marked as resolved.')
        message['State'] = 'INITIAL'
        message['Note'] = 'The finding is related to the AWS account.'
        notify(finding, message, LOGGER, cwlogs=APPLOGGER)
        return

    #==========================================================================
    # Parse ARN of non-compliant resource from Security Hub CWE
    try:
        raw_bucket_info = str(finding.details['Resources'][0]['Id'])

        # Remove ARN string, create new variable
        noncompliant_bucket = remove_arn_prefix(raw_bucket_info)

    except Exception as e:
        message['Note'] = str(e) + ' - Finding format is not as expected.'
        message['State'] = 'FAILED'
        notify(finding, message, LOGGER, cwlogs=APPLOGGER)
        return

    message['AffectedObject'] = AFFECTED_OBJECT + ': ' + noncompliant_bucket

    # Connect to APIs
    try:
        sess = BotoSession(finding.account_id, LAMBDA_ROLE)
        s3 = sess.client('s3')
    except Exception as e:
        LOGGER.error(e)
        failed()
        return

    # Mark the finding NOTIFIED while we remediate
    message['State'] = 'INITIAL'
    notify(finding, message, LOGGER, cwlogs=APPLOGGER)

    try:
        remove_public = s3.put_public_access_block(
            Bucket=noncompliant_bucket,
            PublicAccessBlockConfiguration={
                'BlockPublicAcls': True,
                'IgnorePublicAcls': True,
                'BlockPublicPolicy': True,
                'RestrictPublicBuckets': True
            })

        LOGGER.debug(remove_public)
        # Do no use default wording: this lambda calls SSM to do the remediation.
        # All we know is whether it was invoked, not whether it was successful
        message[
            'Note'] = '\"' + REMEDIATION + '\" remediation was successfully completed'
        message['State'] = 'RESOLVED'
        notify(finding, message, LOGGER, cwlogs=APPLOGGER, sns=AWS)

    except Exception as e:
        LOGGER.error(e)
        failed()
        return
Example #6
0
def remediate(finding, metrics_data):

    message = {
        'Note': '',
        'State': 'INFO',
        'Account': finding.account_id,
        'AffectedObject': AFFECTED_OBJECT,
        'Remediation': REMEDIATION,
        'metrics_data': metrics_data
    }

    def failed():
        """
        Send Failed status message
        """
        message['State'] = 'FAILED'
        message['Note'] = ''
        notify(finding, message, LOGGER, cwlogs=APPLOGGER)

    # Make sure it matches - custom action can be initiated for any finding.
    # Ignore if the finding selected and the playbook do not match
    cis_data = finding.is_cis_ruleset()
    if not cis_data:
        # Not an applicable finding - does not match ruleset
        # send an error and exit
        LOGGER.debug('CIS 2.9: incorrect custom action selection.')
        APPLOGGER.add_message('CIS 2.9: incorrect custom action selection.')
        return

    if (cis_data['ruleid'] not in ['2.9']):
        # Not an applicable finding - does not match rule
        # send an error and exit
        LOGGER.debug('CIS 2.9: incorrect custom action selection.')
        APPLOGGER.add_message('CIS 2.9: incorrect custom action selection.')
        return

    resource_type = str(finding.details['Resources'][0]['Type'])
    if resource_type == 'AwsAccount':
        # This code snippet is invoked when the user selects a finding with type as AwsAccount
        # this finding in security hub is more referring to the account in general and doesn't provide
        # information of the specific security group, once the specific security group errors are resolved
        # this finding will be resolved as well, therefore there is no specific remediation for this finding.
        LOGGER.debug(
            'for finding resource type AwsAccount, there is no resolution.')
        APPLOGGER.add_message(
            'AwsAccount is a general finding for the entire account. Once the specific findings are resolved for resource type(s) other than AwsAccount, \
         this will be marked as resolved.')
        message['State'] = 'INITIAL'
        message['Note'] = 'The finding is related to the AWS account.'
        notify(finding, message, LOGGER, cwlogs=APPLOGGER)
        return

    #==========================================================================
    # Grab non-logged VPC ID from Security Hub finding
    try:
        noncompliantVPC = str(
            finding.details['Resources'][0]['Id']).split(':')[5].split('/')[1]

        message[
            'AffectedObject'] = AFFECTED_OBJECT + ' for VPC: ' + noncompliantVPC
    except Exception as e:
        message['Note'] = str(e) + ' - Finding format is not as expected.'
        message['State'] = 'FAILED'
        notify(finding, message, LOGGER, cwlogs=APPLOGGER)
        return

    lambdaFunctionSeshToken = os.getenv('AWS_SESSION_TOKEN', '')

    # Get Flow Logs Role ARN from env vars
    DeliverLogsPermissionArn = 'arn:aws:iam::' + finding.account_id + ':role/SO0111_CIS29_remediationRole'

    # Import boto3 clients
    try:
        sess = BotoSession(finding.account_id, LAMBDA_ROLE)
        cwl = sess.client('logs')
        ec2 = sess.client('ec2')
    except Exception as e:
        LOGGER.error(e)
        failed()
        return

    # Mark the finding NOTIFIED while we remediate
    message['State'] = 'INITIAL'
    notify(finding, message, LOGGER, cwlogs=APPLOGGER)

    # set dynamic variable for CW Log Group for VPC Flow Logs
    vpcFlowLogGroup = "VPCFlowLogs/" + noncompliantVPC + lambdaFunctionSeshToken[
        0:32]
    # create cloudwatch log group
    try:
        create_log_grp = cwl.create_log_group(logGroupName=vpcFlowLogGroup)
    except Exception as e:
        LOGGER.error(e)
        failed()
        return

    # wait for CWL creation to propagate
    time.sleep(3)
    # create VPC Flow Logging
    try:
        enableFlowlogs = ec2.create_flow_logs(
            DryRun=False,
            DeliverLogsPermissionArn=DeliverLogsPermissionArn,
            LogGroupName=vpcFlowLogGroup,
            ResourceIds=[noncompliantVPC],
            ResourceType='VPC',
            TrafficType='REJECT',
            LogDestinationType='cloud-watch-logs')
        LOGGER.debug(enableFlowlogs)
    except Exception as e:
        failed()
        LOGGER.error(e)
        return

    # wait for Flow Log creation to propogate
    time.sleep(2)
    # searches for flow log status, filtered on unique CW Log Group created earlier
    try:
        confirmFlowlogs = ec2.describe_flow_logs(DryRun=False,
                                                 Filters=[
                                                     {
                                                         'Name':
                                                         'log-group-name',
                                                         'Values':
                                                         [vpcFlowLogGroup]
                                                     },
                                                 ])
        LOGGER.debug(confirmFlowlogs)
        flowStatus = str(confirmFlowlogs['FlowLogs'][0]['FlowLogStatus'])
        if flowStatus == 'ACTIVE':
            message[
                'Note'] = '\"' + REMEDIATION + '\" remediation was successful'
            message['State'] = 'RESOLVED'
            notify(finding, message, LOGGER, cwlogs=APPLOGGER, sns=AWS)
        else:
            failed()
            LOGGER.error(e)
            return

    except Exception as e:
        LOGGER.error(e)
        failed()
        return
Example #7
0
def remediate(finding, metrics_data):

    message = {
        'Note': '',
        'State': 'INFO',
        'Account': finding.account_id,
        'Remediation': REMEDIATION,
        'metrics_data': metrics_data
    }

    def failed():
        """
        Send Failed status message
        """
        message['State'] = 'FAILED'
        message['Note'] = ''
        notify(finding, message, LOGGER, cwlogs=APPLOGGER)

    # Make sure it matches - custom action can be initiated for any finding.
    # Ignore if the finding selected and the playbook do not match
    cis_data = finding.is_cis_ruleset()
    if not cis_data:
        # Not an applicable finding - does not match ruleset
        # send an error and exit
        LOGGER.debug('CIS 4.1 - 4.2: incorrect custom action selection')
        APPLOGGER.add_message(
            'CIS 4.1 - 4.2: incorrect custom action selection')
        return

    if (cis_data['ruleid'] not in ['4.1', '4.2']):
        # Not an applicable finding - does not match rule
        # send an error and exit
        LOGGER.debug('CIS 4.1 - 4.2: incorrect custom action selection')
        APPLOGGER.add_message(
            'CIS 4.1 - 4.2: incorrect custom action selection')
        return

    #==========================================================================
    # parse Security Group ID from Security Hub CWE
    sg_type = str(finding.details['Resources'][0]['Type'])
    if sg_type == 'AwsAccount':
        # This code snippet is invoked when the user selects a finding with type as AwsAccount
        # this finding in security hub is more referring to the account in general and doesn't provide
        # information of the specific security group, once the specific security group errors are resolved
        # this finding will be resolved as well, therefore there is no specific remediation for this finding.
        LOGGER.debug(
            'for security group finding type AwsAccount, there is no resolution.'
        )
        APPLOGGER.add_message(
            'AwsAccount is a general finding for the entire account. Once the specific findings are resolved for resource type(s) other than AwsAccount, \
         this will be marked as resolved.')
        message['State'] = 'INITIAL'
        message['Note'] = 'The finding is related to the AWS account.'
        notify(finding, message, LOGGER, cwlogs=APPLOGGER)
        return

    try:
        non_compliant_sg = str(
            finding.details['Resources'][0]['Details'][sg_type]['GroupId'])
    except Exception as e:
        message['Note'] = str(e) + ' - Finding format is not as expected.'
        message['State'] = 'FAILED'
        notify(finding, message, LOGGER, cwlogs=APPLOGGER)
        return

    message['AffectedObject'] = AFFECTED_OBJECT + ': ' + non_compliant_sg

    #import boto3 clients
    try:
        sess = BotoSession(finding.account_id, LAMBDA_ROLE)
        ssm = sess.client('ssm')
    except Exception as e:
        LOGGER.error(e)
        failed()
        return

    # Mark the finding NOTIFIED while we remediate
    message['State'] = 'INITIAL'
    notify(finding, message, LOGGER, cwlogs=APPLOGGER)

    try:
        response = ssm.start_automation_execution(
            # Launch SSM Doc via Automation
            DocumentName='AWS-DisablePublicAccessForSecurityGroup',
            DocumentVersion='1',
            Parameters={
                'GroupId': [non_compliant_sg],
                'AutomationAssumeRole': [
                    'arn:aws:iam::' + finding.account_id + ':role/' +
                    LAMBDA_ROLE
                ]
            })
        LOGGER.debug(response)
        message[
            'Note'] = '\"' + REMEDIATION + '\" remediation was successfully invoked via AWS Systems Manager'
        message['State'] = 'RESOLVED'
        notify(finding, message, LOGGER, cwlogs=APPLOGGER, sns=AWS)

    except Exception as e:
        LOGGER.error(e)
        failed()
        return
def remediate(finding, metrics_data):

    message = {
        'Note': '',
        'State': 'INFO',
        'Account': finding.account_id,
        'Remediation': REMEDIATION,
        'metrics_data': metrics_data
    }

    def failed():
        """
        Send Failed status message
        """
        message['State'] = 'FAILED'
        message['Note'] = ''
        notify(finding, message, LOGGER, cwlogs=APPLOGGER)

    # Make sure it matches - custom action can be initiated for any finding.
    # Ignore if the finding selected and the playbook do not match
    cis_data = finding.is_cis_ruleset()
    if not cis_data:
        # Not an applicable finding - does not match ruleset
        # send an error and exit
        LOGGER.debug('CIS 4.3: incorrect custom action selection')
        APPLOGGER.add_message('CIS 4.3: incorrect custom action selection')
        return

    if cis_data['ruleid'] not in ['4.3']:
        # Not an applicable finding - does not match rule
        # send an error and exit
        LOGGER.debug('CIS 4.3: incorrect custom action selection')
        APPLOGGER.add_message('CIS 4.3: incorrect custom action selection')
        return

    sg_type = str(finding.details['Resources'][0]['Type'])
    if sg_type == 'AwsAccount':
        # This code snippet is invoked when the user selects a finding with type as AwsAccount
        # this finding in security hub is more referring to the account in general and doesn't provide
        # information of the specific security group, once the specific security group errors are resolved
        # this finding will be resolved as well, therefore there is no specific remediation for this finding.
        LOGGER.debug(
            'for security group finding type AwsAccount, there is no resolution.'
        )
        APPLOGGER.add_message(
            'AwsAccount is a general finding for the entire account. Once the specific findings are resolved for resource type(s) other than AwsAccount, \
         this will be marked as resolved.')
        message['State'] = 'INITIAL'
        message['Note'] = 'The finding is related to the AWS account.'
        notify(finding, message, LOGGER, cwlogs=APPLOGGER)
        return

    #==========================================================================
    try:
        myDefaultSecGroupId = str(
            finding.details['Resources'][0]['Details'][sg_type]['GroupId'])
    except KeyError as key:
        LOGGER.error('Could not find ' + str(key) +
                     ' in Resources data for the finding')
        failed()
        return
    except Exception as e:
        LOGGER.error('Unexpected error: ' + str(sys.exc_info()))
        LOGGER.error(e)
        return

    message['AffectedObject'] = AFFECTED_OBJECT + ': ' + myDefaultSecGroupId

    try:
        sess = BotoSession(finding.account_id, LAMBDA_ROLE)
        ec2 = sess.resource('ec2')
    except Exception as e:
        LOGGER.error('Unexpected error: ' + str(sys.exc_info()))
        LOGGER.error(e)
        failed()
        return

    # Mark the finding NOTIFIED while we remediate
    message['State'] = 'INITIAL'
    notify(finding, message, LOGGER, cwlogs=APPLOGGER)

    try:
        # sg ec2 resource object
        defaultSG = ec2.SecurityGroup(myDefaultSecGroupId)
        # find ingress + egress rules
        defaultIngress = defaultSG.ip_permissions
        defaultEgress = defaultSG.ip_permissions_egress
        revokeIngress = defaultSG.revoke_ingress(IpPermissions=defaultIngress)
        revokeEgress = defaultSG.revoke_egress(IpPermissions=defaultEgress)
        LOGGER.info(revokeIngress)
        LOGGER.info(revokeEgress)
        message['State'] = 'RESOLVED'
        message['Note'] = ''  # Reset the value
        notify(finding, message, LOGGER, cwlogs=APPLOGGER, sns=AWS)
    except Exception as e:
        LOGGER.error('Unexpected error: ' + str(sys.exc_info()))
        LOGGER.error(e)
        failed()
        return
Example #9
0
def remediate(finding, metrics_data):

    message = {
        'Note': '',
        'State': 'INFO',
        'Account': finding.account_id,
        'AffectedObject': AFFECTED_OBJECT,
        'Remediation': REMEDIATION,
        'metrics_data': metrics_data
    }

    def failed():
        """
        Send Failed status message
        """
        message['State'] = 'FAILED'
        message['Note'] = ''
        notify(finding, message, LOGGER, cwlogs=APPLOGGER)

    # Make sure it matches - custom action can be initiated for any finding.
    # Ignore if the finding selected and the playbook do not match
    cis_data = finding.is_cis_ruleset()
    if not cis_data:
        # Not an applicable finding - does not match ruleset
        # send an error and exit
        LOGGER.debug('CIS 2.4: incorrect custom action selection.')
        APPLOGGER.add_message('CIS 2.4: incorrect custom action selection.')
        return

    if (cis_data['ruleid'] not in ['2.4']):
        # Not an applicable finding - does not match rule
        # send an error and exit
        LOGGER.debug('CIS 2.4: incorrect custom action selection.')
        APPLOGGER.add_message('CIS 2.4: incorrect custom action selection.')
        return
    
    resource_type = str(finding.details['Resources'][0]["Type"])

    if resource_type == 'AwsAccount':
        # This code snippet is invoked when the user selects a finding with type as AwsAccount
        # this finding in security hub is more referring to the account in general and doesn't provide
        # information of the specific remediation, once the specific Resource Type errors are resolved 
        # this finding will be resolved as well, therefore there is no specific remediation for this finding.
        LOGGER.debug('for finding type AwsAccount, there is no resolution.')
        APPLOGGER.add_message('AwsAccount is a general finding for the entire account. Once the specific findings are resolved for resource type(s) other than AwsAccount, \
         this will be marked as resolved.')
        message['State'] = 'INITIAL'
        message['Note'] = 'The finding is related to the AWS account.'
        notify(finding, message, LOGGER, cwlogs=APPLOGGER)
        return

    #==========================================================================
    # parse non-compliant trail from Security Hub finding
    try:
        non_compliant_trail = str(finding.details['Resources'][0]['Id']).split(':')[5].split('/')[1]
    except KeyError as key:
        LOGGER.error('Could not find ' + str(key) + ' in Resources data for the finding')
        return
    except Exception as e:
        LOGGER.error(e)
        return

    message['AffectedObject'] = AFFECTED_OBJECT + ': ' + non_compliant_trail
            
    # Set name for Cloudwatch logs group
    cloudwatchLogGroup = 'CloudTrail/CIS2-4-' + non_compliant_trail
    # CloudTrail to CloudWatch logging IAM Role on for each account
    cloudtrailLoggingArn = 'arn:' + AWS_PARTITION + ':iam::' + \
        finding.account_id + ':role/SO0111_CIS24_remediationRole_' + AWS_REGION

    # Connect to APIs
    try:
        sess = BotoSession(finding.account_id, LAMBDA_ROLE)
        cwl = sess.client('logs')
        cloudtrail = sess.client('cloudtrail')
    except Exception as e:
        LOGGER.error(e)
        failed()
        return

    # Mark the finding NOTIFIED while we remediate
    message['State'] = 'INITIAL'
    notify(finding, message, LOGGER, cwlogs=APPLOGGER)

    # create cloudwatch log group
    try:
        createGroup = cwl.create_log_group(
            logGroupName=cloudwatchLogGroup,
        )
        print(createGroup)
    except Exception as e:
        LOGGER.error(e)
        failed()
        return

    # wait for CWL group to propagate 
    time.sleep(2)
    # get CWL ARN
    try:
        describeGroup = cwl.describe_log_groups(logGroupNamePrefix=cloudwatchLogGroup)
        cloudwatchArn = str(describeGroup['logGroups'][0]['arn'])
    except Exception as e:
        LOGGER.error(e)
        message['State'] = 'FAILED'
        message['Note'] = '' # use default
        notify(finding, message, LOGGER, cwlogs=APPLOGGER)
        return

    # update non-compliant Trail
    try:
        updateCloudtrail = cloudtrail.update_trail(
            Name=non_compliant_trail,
            CloudWatchLogsLogGroupArn=cloudwatchArn,
            CloudWatchLogsRoleArn=cloudtrailLoggingArn
        )
        LOGGER.debug(updateCloudtrail)

        message['State'] = 'RESOLVED'
        message['Note'] = '' # use default
        notify(finding, message, LOGGER, cwlogs=APPLOGGER, sns=AWS)

    except Exception as e:
        LOGGER.error(e)
        failed()
        return
def remediate(finding, metrics_data):

    message = {
        'Note': '',
        'State': 'INFO',
        'Account': finding.account_id,
        'Remediation': REMEDIATION,
        'metrics_data': metrics_data
    }

    def failed():
        """
        Send Failed status message
        """
        message['State'] = 'FAILED'
        message['Note'] = ''
        notify(finding, message, LOGGER, cwlogs=APPLOGGER)

    # Make sure it matches - custom action can be initiated for any finding.
    # Ignore if the finding selected and the playbook do not match
    cis_data = finding.is_cis_ruleset()
    if not cis_data:
        # Not an applicable finding - does not match ruleset
        # send an error and exit
        LOGGER.debug('CIS 2.8: incorrect custom action selection.')
        APPLOGGER.add_message('CIS 2.8: incorrect custom action selection.')
        return

    if (cis_data['ruleid'] not in ['2.8']):
        # Not an applicable finding - does not match rule
        # send an error and exit
        LOGGER.debug('CIS 2.8: incorrect custom action selection.')
        APPLOGGER.add_message('CIS 2.8: incorrect custom action selection.')
        return

    resource_type = str(finding.details['Resources'][0]["Type"])

    if resource_type == 'AwsAccount':
        # This code snippet is invoked when the user selects a finding with type as AwsAccount
        # this finding in security hub is more referring to the account in general and doesn't provide
        # information of the specific remediation, once the specific Resource Type errors are resolved
        # this finding will be resolved as well, therefore there is no specific remediation for this finding.
        LOGGER.debug('for finding type AwsAccount, there is no resolution.')
        APPLOGGER.add_message(
            'AwsAccount is a general finding for the entire account. Once the specific findings are resolved for resource type(s) other than AwsAccount, \
         this will be marked as resolved.')
        message['State'] = 'INITIAL'
        message['Note'] = 'The finding is related to the AWS account.'
        notify(finding, message, LOGGER, cwlogs=APPLOGGER)
        return
    #==========================================================================
    # Parse ARN of non-compliant resource from Security Hub CWE
    try:
        noncompliantCMK = str(finding.details['Resources'][0]['Id'])
        formattedCMK = noncompliantCMK.replace("AWS::KMS::Key:", "")
    except Exception as e:
        message['Note'] = str(e) + ' - Finding format is not as expected.'
        message['State'] = 'FAILED'
        notify(finding, message, LOGGER, cwlogs=APPLOGGER)
        return

    message['AffectedObject'] = AFFECTED_OBJECT + ': ' + formattedCMK

    try:
        sess = BotoSession(finding.account_id, LAMBDA_ROLE)
        kms = sess.client('kms')
    except Exception as e:
        LOGGER.error(e)
        failed()
        return

    # Mark the finding NOTIFIED while we remediate
    message['State'] = 'INITIAL'
    notify(finding, message, LOGGER, cwlogs=APPLOGGER)

    # Rotate KMS Key
    try:
        rotate = kms.enable_key_rotation(KeyId=formattedCMK)
        time.sleep(3)
    except Exception as e:
        failed()
        return

    try:
        confirmRotate = kms.get_key_rotation_status(KeyId=formattedCMK)
        rotationStatus = str(confirmRotate['KeyRotationEnabled'])
        if rotationStatus == 'True':
            message['Note'] = ''
            message['State'] = 'RESOLVED'
            notify(finding, message, LOGGER, cwlogs=APPLOGGER, sns=AWS)
        else:
            failed()

    except Exception as e:
        LOGGER.error(e)
        failed()
        return
Example #11
0
def remediate(finding, metrics_data):

    message = {
        'Note': '',
        'State': 'INFO',
        'Account': finding.account_id,
        'Remediation': REMEDIATION,
        'metrics_data': metrics_data
    }

    def failed():
        """
        Send Failed status message
        """
        message['State'] = 'FAILED'
        message['Note'] = ''
        notify(finding, message, LOGGER, cwlogs=APPLOGGER)

    # Make sure it matches - custom action can be initiated for any finding.
    # Ignore if the finding selected and the playbook do not match
    cis_data = finding.is_cis_ruleset()
    if not cis_data:
        # Not an applicable finding - does not match ruleset
        # send an error and exit
        LOGGER.debug('CIS 2.6: incorrect custom action selection.')
        APPLOGGER.add_message('CIS 2.6: incorrect custom action selection.')
        return

    if (cis_data['ruleid'] not in ['2.6']):
        # Not an applicable finding - does not match rule
        # send an error and exit
        LOGGER.debug('CIS 2.6: incorrect custom action selection.')
        APPLOGGER.add_message('CIS 2.6: incorrect custom action selection.')
        return

    resource_type = str(finding.details['Resources'][0]["Type"])

    if resource_type == 'AwsAccount':
        # This code snippet is invoked when the user selects a finding with type as AwsAccount
        # this finding in security hub is more referring to the account in general and doesn't provide
        # information of the specific remediation, once the specific Resource Type errors are resolved
        # this finding will be resolved as well, therefore there is no specific remediation for this finding.
        LOGGER.debug('for finding type AwsAccount, there is no resolution.')
        APPLOGGER.add_message(
            'AwsAccount is a general finding for the entire account. Once the specific findings are resolved for resource type(s) other than AwsAccount, \
         this will be marked as resolved.')
        message['State'] = 'INITIAL'
        message['Note'] = 'The finding is related to the AWS account.'
        notify(finding, message, LOGGER, cwlogs=APPLOGGER)
        return
    #==========================================================================
    # Parse ARN of non-compliant resource from Security Hub CWE
    try:
        ctBucket = str(finding.details['Resources'][0]['Id'])
        # Remove ARN string, create new variable
        formattedCTBucket = resource_from_arn(ctBucket)
    except Exception as e:
        message['Note'] = str(e) + ' - Finding format is not as expected.'
        message['State'] = 'FAILED'
        notify(finding, message, LOGGER, cwlogs=APPLOGGER)
        return

    message['AffectedObject'] = AFFECTED_OBJECT + ': ' + formattedCTBucket

    try:
        sess = BotoSession(finding.account_id, LAMBDA_ROLE)
        ssm = sess.client('ssm')
        s3 = sess.client('s3')
    except Exception as e:
        LOGGER.error(e)
        failed()
        return

    # Mark the finding NOTIFIED while we remediate
    message['State'] = 'INITIAL'
    notify(finding, message, LOGGER, cwlogs=APPLOGGER)

    # Create a bucket for the access logs
    # The same bucket is used to log access for all CloudTrails in the same account
    accessLoggingBucket = LOGGING_BUCKET_PREFIX + "-" + finding.account_id + "-" + AWS_REGION
    accessLoggingBucket = accessLoggingBucket.lower()

    try:
        kwargs = {
            'Bucket': accessLoggingBucket,
            'GrantWrite': 'uri=http://acs.amazonaws.com/groups/s3/LogDelivery',
            'GrantReadACP':
            'uri=http://acs.amazonaws.com/groups/s3/LogDelivery'
        }
        if AWS_REGION != 'us-east-1':
            kwargs['CreateBucketConfiguration'] = {
                'LocationConstraint': AWS_REGION
            }

        s3.create_bucket(**kwargs)

        s3.put_bucket_encryption(Bucket=accessLoggingBucket,
                                 ServerSideEncryptionConfiguration={
                                     'Rules': [{
                                         'ApplyServerSideEncryptionByDefault':
                                         {
                                             'SSEAlgorithm': 'AES256'
                                         }
                                     }]
                                 })
    except botocore.exceptions.ClientError as error:
        if error.response['Error']['Code'] == 'BucketAlreadyExists':
            pass
        elif error.response['Error']['Code'] == 'BucketAlreadyOwnedByYou':
            pass
        else:
            LOGGER.error(error)
            failed()
            return
    except Exception as e:
        LOGGER.error(e)
        failed()
        return

    # execute automation with ConfigureS3BucketLogging Document
    try:
        response = ssm.start_automation_execution(
            DocumentName='AWS-ConfigureS3BucketLogging',
            DocumentVersion='1',
            Parameters={
                'BucketName': [formattedCTBucket],
                'GrantedPermission': ['READ'],
                'GranteeType': ['Group'],
                'GranteeUri': ['http://acs.amazonaws.com/groups/s3/LogDelivery'], ## Must Use URI, fails with Canonical Group Id
                'TargetPrefix' : [formattedCTBucket + '/'],
                'TargetBucket': [accessLoggingBucket],
                'AutomationAssumeRole': ['arn:' + AWS_PARTITION + ':iam::' + \
                    finding.account_id + ':role/' + LAMBDA_ROLE]
            }
        )
        LOGGER.debug(response)

        message[
            'Note'] = '\"' + REMEDIATION + '\" remediation was successfully invoked via AWS Systems Manager'
        message['State'] = 'RESOLVED'
        notify(finding, message, LOGGER, cwlogs=APPLOGGER, sns=AWS)

    except Exception as e:
        LOGGER.error(e)
        failed()
        return
Example #12
0
def remediate(finding, metrics_data):

    message = {
        'Note': '',
        'State': 'INFO',
        'Account': finding.account_id,
        'Remediation': REMEDIATION,
        'metrics_data': metrics_data
    }

    def failed():
        """
        Send Failed status message
        """
        message['State'] = 'FAILED'
        message['Note'] = ''
        notify(finding, message, LOGGER, cwlogs=APPLOGGER)

    # Make sure it matches - custom action can be initiated for any finding.
    # Ignore if the finding selected and the playbook do not match
    cis_data = finding.is_cis_ruleset()
    if not cis_data:
        # Not an applicable finding - does not match ruleset
        # send an error and exit
        LOGGER.debug('CIS 2.2: incorrect custom action selection.')
        APPLOGGER.add_message('CIS 2.2: incorrect custom action selection.')
        return

    if (cis_data['ruleid'] not in ['2.2']):
        # Not an applicable finding - does not match rule
        # send an error and exit
        LOGGER.debug('CIS 2.2: incorrect custom action selection.')
        APPLOGGER.add_message('CIS 2.2: incorrect custom action selection.')
        return

    resource_type = str(finding.details['Resources'][0]["Type"])

    if resource_type == 'AwsAccount':
        # This code snippet is invoked when the user selects a finding with type as AwsAccount
        # this finding in security hub is more referring to the account in general and doesn't provide
        # information of the specific remediation, once the specific Resource Type errors are resolved
        # this finding will be resolved as well, therefore there is no specific remediation for this finding.
        LOGGER.debug('for finding type AwsAccount, there is no resolution.')
        APPLOGGER.add_message(
            'AwsAccount is a general finding for the entire account. Once the specific findings are resolved for resource type(s) other than AwsAccount, \
         this will be marked as resolved.')
        message['State'] = 'INITIAL'
        message['Note'] = 'The finding is related to the AWS account.'
        notify(finding, message, LOGGER, cwlogs=APPLOGGER)
        return

    #==========================================================================
    # parse non-compliant trail from Security Hub finding
    try:
        non_compliant_trail = str(
            finding.details['Resources'][0]['Id']).split(':')[5].split('/')[1]
    except KeyError as key:
        LOGGER.error('Could not find ' + str(key) +
                     ' in Resources data for the finding')
        return
    except Exception as e:
        LOGGER.error(e)
        return

    message['AffectedObject'] = AFFECTED_OBJECT + ': ' + non_compliant_trail

    # Connect to APIs
    try:
        sess = BotoSession(finding.account_id, LAMBDA_ROLE)
        cloudtrail = sess.client('cloudtrail')
    except Exception as e:
        LOGGER.error(e)
        failed()
        return

    # Mark the finding NOTIFIED while we remediate
    message['State'] = 'INITIAL'
    notify(finding, message, LOGGER, cwlogs=APPLOGGER)

    # turn on cloudtrail log file validation
    try:
        response = cloudtrail.update_trail(Name=non_compliant_trail,
                                           EnableLogFileValidation=True)
        LOGGER.debug(response)

        message['State'] = 'RESOLVED'
        message['Note'] = ''  # use default
        notify(finding, message, LOGGER, cwlogs=APPLOGGER, sns=AWS)

    except Exception as e:
        LOGGER.error(e)
        failed()
        return