Exemple #1
0
def handle(event, context):
    log.info(f"Got Event: {event} with context {context}")
    try:
        records = event.get('Records', [])
        for record in records:
            event_name = record.get("eventName")
            # Only resync on adds / updates, never on deletes.
            if event_name != 'REMOVE':
                ddb_record = record.get("dynamodb", {})
                keys = ddb_record.get("Keys", {})
                destination = keys.get(REPL_DEST_KEY_NAME, {}).get("S", None)

                if destination:
                    log.info(f"Record updated with key: {destination}")
                    config: ReplicationConfig = repl_dao.get_config_repl(destination)

                    if config:
                        log.info(f"Got config: {config}, syncing...")
                        repl_svc.sync_config(config) and notify_slack(config)
                    else:
                        log.warning(f"Unable to find record with destination: {destination}. This *could* "
                                    f"indicate a serious issue with replication. If you see lots of these, please pay "
                                    f"attention.")
            else:
                log.info("Event is a delete event, skipping!")
    except Exception as e:
        log.error(e)
        title = "Figgy Dynamo Stream Replicator experienced and irrecoverable error!"
        message = f"The following error occurred in an the *figgy-dynamo-stream-replicator* lambda.\n"\
                  f"Figgy is designed to continually backoff and retry in the face of error. \n```{Utils.printable_exception(e)}```"
        message = SimpleSlackMessage(title=title, message=message, color=SlackColor.RED)
        slack.send_message(message)
        raise e
def handle(event, context):
    # Don't process other account's events.
    originating_account = event.get('account')
    if originating_account != ACCOUNT_ID:
        log.info(f"Received event from different account with id: {ACCOUNT_ID}. Skipping this event.")
        return

    try:
        log.info(f"Event: {event}")
        cache_dao: ConfigCacheDao = ConfigCacheDao(dynamo_resource)

        detail = event["detail"]
        action = detail.get("eventName")
        params = detail.get('requestParameters', {})
        ps_name = params.get('name') if params else None

        event_time = detail.get('eventTime')

        # Convert to millis since epoch
        # if event_time:
        #     event_time = int(datetime.strptime(event_time, "%Y-%m-%dT%H:%M:%SZ").timestamp() * 1000)
        # else:
        #     event_time = int(time.time() * 1000)

        if not ps_name:
            log.info(f"Received an event missing parameterStore path: {event}")
            return

        if action == DELETE_PARAM_ACTION or action == DELETE_PARAMS_ACTION:
            log.info(f"Deleting from cache: {ps_name}")
            items: Set[ConfigItem] = cache_dao.get_items(ps_name)
            if items:
                sorted_items = sorted(items)
                [cache_dao.delete(item) for item in sorted_items[:-1]]  # Delete all but the most recent item.
                cache_dao.mark_deleted(sorted_items[-1])
        elif action == PUT_PARAM_ACTION:
            items: Set[ConfigItem] = cache_dao.get_items(ps_name)
            [cache_dao.delete(item) for item in items]  # If any stragglers exist, get rid of em
            log.info(f"Putting in cache: {ps_name}")
            cache_dao.put_in_cache(ps_name)
        else:
            log.info(f"Unsupported action type found! --> {action}")
    except Exception as e:
        log.error(e)
        title = f"Figgy experienced an irrecoverable error! In account: {ACCOUNT_ID[0:5]}[REDACTED]"
        message = f"The following error occurred in an the figgy-config-cache-manager lambda. "
        f"If this appears to be a bug with figgy, please tell us by submitting a GitHub issue!"
        f" \n\n{Utils.printable_exception(e)}"
        message = SimpleSlackMessage(
            title=title,
            message=message,
            color=SlackColor.RED
        )
        slack.send_message(message)
        raise e
