예제 #1
0
def handle(alert,
           type='sms',
           recipient_phone=None,
           sender_phone=None,
           message=None):

    if not os.environ.get('TWILIO_API_SID'):
        log.info(f"No TWILIO_API_SID in env, skipping handler.")
        return None

    twilio_sid = os.environ["TWILIO_API_SID"]

    twilio_token = vault.decrypt_if_encrypted(os.environ['TWILIO_API_TOKEN'])

    # check if phone is not empty if yes notification will be delivered to twilio
    if recipient_phone is None:
        log.error(f'Cannot identify assignee phone number')
        return None

    if message is None:
        log.error(f'SMS Message is empty')
        return None

    log.debug(
        f'Twilio message for recipient with phone number {recipient_phone}',
        message)

    client = Client(twilio_sid, twilio_token)

    response = client.messages.create(body=message,
                                      from_=sender_phone,
                                      to=recipient_phone)

    return response
예제 #2
0
def message_template(vars):
    payload = None

    # if we have Slack user data, send it to template
    if 'user' in vars:
        params = {
            'alert': vars['alert'],
            'properties': vars['properties'],
            'user': vars['user']
        }
    else:
        params = {'alert': vars['alert'], 'properties': vars['properties']}

    try:
        # retrieve Slack message structure from javascript UDF
        rows = db.connect_and_fetchall("select " + vars['template'] +
                                       "(parse_json('" + json.dumps(params) +
                                       "'))")
        row = rows[1]

        if len(row) > 0:
            log.debug(f"Template {vars['template']}", ''.join(row[0]))
            payload = json.loads(''.join(row[0]))
        else:
            log.error(f"Error loading javascript template {vars['template']}")
            raise Exception("Error loading javascript template " +
                            {vars['template']})
    except Exception as e:
        log.error(f"Error loading javascript template", e)
        raise

    return payload
예제 #3
0
    def GET(resource, key=None, limit=100, offset=0):
        if key is None:
            key = resource
        log.debug(f'GET {resource} limit={limit} offset={offset}')
        response = requests.get(
            url=f'https://cloud.tenable.com/{resource}',
            params={
                'limit': limit,
                'offset': offset
            },
            headers={"X-ApiKeys": f"accessKey={token}; secretKey={secret}"},
        )
        if response.status_code != 200:
            log.info(
                f'response status {response.status_code}: {response.text}')
            return

        result = response.json()
        elements = result.get(key)

        if elements is None:
            log.error(f'no {key} in :', result)
            return

        yield from elements

        pages = result.get('pagination', {})
        total = pages.get('total', 0)
        limit = pages.get('limit', 0)
        offset = pages.get('offset', 0)

        if total > limit + offset:
            yield from GET(resource, key, limit, offset + limit)
예제 #4
0
def get_data(organization_id: int,
             key: str,
             secret: str,
             params: dict = {}) -> dict:
    url = f"https://management.api.umbrella.com/v1/organizations/{organization_id}/roamingcomputers"
    headers: dict = {
        "Content-Type": "application/json",
        "Accept": "application/json"
    }
    try:
        req = requests.get(
            url,
            params=params,
            headers=headers,
            auth=requests.auth.HTTPBasicAuth(key, secret),
        )
        req.raise_for_status()

    except requests.HTTPError as http_err:
        log.error(f"Error GET: url={url}")
        log.error(f"HTTP error occurred: {http_err}")
        raise

    try:
        log.debug(req.status_code)
        json = req.json()

    except Exception as json_error:
        log.error(f"JSON error occurred: {json_error}")
        log.debug(f"requests response {req}")
        raise

    return json
예제 #5
0
def get_agent_data():
    scanners = list(GET('scanners'))
    log.debug(f'got {len(scanners)} scanners')
    for s in scanners:
        sid = s['id']
        agents = list(GET(f'scanners/{sid}/agents', 'agents', 5000))
        log.debug(f'scanner {sid} has {len(agents)} agents')
        yield from agents
예제 #6
0
def handle(alert, procedure=None, parameters=None):
    log.debug(f"Procedure name {procedure}")
    log.debug(f"Procedure parameters {parameters}")
    if procedure is not None:
        # call Snowflake stored procedure
        try:
            result = call_procedure(procedure, parameters)
            return result
        except Exception:
            return None
    else:
        return None
