/******************************************************************************* * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. ******************************************************************************/ package com.amazonaws.services.cloudtrail.processinglibrary.manager; import com.amazonaws.AmazonServiceException; import com.amazonaws.services.cloudtrail.processinglibrary.configuration.ProcessingConfiguration; import com.amazonaws.services.cloudtrail.processinglibrary.interfaces.ExceptionHandler; import com.amazonaws.services.cloudtrail.processinglibrary.interfaces.ProgressReporter; import com.amazonaws.services.cloudtrail.processinglibrary.model.CloudTrailSource; import com.amazonaws.services.cloudtrail.processinglibrary.model.SourceAttributeKeys; import com.amazonaws.services.cloudtrail.processinglibrary.model.internal.SourceType; import com.amazonaws.services.cloudtrail.processinglibrary.progress.BasicParseMessageInfo; import com.amazonaws.services.cloudtrail.processinglibrary.progress.BasicPollQueueInfo; import com.amazonaws.services.cloudtrail.processinglibrary.progress.ProgressState; import com.amazonaws.services.cloudtrail.processinglibrary.progress.ProgressStatus; import com.amazonaws.services.cloudtrail.processinglibrary.serializer.SourceSerializer; import com.amazonaws.services.cloudtrail.processinglibrary.utils.LibraryUtils; import com.amazonaws.services.sqs.AmazonSQS; import com.amazonaws.services.sqs.model.DeleteMessageRequest; import com.amazonaws.services.sqs.model.Message; import com.amazonaws.services.sqs.model.ReceiveMessageRequest; import com.amazonaws.services.sqs.model.ReceiveMessageResult; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.util.ArrayList; import java.util.List; /** * A convenient class to manage Amazon SQS Service related operations. */ public class SqsManager { private static final Log logger = LogFactory.getLog(SqsManager.class); private static final String ALL_ATTRIBUTES = "All"; /** * Pull 10 messages from SQS queue at a time. */ private static final int DEFAULT_SQS_MESSAGE_SIZE_LIMIT = 10; /** * Enable long pulling, wait at most 20 seconds for a incoming messages for a single poll queue request. */ private static final int DEFAULT_WAIT_TIME_SECONDS = 20; /** * An instance of ProcessingConfiguration. */ private ProcessingConfiguration config; /** * An instance of AmazonSQSClient. */ private AmazonSQS sqsClient; /** * An instance of SourceSerializer. */ private SourceSerializer sourceSerializer; /** * User implementation of ExceptionHandler, used to handle error case. */ private ExceptionHandler exceptionHandler; /** * User implementation of ProgressReporter, used to report progress. */ private ProgressReporter progressReporter; /** * SqsManager constructor. * * @param sqsClient used to poll message from SQS. * @param config user provided ProcessingConfiguration. * @param exceptionHandler user provided exceptionHandler. * @param progressReporter user provided progressReporter. * @param sourceSerializer user provided SourceSerializer. */ public SqsManager(AmazonSQS sqsClient, ProcessingConfiguration config, ExceptionHandler exceptionHandler, ProgressReporter progressReporter, SourceSerializer sourceSerializer) { this.config = config; this.exceptionHandler = exceptionHandler; this.progressReporter = progressReporter; this.sqsClient = sqsClient; this.sourceSerializer = sourceSerializer; validate(); } /** * Poll SQS queue for incoming messages, filter them, and return a list of SQS Messages. * * @return a list of SQS messages. */ public List<Message> pollQueue() { boolean success = false; ProgressStatus pollQueueStatus = new ProgressStatus(ProgressState.pollQueue, new BasicPollQueueInfo(0, success)); final Object reportObject = progressReporter.reportStart(pollQueueStatus); ReceiveMessageRequest request = new ReceiveMessageRequest().withAttributeNames(ALL_ATTRIBUTES); request.setQueueUrl(config.getSqsUrl()); request.setVisibilityTimeout(config.getVisibilityTimeout()); request.setMaxNumberOfMessages(DEFAULT_SQS_MESSAGE_SIZE_LIMIT); request.setWaitTimeSeconds(DEFAULT_WAIT_TIME_SECONDS); List<Message> sqsMessages = new ArrayList<Message>(); try { ReceiveMessageResult result = sqsClient.receiveMessage(request); sqsMessages = result.getMessages(); logger.info("Polled " + sqsMessages.size() + " sqs messages from " + config.getSqsUrl()); success = true; } catch (AmazonServiceException e) { LibraryUtils.handleException(exceptionHandler, pollQueueStatus, e, "Failed to poll sqs message."); } finally { LibraryUtils.endToProcess(progressReporter, success, pollQueueStatus, reportObject); } return sqsMessages; } /** * Given a list of raw SQS message parse each of them, and return a list of CloudTrailSource. * * @param sqsMessages list of SQS messages. * @return list of CloudTrailSource. */ public List<CloudTrailSource> parseMessage(List<Message> sqsMessages) { List<CloudTrailSource> sources = new ArrayList<>(); for (Message sqsMessage : sqsMessages) { boolean parseMessageSuccess = false; ProgressStatus parseMessageStatus = new ProgressStatus(ProgressState.parseMessage, new BasicParseMessageInfo(sqsMessage, parseMessageSuccess)); final Object reportObject = progressReporter.reportStart(parseMessageStatus); CloudTrailSource ctSource = null; try { ctSource = sourceSerializer.getSource(sqsMessage); if (containsCloudTrailLogs(ctSource)) { sources.add(ctSource); parseMessageSuccess = true; } } catch (Exception e) { LibraryUtils.handleException(exceptionHandler, parseMessageStatus, e, "Failed to parse sqs message."); } finally { if (containsCloudTrailValidationMessage(ctSource) || shouldDeleteMessageUponFailure(parseMessageSuccess)) { deleteMessageFromQueue(sqsMessage, new ProgressStatus(ProgressState.deleteMessage, new BasicParseMessageInfo(sqsMessage, false))); } LibraryUtils.endToProcess(progressReporter, parseMessageSuccess, parseMessageStatus, reportObject); } } return sources; } /** * Delete a message from the SQS queue that you specified in the configuration file. * * @param sqsMessage the {@link Message} that you want to delete. * @param progressStatus {@link ProgressStatus} tracks the start and end status. * */ public void deleteMessageFromQueue(Message sqsMessage, ProgressStatus progressStatus) { final Object reportObject = progressReporter.reportStart(progressStatus); boolean deleteMessageSuccess = false; try { sqsClient.deleteMessage(new DeleteMessageRequest(config.getSqsUrl(), sqsMessage.getReceiptHandle())); deleteMessageSuccess = true; } catch (AmazonServiceException e) { LibraryUtils.handleException(exceptionHandler, progressStatus, e, "Failed to delete sqs message."); } LibraryUtils.endToProcess(progressReporter, deleteMessageSuccess, progressStatus, reportObject); } /** * Check whether <code>ctSource</code> contains CloudTrail log files. * @param ctSource a {@link CloudTrailSource}. * @return <code>true</code> if contains CloudTrail log files, <code>false</code> otherwise. * */ private boolean containsCloudTrailLogs(CloudTrailSource ctSource) { SourceType sourceType = SourceType.valueOf(ctSource.getSourceAttributes().get(SourceAttributeKeys.SOURCE_TYPE.getAttributeKey())); switch(sourceType) { case CloudTrailLog: return true; case CloudTrailValidationMessage: logger.warn(String.format("Delete CloudTrail validation message: %s.", ctSource.toString())); return false; case Other: default: logger.info(String.format("Skip Non CloudTrail Log File: %s.", ctSource.toString())); return false; } } /** * Check whether <code>ctSource</code> contains CloudTrail validation message. * @param ctSource a {@link CloudTrailSource}. * @return <code>true</code> if contains CloudTrail validation message, <code>false</code> otherwise. * */ private boolean containsCloudTrailValidationMessage(CloudTrailSource ctSource) { if (ctSource == null){ return false; } SourceType sourceType = SourceType.valueOf(ctSource.getSourceAttributes().get(SourceAttributeKeys.SOURCE_TYPE.getAttributeKey())); return sourceType == SourceType.CloudTrailValidationMessage; } /** * Delete the message if the CPL failed to process the message and {@link ProcessingConfiguration#isDeleteMessageUponFailure()} * is enabled. * @param processSuccess Indicates whether the CPL processing is successful, such as parsing message, or * consuming the events in the CloudTrail log file. * @return <code>true</code> if the message is removable. Otherwise, <code>false</code>. */ public boolean shouldDeleteMessageUponFailure(boolean processSuccess) { return !processSuccess && config.isDeleteMessageUponFailure(); } /** * Convenient function to validate input. */ private void validate() { LibraryUtils.checkArgumentNotNull(config, "configuration is null"); LibraryUtils.checkArgumentNotNull(exceptionHandler, "exceptionHandler is null"); LibraryUtils.checkArgumentNotNull(progressReporter, "progressReporter is null"); LibraryUtils.checkArgumentNotNull(sqsClient, "sqsClient is null"); LibraryUtils.checkArgumentNotNull(sourceSerializer, "sourceSerializer is null"); } }