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
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
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
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
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
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