예제 #7
0
def ingest(table_name, options):
    landing_table = f'data.{table_name}'

    token = options['token']
    asset_entity_id = options['asset_entity_id']

    general_url = (
        f"https://api.assetpanda.com:443//v2/entities/{asset_entity_id}/objects"
    )
    fields_url = f"https://api.assetpanda.com:443//v2/entities/{asset_entity_id}"

    params = {"offset": 0, "limit": PAGE_SIZE}

    total_object_count = 0

    insert_time = datetime.utcnow()

    while params['offset'] <= total_object_count:

        log.debug("total_object_count: ", total_object_count)

        assets = get_data(token=token, url=general_url, params=params)

        list_object, total_object_count = get_list_objects_and_total_from_get_object(
            assets)

        dict_fields = get_data(token, fields_url, params=params)
        list_field = dict_fields["fields"]

        # Stripping down the metadata to remove unnecessary fields. We only really care about the following:
        # {"field_140": "MAC_Address", "field_135" :"IP"}
        clear_fields: dict = reduce(reduce_fields, list_field, {})

        # replace every key "field_NO" by the value of the clear_field["field_NO"]
        list_object_without_field_id = replace_device_key(
            list_object, clear_fields)

        db.insert(
            landing_table,
            values=[(entry, entry.get('id', None), insert_time)
                    for entry in list_object_without_field_id],
            select=db.derive_insert_select(LANDING_TABLE_COLUMNS),
            columns=db.derive_insert_columns(LANDING_TABLE_COLUMNS),
        )

        log.info(
            f'Inserted {len(list_object_without_field_id)} rows ({landing_table}).'
        )
        yield len(list_object_without_field_id)

        # increment the offset to get new entries each iteration in the while loop
        params["offset"] += PAGE_SIZE
예제 #8
0
def get_data(token: str, url: str, params: dict = {}) -> dict:
    headers: dict = {"Authorization": f"Bearer {token}"}
    try:
        log.debug(f"Preparing GET: url={url} with params={params}")
        req = requests.get(url, params=params, headers=headers)
        req.raise_for_status()
    except HTTPError as http_err:
        log.error(f"Error GET: url={url}")
        log.error(f"HTTP error occurred: {http_err}")
        raise http_err
    log.debug(req.status_code)

    return req.json()
예제 #9
0
def get_data(url: str, token: str, params: dict = {}) -> dict:
    headers: dict = {
        "Content-Type": "application/json",
        "Accept": "application/json",
        "X-Cisco-Meraki-API-Key": f"{token}",
    }
    try:
        log.debug(f"Preparing GET: url={url} with params={params}")
        req = requests.get(url, params=params, headers=headers)
        req.raise_for_status()
    except HTTPError as http_err:
        log.error(f"Error GET: url={url}")
        log.error(f"HTTP error occurred: {http_err}")
        raise
    log.debug(req.status_code)
    return req.json()
예제 #10
0
def get_data(url: str, cms_auth: str, api_key: str, params: dict = {}) -> dict:
    headers: dict = {
        'Content-Type': 'application/json',
        'aw-tenant-code': api_key,
        'Accept': 'application/json',
        'Authorization': cms_auth,
    }
    try:
        log.debug(f"Preparing GET: url={url} with params={params}")
        req = requests.get(url, params=params, headers=headers)
        req.raise_for_status()
    except HTTPError as http_err:
        log.error(f"Error GET: url={url}")
        log.error(f"HTTP error occurred: {http_err}")
        raise
    return req.json()
예제 #11
0
def handle(alert,
           type='msteams',
           webhook=None,
           title=None,
           color=None,
           message=None):
    """ Handler for the MS Teams integration utilizing the pymsteams library """

    if not webhook and not os.environ.get('MSTEAMS_WEBHOOK'):
        # log.info(f"No Webhook is provided nor there is a MSTEAMS_WEBHOOK in env, skipping handler.")
        return None

    webhook = webhook or vault.decrypt_if_encrypted(
        os.environ['MSTEAMS_WEBHOOK'])

    if message is None:
        log.error('Message is empty')
        return None

    # You must create the connectorcard object with the Microsoft Webhook URL
    m = connectorcard(webhook)

    if title:
        m.title(f'SnowAlert: {title}')
    else:
        m.title('SnowAlert')

    if color:
        # setting a hex color for the message
        m.color(color)

    # Add text to the message.
    if message:
        m.text(message)

    log.debug('Microsoft Teams message for via webhook', message)

    # send the message.
    m.send()

    if m.last_http_status.status_code != 300:
        log.error(f"MS Teams handler error", m.last_http_status.text)
        return None

    return m.last_http_status