def handle(event, context):
    # Don't process other account's events.
    originating_account = event.get('account')
    if originating_account != ACCOUNT_ID:
        log.info(f"{originating_account} != {ACCOUNT_ID}")
        log.info(f"Received event from different account with id: {ACCOUNT_ID}. Skipping this event.")
        return

    try:
        log.info(f"Event: {event}")
        detail = event["detail"]
        action = detail.get("eventName")

        if 'errorMessage' in detail:
            log.info(f'Not processing event due to this being an error event with message: {detail["errorMessage"]}')
            return

        ps_name = detail.get('requestParameters', {}).get('name')
        triggering_user = parse_user(detail)

        if ps_name and action == PUT_PARAM_ACTION:
            repl_configs: List[ReplicationConfig] = repl_dao.get_config_repl_by_source(ps_name)
            merge_configs: List[ReplicationConfig] = repl_dao.get_configs_by_type(ReplicationType(REPL_TYPE_MERGE))
            for config in repl_configs:
                updated = repl_svc.sync_config(config)
                updated and notify_slack(config, triggering_user)  # Notify on update

            for config in merge_configs:
                log.info(f"Evaluating config: {config}")
                if isinstance(config.source, list):
                    for source in config.source:
                        if ps_name in source:
                            updated = repl_svc.sync_config(config)
                            updated and notify_slack(config, triggering_user)
                            continue

        elif action == DELETE_PARAM_ACTION or action == DELETE_PARAMS_ACTION:
            log.info("Delete found, skipping...")
        else:
            log.info(f"Unsupported action type found! --> {action}")

    except Exception as e:
        log.exception("Caught irrecoverable error while executing.")
        title = "Figgy experienced an irrecoverable error!"
        message = f"The following error occurred in an the figgy-ssm-stream-replicator lambda. \n" \
                  f"Figgy is designed to backoff and continually retry in the face of errors. \n" \
                  f"If this appears to be a bug with figgy, please tell us by submitting a GitHub issue!" \
                  f" \n\n```{Utils.printable_exception(e)}```"

        message = SimpleSlackMessage(title=title, message=message, color=SlackColor.RED)
        slack.send_message(message)
        raise e
Exemple #4
0
def handle(event, context):
    try:
        repl_configs: List[ReplicationConfig] = repl_dao.get_all()
        for config in repl_configs:
            time.sleep(.15)  # This is to throttle PS API Calls to prevent overloading the API.
            updated = repl_svc.sync_config(config)

            if updated:
                notify_slack(config)
    except Exception as e:
        log.error(e)
        title = "Figgy experienced an irrecoverable error!"
        message=f"The following error occurred in an the *figgy-replication-syncer* lambda. " \
                f"If this appears to be a bug with figgy, please tell us by submitting a GitHub issue!" \
                f" \n\n```{Utils.printable_exception(e)}```"
        message = SimpleSlackMessage(title=title, message=message, color=SlackColor.RED)
        slack.send_message(message)
        raise e
def handle(event, context):
    try:
        param_names = ssm_dao.get_all_param_names(namespaces)
        cacheable_params = set([x for x in param_names if is_cacheable(x)])
        cached_configs: Set[ConfigItem] = cache_dao.get_active_configs()
        cached_names = set([config.name for config in cached_configs])
        missing_params: Set[str] = cacheable_params.difference(cached_names)
        names_to_delete: Set[str] = cached_names.difference(cacheable_params)

        for param in missing_params:
            log.info(f"Storing in cache: {param}")
            items: Set[ConfigItem] = cache_dao.get_items(param)
            cache_dao.put_in_cache(param)
            [cache_dao.delete(item) for item in items]  # If any dupes exist, get rid of em

        for param in names_to_delete:
            items: Set[ConfigItem] = cache_dao.get_items(param)
            sorted_items = sorted(items)
            [cache_dao.delete(item) for item in sorted_items[:-1]]  # Delete all but the most recent item.

            # Double check that item is missing before deleting in case it was just added.
            if not ssm_dao.get_parameter_value(param):
                log.info(f"Deleting from cache: {param}")
                cache_dao.mark_deleted(sorted_items[-1])

        remove_old_deleted_items()

    except Exception as e:
        log.exception("Caught irrecoverable error while executing.")
        title = "Figgy experienced an irrecoverable error!"
        message = f"The following error occurred in an the figgy-ssm-stream-replicator lambda. " \
                  f"If this appears to be a bug with figgy, please tell us by submitting a GitHub issue!" \
                  f" \n\n{Utils.printable_exception(e)}"

        message = SimpleSlackMessage(
            title=title,
            message=message,
            color=SlackColor.RED
        )
        slack.send_message(message)

        raise e
