/* CloudTrail Viewer, is a Java desktop application for reading AWS CloudTrail logs files. Copyright (C) 2017 Mark P. Haskins This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package io.haskins.java.cloudtrailviewer.service; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.GetObjectRequest; import com.amazonaws.services.s3.model.S3Object; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import io.haskins.java.cloudtrailviewer.controller.components.StatusBarController; import io.haskins.java.cloudtrailviewer.filter.CompositeFilter; import io.haskins.java.cloudtrailviewer.model.aws.AwsAccount; import io.haskins.java.cloudtrailviewer.model.event.Event; import io.haskins.java.cloudtrailviewer.service.listener.EventServiceListener; import io.haskins.java.cloudtrailviewer.utils.AwsService; import io.haskins.java.cloudtrailviewer.utils.EventUtils; import javafx.concurrent.Task; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.io.*; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.Scanner; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.GZIPInputStream; import java.util.zip.ZipException; /** * Service responsible for handling CloudTrail events. * <p> * Created by markhaskins on 04/01/2017. */ @Service public class EventService { public static final int FILE_TYPE_LOCAL = 1; public static final int FILE_TYPE_S3 = 2; private final static int BUFFER_SIZE = 32; private final static Logger LOGGER = Logger.getLogger("CloudTrail"); private final GeoService geoService; private final AccountService accountDao; private final StatusBarController statusBarController; private final AwsService awsService; private final List<EventServiceListener> listeners = new ArrayList<>(); private final List<Event> eventDb = new ArrayList<>(); @Autowired public EventService( AccountService accountDao, GeoService geoService, StatusBarController statusBarController, AwsService awsService) { this.accountDao = accountDao; this.geoService = geoService; this.awsService = awsService; this.statusBarController = statusBarController; this.listeners.add(statusBarController); } public void registerAsListener(EventServiceListener l) { listeners.add(l); } public void loadFiles(List<String> filenames, final CompositeFilter filters, int file_type) { Task<Void> task = new Task<Void>() { @Override protected Void call() throws Exception { AwsAccount activeAccount = null; AmazonS3 s3Client = null; if (file_type == FILE_TYPE_S3) { activeAccount = awsService.getActiveAccount(accountDao); s3Client = awsService.getS3Client(activeAccount); } int count = 0; for (String filename : filenames) { count++; String message = "Processing file " + count + " of " + filenames.size(); updateMessage(message); if (file_type == FILE_TYPE_S3) { try (InputStream stream = loadEventFromS3(s3Client, activeAccount.getBucket(), filename)) { processStream(stream, filters); } catch (Exception ioe) { LOGGER.log(Level.WARNING, "Failed to load file : " + filename, ioe); } } else if (file_type == FILE_TYPE_LOCAL) { try (InputStream stream = loadEventFromLocalFile(filename)) { processStream(stream, filters); } catch (Exception ioe) { LOGGER.log(Level.WARNING, "Failed to load file : " + filename, ioe); } } } return null; } @Override protected void succeeded() { super.succeeded(); updateMessage(""); for (EventServiceListener l : listeners) { l.finishedLoading(false); } } }; statusBarController.message.textProperty().bind(task.messageProperty()); new Thread(task).start(); } void injectEvents(EventServiceListener l) { l.newEvents(eventDb); } public void clearEvents() { eventDb.clear(); for (EventServiceListener l : listeners) { l.clearEvents(); } } public List<Event> getAllEvents() { return this.eventDb; } //////////////////////////////////////////////////////////////////////////// ///// private methods //////////////////////////////////////////////////////////////////////////// private static InputStream loadEventFromLocalFile(final String file) throws IOException { byte[] encoded = Files.readAllBytes(Paths.get(file)); return new ByteArrayInputStream(encoded); } private InputStream loadEventFromS3(AmazonS3 s3Client, String bucketName, final String key) { S3Object s3Object = s3Client.getObject(new GetObjectRequest(bucketName, key)); return s3Object.getObjectContent(); } private String uncompress(InputStream stream) { StringBuilder json = new StringBuilder(); try (GZIPInputStream gzis = new GZIPInputStream(stream, BUFFER_SIZE)) { try (BufferedReader bf = new BufferedReader(new InputStreamReader(gzis, "UTF-8"))) { String line; while ((line = bf.readLine()) != null) { json.append(line); } } } catch (ZipException ex) { json.append(loadUncompressedFile(stream)); } catch (UnsupportedEncodingException ex) { LOGGER.log(Level.WARNING, "File encoding not recognised : ", ex); } catch (IOException ex) { LOGGER.log(Level.WARNING, "Problem uncompressing file data : ", ex); } return json.toString(); } private String loadUncompressedFile(InputStream stream) { StringBuilder json = new StringBuilder(); Scanner scanner = new Scanner(stream, "UTF-8"); while (scanner.hasNext()) { String line = scanner.next(); json.append(line.replaceAll("(\\r|\\n|\\t)", "")); } // check if the first character is a { otherwise add one String firstChars = json.substring(0, 2); if (firstChars.equalsIgnoreCase("Re")) { json.insert(0, "{\""); } else if (firstChars.equalsIgnoreCase("\"R")) { json.insert(0, "{"); } return json.toString(); } private List<Event> createEvents(String json_string) { Gson g = new Gson(); List<Event> events = new ArrayList<>(); JsonObject jsonObject = new JsonParser().parse(json_string).getAsJsonObject(); JsonArray records = (JsonArray) jsonObject.get("Records"); for (Object record : records) { try { JsonObject obj = (JsonObject) record; Event e = g.fromJson(obj, Event.class); events.add(e); } catch (Exception e) { LOGGER.log(Level.WARNING, "Create Event from JSON : ", e); } } return events; } private void processStream(InputStream stream, CompositeFilter filter) { List<Event> events = createEvents(uncompress(stream)); for (Event event : events) { geoService.populateGeoData(event); EventUtils.addTimestamp(event); if (filter.passes(event)) { eventDb.add(event); for (EventServiceListener l : listeners) { l.newEvent(event); } } } } }