예제 #12
0
def ingest_agents(table_name, options):
    last_export_time = next(
        db.fetch(
            f'SELECT MAX(export_at) as time FROM data.{table_name}'))['TIME']
    timestamp = datetime.now(timezone.utc)

    if (last_export_time is None
            or (timestamp - last_export_time).total_seconds() > 86400):
        all_agents = sorted(get_agent_data(),
                            key=lambda a: a.get('last_connect', 0))
        unique_agents = {a['uuid']: a for a in all_agents}.values()
        rows = [{'raw': ua, 'export_at': timestamp} for ua in unique_agents]
        log.debug(f'inserting {len(unique_agents)} unique (by uuid) agents')
        db.insert(f'data.{table_name}', rows)
        return len(rows)
    else:
        log.info('Not time to import Tenable Agents')
        return 0
예제 #13
0
def get_token_basic(client_id: str, client_secret: str) -> str:
    headers: dict = {
        "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8"
    }
    try:
        log.debug(f"Preparing POST: url={CROWDSTRIKE_AUTH_TOKEN_URL}")
        req = requests.post(
            CROWDSTRIKE_AUTH_TOKEN_URL,
            headers=headers,
            auth=requests.auth.HTTPBasicAuth(client_id, client_secret),
        )
        req.raise_for_status()
    except requests.HTTPError as http_err:
        log.error(f"Error GET: url={CROWDSTRIKE_AUTH_TOKEN_URL}")
        log.error(f"HTTP error occurred: {http_err}")
        raise http_err
    try:
        credential = req.json()
    except Exception as json_error:
        log.debug(f"JSON error occurred: {json_error}")
        log.debug(f"requests response {req}")
        raise (json_error)
    try:
        access_token = credential["access_token"]
    except BaseException:
        log.error("error auth request token")
        raise AttributeError("error auth request token")
    return access_token
예제 #14
0
def ingest_vulns(table_name):
    last_export_time = next(
        db.fetch(
            f'SELECT MAX(export_at) AS time FROM data.{table_name}'))['TIME']
    now = datetime.now(timezone.utc)

    if (last_export_time is None
            or (now - last_export_time) > timedelta(days=1)):
        log.debug('TIO export vulns')

        # insert empty row...
        db.insert(f'data.{table_name}', [{'export_at': now}])

        # ...because this line takes awhile
        vulns = TIO.exports.vulns()

        rows = [{'raw': v, 'export_at': now} for v in vulns]
        db.insert(f'data.{table_name}', rows)
        return len(rows)

    else:
        log.info('Not time to import Tenable vulnerabilities yet')
        return 0
예제 #15
0
def call_procedure(procedure, parameters):
    payload = None

    try:
        # call stored procedure
        if parameters is not None and len(parameters) > 0:
            params = "("
            for i in range(len(parameters)):
                params = params + "%s"
                if i < len(parameters) - 1:
                    params = params + ","
            params = params + ")"
        else:
            params = "()"

        sql = "call " + procedure + params

        log.debug(f"Procedure call sql {sql}")
        connection = db.connect()
        cur = connection.cursor()
        cur.execute(sql, tuple(parameters))
        rows = cur.fetchall()

        if len(rows) > 0:
            row = rows[0]

            if len(row) > 0:
                log.debug(f"Stored procedure  {procedure} response",
                          ''.join(row[0]))
                payload = ''.join(row[0])

        cur.close()
    except Exception as e:
        log.error(f"Error executing stored procedure", e)
        raise

    return payload