Exemple #6
0
def handle(event, context):
    records = event.get('Records', [])
    for record in records:
        s3_record = record.get("s3", {})
        bucket = s3_record.get('bucket', {}).get('name')
        key = s3_record.get('object', {}).get('key')
        download_dest = f'/tmp/{key.split("/")[-1]}'
        s3_svc.download_file(bucket=bucket,
                             obj_key=key,
                             download_destination=f'/tmp/{key.split("/")[-1]}')
        parser: CloudtrailParser = CloudtrailParser(download_dest)

        try:
            for ssm_event in parser.next_ssm_event():
                if ssm_event.is_error():
                    log.info(
                        f'Not processing event due to error Message {ssm_event.error_message} '
                        f'and Code: {ssm_event.error_code}')
                    continue

                log.info(
                    f"Got user: {ssm_event.user}, action: {ssm_event.action} for parameter(s) {ssm_event.parameters}"
                )

                if ssm_event.user.startswith(
                        'figgy'
                ) and ssm_event.user != '*****@*****.**':
                    log.info(f'Found event from figgy, not logging.')
                    continue

                for ps_name in ssm_event.parameters:
                    name = f'/{ps_name}' if not ps_name.startswith(
                        '/') else ps_name
                    matching_ns = [
                        ns for ns in FIGGY_NAMESPACES if ps_name.startswith(ns)
                    ]

                    if matching_ns:
                        log.info(
                            f"Found GET event for matching namespace: {matching_ns} and name: {name}"
                        )
                        usage_tracker.add_usage_log(name, ssm_event.user,
                                                    ssm_event.time)
                        user_cache.add_user_to_cache(
                            ssm_event.user, state=USER_CACHE_STATE_ACTIVE)
                    else:
                        log.info(
                            f'PS Name: {name} - {FIGGY_NAMESPACES} - no match found. '
                        )

        except Exception as e:
            log.exception("Caught irrecoverable error while executing.")
            message = f"The following error occurred in an the figgy-config-usage-tracker lambda. " \
                      f"If this appears to be a bug with Figgy, please tell us by submitting a GitHub issue!" \
                      f" \n\n{Utils.printable_exception(e)}"

            title = "Figgy experienced an irrecoverable error!"
            message = SimpleSlackMessage(title=title,
                                         message=message,
                                         color=SlackColor.RED)
            slack.send_message(message)
            continue