예제 #16
0
def handle(
    alert,
    type='sns',
    topic=None,
    target=None,
    recipient_phone=None,
    subject=None,
    message_structure=None,
    message=None,
):

    # check if phone is nit empty if yes notification will be delivered to twilio
    if recipient_phone is None and topic is None and target is None:
        log.error(f'Cannot identify recipient')
        return None

    if message is None:
        log.error(f'SNS Message is empty')
        return None

    log.debug(f'SNS message ', message)

    client = boto3.client('sns', region_name=REGION)

    params = {}

    if message_structure is not None:
        params['MessageStructure'] = message_structure
        if message_structure == 'json':
            message = json.dumps(message)

    if topic is not None:
        params['TopicArn'] = topic
    if target is not None:
        params['TargetArn'] = target
    if recipient_phone is not None:
        params['PhoneNumber'] = recipient_phone
    if subject is not None:
        params['Subject'] = subject

    log.debug(f"SNS message", message)

    params['Message'] = message

    # Try to send the message.
    try:
        # Provide the contents of the message.
        response = client.publish(**params)
    # Display an error if something goes wrong.
    except ClientError as e:
        log.error(f'Failed to send message {e}')
        return None
    else:
        log.debug("SNS message sent!")

    return response
예제 #17
0
def get_data(token: str, url: str, params: dict = {}) -> dict:
    headers: dict = {"Authorization": f"Bearer {token}"}
    try:
        log.debug(f"Preparing GET: url={url} with params={params}")
        req = requests.get(url, params=params, headers=headers)
        req.raise_for_status()
    except requests.HTTPError as http_err:
        log.error(f"Error GET: url={url}")
        log.error(f"Error GET: url={url}")
        log.error(f"HTTP error occurred: {http_err}")
        raise http_err
    try:
        json = req.json()

    except Exception as json_error:
        log.debug(f"JSON error occurred: {json_error}")
        log.debug(f"requests response {req}")
        json = {}
    return json
예제 #18
0
def message_template(vars):
    payload = None

    # remove handlers data, it might contain JSON incompatible strucutres
    vars['alert'].pop('HANDLERS')

    # if we have Slack user data, send it to template
    if 'user' in vars:
        params = {
            'alert': vars['alert'],
            'properties': vars['properties'],
            'user': vars['user'],
        }
    else:
        params = {'alert': vars['alert'], 'properties': vars['properties']}

    log.debug(f"Javascript template parameters", params)
    try:
        # retrieve Slack message structure from javascript UDF
        rows = db.connect_and_fetchall(
            "select " + vars['template'] + "(parse_json(%s))",
            params=[json.dumps(params)],
        )
        row = rows[1]

        if len(row) > 0:
            log.debug(f"Template {vars['template']}", ''.join(row[0]))
            payload = json.loads(''.join(row[0]))
        else:
            log.error(f"Error loading javascript template {vars['template']}")
            raise Exception(
                f"Error loading javascript template {vars['template']}")
    except Exception as e:
        log.error(f"Error loading javascript template", e)
        raise

    log.debug(f"Template payload", payload)
    return payload
예제 #19
0
def handle(alert,
           recipient_email=None,
           channel=None,
           template=None,
           message=None):
    if 'SLACK_API_TOKEN' not in os.environ:
        log.info(f"No SLACK_API_TOKEN in env, skipping handler.")
        return None

    slack_token = vault.decrypt_if_encrypted(os.environ['SLACK_API_TOKEN'])

    sc = SlackClient(slack_token)

    # otherwise we will retrieve email from assignee and use it to identify Slack user
    # Slack user id will be assigned as a channel

    title = alert['TITLE']

    if recipient_email is not None:
        result = sc.api_call("users.lookupByEmail", email=recipient_email)

        # log.info(f'Slack user info for {email}', result)

        if result['ok'] is True and 'error' not in result:
            user = result['user']
            userid = user['id']
        else:
            log.error(
                f'Cannot identify  Slack user for email {recipient_email}')
            return None

    # check if channel exists, if yes notification will be delivered to the channel
    if channel is not None:
        log.info(f'Creating new SLACK message for {title} in channel', channel)
    else:
        if recipient_email is not None:
            channel = userid
            log.info(
                f'Creating new SLACK message for {title} for user {recipient_email}'
            )
        else:
            log.error(f'Cannot identify assignee email')
            return None

    blocks = None
    attachments = None
    text = title

    if template is not None:
        properties = {'channel': channel, 'message': message}

        # create Slack message structure in Snowflake javascript UDF
        try:
            payload = message_template(locals())
        except Exception:
            return None

        if payload is not None:
            if 'blocks' in payload:
                blocks = json.dumps(payload['blocks'])

            if 'attachments' in payload:
                attachments = json.dumps(payload['attachments'])

            if 'text' in payload:
                text = payload['text']
        else:
            log.error(f'Payload is empty for template {template}')
            return None
    else:
        # does not have template, will send just simple message
        if message is not None:
            text = message

    response = sc.api_call("chat.postMessage",
                           channel=channel,
                           text=text,
                           blocks=blocks,
                           attachments=attachments)

    log.debug(f'Slack response', response)

    if response['ok'] is False:
        log.error(f"Slack handler error", response['error'])
        return None

    if 'message' in response:
        del response['message']

    return response
예제 #20
0
def handle(
    alert,
    recipient_email=None,
    channel=None,
    template=None,
    message=None,
    file_content=None,
    file_type=None,
    file_name=None,
    blocks=None,
    attachments=None,
    api_token=API_TOKEN,
    slack_api_token=None,
):
    slack_token_ct = slack_api_token or api_token
    slack_token = vault.decrypt_if_encrypted(slack_token_ct)

    sc = SlackClient(slack_token)

    # otherwise we will retrieve email from assignee and use it to identify Slack user
    # Slack user id will be assigned as a channel

    title = alert['TITLE']

    if recipient_email is not None:
        result = sc.api_call("users.lookupByEmail", email=recipient_email)

        # log.info(f'Slack user info for {email}', result)

        if result['ok'] is True and 'error' not in result:
            user = result['user']
            userid = user['id']
        else:
            log.error(
                f'Cannot identify  Slack user for email {recipient_email}')
            return None

    # check if channel exists, if yes notification will be delivered to the channel
    if channel is not None:
        log.info(f'Creating new SLACK message for {title} in channel', channel)
    else:
        if recipient_email is not None:
            channel = userid
            log.info(
                f'Creating new SLACK message for {title} for user {recipient_email}'
            )
        else:
            log.error(f'Cannot identify assignee email')
            return None

    text = title

    if template is not None:
        properties = {'channel': channel, 'message': message}

        # create Slack message structure in Snowflake javascript UDF
        payload = message_template(locals())

        if payload is not None:
            if 'blocks' in payload:
                blocks = json.dumps(payload['blocks'])

            if 'attachments' in payload:
                attachments = json.dumps(payload['attachments'])

            if 'text' in payload:
                text = payload['text']
        else:
            log.error(f'Payload is empty for template {template}')
            return None
    else:
        # does not have template, will send just simple message
        if message is not None:
            text = message

    response = None

    if file_content is not None:
        if template is not None:
            response = sc.api_call(
                "chat.postMessage",
                channel=channel,
                text=text,
                blocks=blocks,
                attachments=attachments,
            )

        file_descriptor = sc.api_call(
            "files.upload",
            content=file_content,
            title=text,
            channels=channel,
            iletype=file_type,
            filename=file_name,
        )

        if file_descriptor['ok'] is True:
            file = file_descriptor["file"]
            file_url = file["url_private"]
        else:
            log.error(f"Slack file upload error", file_descriptor['error'])

    else:
        response = sc.api_call(
            "chat.postMessage",
            channel=channel,
            text=text,
            blocks=blocks,
            attachments=attachments,
        )

    if response is not None:
        log.debug(f'Slack response', response)

        if response['ok'] is False:
            log.error(f"Slack handler error", response['error'])
            return None

        if 'message' in response:
            del response['message']

    return response