Exemple #7
0
def handle(event, context):
    global LAST_CLEANUP

    # Don't process other account's events.
    originating_account = event.get('account')
    if originating_account != ACCOUNT_ID:
        log.info(f"Received event from different account with id: {ACCOUNT_ID}. Skipping this event.")
        return

    try:
        detail = event.get('detail')
        log.info(f'Got event details: {detail}')
        ssm_event = SSMEvent(detail)

        if ssm_event.is_error():
            log.info(f'Not processing event due to error Message {ssm_event.error_message} '
                     f'and Code: {ssm_event.error_code}')
            return

        log.info(f"Got user: {ssm_event.user}, action: {ssm_event.action} for parameter(s) {ssm_event.parameters}")

        for ps_name in ssm_event.parameters:
            ps_name = f'/{ps_name}' if not ps_name.startswith('/') else ps_name
            matching_ns = [ns for ns in FIGGY_NAMESPACES if ps_name.startswith(ns)]

            if not matching_ns:
                log.info(f'PS Name: {ps_name} - is not maintained by Figgy. Skipping..')
                continue

            if ssm_event.action == DELETE_PARAM_ACTION or ssm_event.action == DELETE_PARAMS_ACTION:
                audit.put_delete_log(ssm_event.user, ssm_event.action, ps_name, timestamp=ssm_event.time)
                notify_delete(ps_name, ssm_event.user)
            elif ssm_event.action == PUT_PARAM_ACTION:
                if not ssm_event.value:
                    ssm_event.value = ssm.get_parameter_value_encrypted(ps_name)

                audit.put_audit_log(
                    ssm_event.user,
                    ssm_event.action,
                    ps_name,
                    ssm_event.value,
                    ssm_event.type,
                    ssm_event.key_id,
                    ssm_event.description,
                    ssm_event.version,
                    timestamp=ssm_event.time,
                )
            else:
                log.info(f"Unsupported action type found! --> {ssm_event.action}")

        # This will occasionally cleanup parameters with the explict value of DELETE_ME.
        # Great for testing and adding PS parameters you don't want to be restored later on.
        if time.time() - CLEANUP_INTERVAL > LAST_CLEANUP:
            log.info("Cleaning up.")
            audit.cleanup_test_logs()
            LAST_CLEANUP = time.time()

    except Exception as e:
        log.exception("Caught irrecoverable error while executing.")
        message = f"The following error occurred in an the figgy-ssm-stream-replicator lambda. " \
                  f"If this appears to be a bug with Figgy, please tell us by submitting a GitHub issue!" \
                  f" \n\n{Utils.printable_exception(e)}"

        title = "Figgy experienced an irrecoverable error!"
        message = SimpleSlackMessage(
            title=title,
            message=message,
            color=SlackColor.RED
        )
        slack.send_message(message)
        raise e
Exemple #8
0
def handle(event, context):
    global LAST_CLEANUP
    log.info(f"Event: {event}")
    data = event.get('awslogs', {}).get('data')
    bytes = base64.b64decode(data)
    decompressed = gzip.decompress(bytes)
    log.info(f'Decompressed: {decompressed}')
    if data:
        data: Dict = json.loads(decompressed)
        log.info(f'Got data: {data}')
    else:
        raise ValueError(
            f"Unable decode and decompress event data: {event.get(data)}")

    log_events = data.get('logEvents')
    log.info(f'Got events: {log_events}')
    for log_event in log_events:
        log.info(f'Processing event: {log_event}')
        message = json.loads(log_event.get('message'))

        log.info(f"Got message: {message}")
        # Don't process other account's events.
        originating_account = message.get('userIdentity', {}).get('accountId')
        if originating_account != ACCOUNT_ID:
            log.info(
                f"Received event from account {originating_account}, only processing events from account with "
                f"id: {ACCOUNT_ID}. Skipping this event.")
            continue

        try:
            ssm_event = SSMEvent(message)

            if ssm_event.is_error():
                log.info(
                    f'Not processing event due to error Message {ssm_event.error_message} '
                    f'and Code: {ssm_event.error_code}')
                continue

            log.info(
                f"Got user: {ssm_event.user}, action: {ssm_event.action} for parameter(s) {ssm_event.parameters}"
            )
            if ssm_event.user.startswith(
                    'figgy') and ssm_event.user != '*****@*****.**':
                log.info(f'Found event from figgy, not logging.')
                return

            for ps_name in ssm_event.parameters:
                name = f'/{ps_name}' if not ps_name.startswith(
                    '/') else ps_name
                matching_ns = [
                    ns for ns in FIGGY_NAMESPACES if ps_name.startswith(ns)
                ]

                if matching_ns:
                    log.info(
                        f"Found GET event for matching namespace: {matching_ns} and name: {name}"
                    )
                    usage_tracker.add_usage_log(name, ssm_event.user,
                                                ssm_event.time)
                    user_cache.add_user_to_cache(ssm_event.user,
                                                 state=USER_CACHE_STATE_ACTIVE)
                else:
                    log.info(
                        f'PS Name: {name} - {FIGGY_NAMESPACES} - no match found. '
                    )

        except Exception as e:
            log.exception("Caught irrecoverable error while executing.")
            message = f"The following error occurred in an the figgy-config-usage-tracker lambda. " \
                      f"If this appears to be a bug with Figgy, please tell us by submitting a GitHub issue!" \
                      f" \n\n{Utils.printable_exception(e)}"

            title = "Figgy experienced an irrecoverable error!"
            message = SimpleSlackMessage(title=title,
                                         message=message,
                                         color=SlackColor.RED)
            slack.send_message(message)
            continue
Exemple #9
0
def handle(event, context):
    global LAST_CLEANUP

    # Don't process other account's events.
    originating_account = event.get('account')
    if originating_account != ACCOUNT_ID:
        log.info(
            f"Received event from different account with id: {ACCOUNT_ID}. Skipping this event."
        )
        return

    try:
        log.info(f"Event: {event}")
        detail = event["detail"]
        user_arn = detail.get("userIdentity", {}).get("arn", "UserArnUnknown")
        user = user_arn.split("/")[-1:][0]
        action = detail.get("eventName")

        if 'errorMessage' in detail:
            log.info(
                f'Not processing event due to this being an error event with message: {detail["errorMessage"]}'
            )
            return

        request_params = detail.get('requestParameters', {})
        ps_names = request_params.get('names', [])
        ps_name = [request_params['name']] if 'name' in request_params else []
        ps_names = ps_names + ps_name
        event_time = detail.get('eventTime')

        # Convert to millis since epoch
        if event_time:
            event_time = int(
                datetime.strptime(event_time,
                                  "%Y-%m-%dT%H:%M:%SZ").timestamp() * 1000)
        else:
            event_time = int(time.time() * 1000)

        log.info(
            f"Got user: {user}, action: {action} for parameter(s) {ps_names}")

        for ps_name in ps_names:
            ps_name = f'/{ps_name}' if not ps_name.startswith('/') else ps_name

            if action == DELETE_PARAM_ACTION or action == DELETE_PARAMS_ACTION:
                audit.put_delete_log(user,
                                     action,
                                     ps_name,
                                     timestamp=event_time)
                notify_delete(ps_name, user)
            elif action == PUT_PARAM_ACTION:
                ps_value = request_params.get("value")
                ps_type = request_params.get("type")
                ps_description = request_params.get("description")
                ps_version = detail.get("responseElements",
                                        {}).get("version", 1)
                ps_key_id = request_params.get("keyId")

                if not ps_value:
                    ps_value = ssm.get_parameter_value(ps_name)

                audit.put_audit_log(
                    user,
                    action,
                    ps_name,
                    ps_value,
                    ps_type,
                    ps_key_id,
                    ps_description,
                    ps_version,
                    timestamp=event_time,
                )
            else:
                log.info(f"Unsupported action type found! --> {action}")

        # This will occassionally cleanup parameters with the explict value of DELETE_ME.
        # Great for testing and adding PS parameters
        # you don't want to be restored later on.
        if time.time() - CLEANUP_INTERVAL > LAST_CLEANUP:
            log.info("Cleaning up.")
            audit.cleanup_test_logs()
            LAST_CLEANUP = time.time()

    except Exception as e:
        log.error(e)
        message = f"The following error occurred in an the figgy-ssm-stream-replicator lambda. " \
                  f"If this appears to be a bug with Figgy, please tell us by submitting a GitHub issue!" \
                  f" \n\n{Utils.printable_exception(e)}"

        title = "Figgy experienced an irrecoverable error!"
        message = SimpleSlackMessage(title=title,
                                     message=message,
                                     color=SlackColor.RED)
        slack.send_message(message)
        raise e