예제 #21
0
def ingest(table_name, options):
    ingest_type = 'client' if table_name.endswith(
        '_CLIENT_CONNECTION') else 'device'
    landing_table = f'data.{table_name}'

    timestamp = datetime.utcnow()
    api_token = options['api_token']
    whitelist = set(options['network_id_whitelist'])

    organizations = get_data(f"https://api.meraki.com/api/v0/organizations",
                             api_token)

    for organization in organizations:
        organization_id = organization.get('id')
        log.debug(f'Processing Meraki organization id {organization_id}')
        if not organization_id:
            continue

        networks = get_data(
            f"https://api.meraki.com/api/v0/organizations/{organization_id}/networks",
            api_token,
        )
        network_ids = {network.get('id') for network in networks}

        if whitelist:
            network_ids = network_ids.intersection(whitelist)

        for network in network_ids:
            log.debug(f'Processing Meraki network {network}')
            try:
                devices = get_data(
                    f"https://api.meraki.com/api/v0/networks/{network}/devices",
                    api_token,
                )
            except requests.exceptions.HTTPError as e:
                log.error(f"{network} not accessible, ")
                log.error(e)
                continue

            if ingest_type == 'device':
                db.insert(
                    landing_table,
                    values=[(
                        timestamp,
                        device,
                        device.get('serial'),
                        device.get('address'),
                        device.get('name'),
                        device.get('networkId'),
                        device.get('model'),
                        device.get('mac'),
                        device.get('lanIp'),
                        device.get('wan1Ip'),
                        device.get('wan2Ip'),
                        device.get('tags'),
                        device.get('lng'),
                        device.get('lat'),
                    ) for device in devices],
                    select=db.derive_insert_select(
                        LANDING_TABLE_COLUMNS_DEVICE),
                    columns=db.derive_insert_columns(
                        LANDING_TABLE_COLUMNS_DEVICE),
                )
                log.info(f'Inserted {len(devices)} rows ({landing_table}).')
                yield len(devices)

            else:
                for device in devices:
                    serial_number = device['serial']

                    try:
                        clients = get_data(
                            f"https://api.meraki.com/api/v0/devices/{serial_number}/clients",
                            api_token,
                        )
                    except requests.exceptions.HTTPError as e:
                        log.error(f"{network} not accessible, ")
                        log.error(e)
                        continue

                    db.insert(
                        landing_table,
                        values=[
                            (
                                timestamp,
                                client,
                                client.get('id'),
                                client.get('mac'),
                                client.get('description'),
                                client.get('mdnsName'),
                                client.get('dhcpHostname'),
                                client.get('ip'),
                                client.get('switchport'),
                                # vlan sometimes set to ''
                                client.get('vlan') or None,
                                client.get('usage', {}).get('sent') or None,
                                client.get('usage', {}).get('recv') or None,
                                serial_number,
                            ) for client in clients
                        ],
                        select=db.derive_insert_select(
                            LANDING_TABLE_COLUMNS_CLIENT),
                        columns=db.derive_insert_columns(
                            LANDING_TABLE_COLUMNS_CLIENT),
                    )
                    log.info(
                        f'Inserted {len(clients)} rows ({landing_table}).')
                    yield len(clients)
예제 #22
0
def handle(
    alert,
    type='ses',
    recipient_email=None,
    sender_email=None,
    text=None,
    html=None,
    subject=None,
    cc=None,
    bcc=None,
    reply_to=None,
    charset="UTF-8",
):

    # check if recipient email is not empty
    if recipient_email is None:
        log.error(f'Cannot identify recipient email')
        return None

    if text is None:
        log.error(f'SES Message is empty')
        return None

    if cc is None:
        ccs = []
    else:
        ccs = cc.split(",")

    if bcc is None:
        bccs = []
    else:
        bccs = bcc.split(",")

    if reply_to is None:
        replyTo = []
    else:
        replyTo = reply_to.split(",")

    destination = {
        'ToAddresses': [recipient_email],
        'CcAddresses': ccs,
        'BccAddresses': bccs,
    }

    body = {'Text': {'Charset': charset, 'Data': text}}

    if html is not None:
        body.update(Html={'Charset': charset, 'Data': html})

    message = {'Body': body, 'Subject': {'Charset': charset, 'Data': subject}}

    log.debug(f'SES message for recipient with email {recipient_email}', message)

    client = boto3.client('ses', region_name=REGION)

    # Try to send the email.
    try:
        # Provide the contents of the email.
        response = client.send_email(
            Destination=destination,
            Message=message,
            Source=sender_email,
            ReplyToAddresses=replyTo,
        )
    # Display an error if something goes wrong.
    except ClientError as e:
        log.error(f'Failed to send email {e}')
        return None
    else:
        log.debug("SES Email sent!")

    return response