Transform http and ssh logs into audit logs
Tranformation of SSH and HTTP logs into auditlogs will permit
to analyse audit log data even before the plugin was enabled.
Feature: Issue 10307
Change-Id: I404b17b4e18d6f0aa648bd13a471e1bf8847c04f
diff --git a/BUILD b/BUILD
index 2dec1e5..db99a0a 100644
--- a/BUILD
+++ b/BUILD
@@ -13,6 +13,7 @@
"Gerrit-PluginName: audit-sl4j",
"Gerrit-ReloadMode: reload",
"Gerrit-Module: com.googlesource.gerrit.plugins.auditsl4j.Module",
+ "Gerrit-SshModule: com.googlesource.gerrit.plugins.auditsl4j.SshModule",
"Implementation-Title: Gerrit Audit provider for SLF4J",
"Implementation-URL: https://gerrit.googlesource.com/plugins/audit-sl4j/",
],
diff --git a/README.md b/README.md
index d4c4c19..7145605 100644
--- a/README.md
+++ b/README.md
@@ -103,3 +103,11 @@
}
}
```
+
+## Import old logs
+
+The makes available an ssh command to transform `http_logs` and `ssh_logs` into Audit Logs:
+
+```bash
+ssh -p 29418 admin@localhost audit-sl4j transform --from 2019-01-23 --until 2019-01-24
+```
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditFormatRenderer.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditFormatRenderer.java
index 614b862..2507acc 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditFormatRenderer.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditFormatRenderer.java
@@ -15,12 +15,13 @@
package com.googlesource.gerrit.plugins.auditsl4j;
import com.google.gerrit.server.AuditEvent;
-import com.google.gerrit.server.audit.SshAuditEvent;
import java.util.Optional;
public interface AuditFormatRenderer {
String render(AuditEvent auditEvent);
+ String render(AuditEvent auditEvent, TransformableAuditLogType type);
+
Optional<String> headers();
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRecord.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRecord.java
index 85c4be5..794e820 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRecord.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRecord.java
@@ -27,4 +27,11 @@
this.type = eventClass.substring(eventClass.lastIndexOf('.') + 1);
this.event = event;
}
+
+ public AuditRecord(AuditEvent event, TransformableAuditLogType type) {
+ super();
+
+ this.type = type.name();
+ this.event = event;
+ }
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRendererToCsv.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRendererToCsv.java
index 0dbbf74..4c162be 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRendererToCsv.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRendererToCsv.java
@@ -89,6 +89,11 @@
}
@Override
+ public String render(AuditEvent auditEvent, TransformableAuditLogType type) {
+ return render(auditEvent);
+ }
+
+ @Override
public Optional<String> headers() {
return Optional.of(
"EventId | EventTS | SessionId | User | Protocol data | Action | Parameters | Result | StartTS | Elapsed");
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRendererToJson.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRendererToJson.java
index b0c8b47..5a21054 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRendererToJson.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRendererToJson.java
@@ -17,9 +17,9 @@
import com.google.common.collect.ListMultimap;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.AccessPath;
+import com.google.gerrit.server.AuditEvent;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.OutputFormat;
-import com.google.gerrit.server.AuditEvent;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
@@ -74,6 +74,11 @@
}
@Override
+ public String render(AuditEvent auditEvent, TransformableAuditLogType type) {
+ return gson.toJson(new AuditRecord(auditEvent, type));
+ }
+
+ @Override
public Optional<String> headers() {
return Optional.empty();
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditUser.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditUser.java
new file mode 100644
index 0000000..2e6dd8a
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditUser.java
@@ -0,0 +1,42 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License 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.googlesource.gerrit.plugins.auditsl4j;
+
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.GroupMembership;
+import java.util.Optional;
+
+public class AuditUser extends CurrentUser {
+ String username;
+
+ @Override
+ public GroupMembership getEffectiveGroups() {
+ return null;
+ }
+
+ @Override
+ public Object getCacheKey() {
+ return null;
+ }
+
+ @Override
+ public Optional<String> getUserName() {
+ return Optional.of(username);
+ }
+
+ public void setUserName(String username) {
+ this.username = username;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditWriter.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditWriter.java
index 1da9b2a..32866a0 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditWriter.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditWriter.java
@@ -14,7 +14,6 @@
package com.googlesource.gerrit.plugins.auditsl4j;
-import com.google.gerrit.server.audit.HttpAuditEvent;
import com.google.inject.ImplementedBy;
@ImplementedBy(AuditWriterToLogger.class)
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditWriterToLogger.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditWriterToLogger.java
index 98f64e2..51d4629 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditWriterToLogger.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditWriterToLogger.java
@@ -14,7 +14,6 @@
package com.googlesource.gerrit.plugins.auditsl4j;
-import com.google.gerrit.server.audit.SshAuditEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditWriterToStringList.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditWriterToStringList.java
index 6aab77b..bb24056 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditWriterToStringList.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditWriterToStringList.java
@@ -14,7 +14,6 @@
package com.googlesource.gerrit.plugins.auditsl4j;
-import com.google.gerrit.server.audit.HttpAuditEvent;
import com.google.inject.Singleton;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/LoggerAudit.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/LoggerAudit.java
index 9d14f18..e09035e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/LoggerAudit.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/LoggerAudit.java
@@ -27,7 +27,7 @@
public static final String AUDIT_LOGGER_NAME = LoggerAudit.class.getName();
@Inject
- LoggerAudit(AuditWriter auditWriter, AuditFormatRenderer auditRenderer) {
+ public LoggerAudit(AuditWriter auditWriter, AuditFormatRenderer auditRenderer) {
this.auditWriter = auditWriter;
this.auditRenderer = auditRenderer;
@@ -36,7 +36,15 @@
@Override
public void onAuditableAction(AuditEvent auditEvent) {
- String auditString = auditRenderer.render(auditEvent);
+ String auditString = getAuditString(auditEvent);
auditWriter.write(auditString);
}
+
+ public String getAuditString(AuditEvent auditEvent) {
+ return auditRenderer.render(auditEvent);
+ }
+
+ public String getAuditString(AuditEvent auditEvent, TransformableAuditLogType type) {
+ return auditRenderer.render(auditEvent, type);
+ }
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/SshModule.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/SshModule.java
new file mode 100644
index 0000000..125b2fc
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/SshModule.java
@@ -0,0 +1,24 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License 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.googlesource.gerrit.plugins.auditsl4j;
+
+import com.google.gerrit.sshd.PluginCommandModule;
+
+public class SshModule extends PluginCommandModule {
+ @Override
+ protected void configureCommands() {
+ command(TransformLogsCommand.class);
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/TransformLogsCommand.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/TransformLogsCommand.java
new file mode 100644
index 0000000..6c5b463
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/TransformLogsCommand.java
@@ -0,0 +1,149 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License 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.googlesource.gerrit.plugins.auditsl4j;
+
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.auditsl4j.logsource.HTTPLog;
+import com.googlesource.gerrit.plugins.auditsl4j.logsource.SSHLog;
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.zip.GZIPInputStream;
+import org.kohsuke.args4j.Option;
+
+@CommandMetaData(name = "transform", description = "Transform ssh and http logs into audit logs")
+public class TransformLogsCommand extends SshCommand {
+
+ private LoggerAudit loggerAudit;
+ private final SitePaths sitePaths;
+
+ @Inject
+ public TransformLogsCommand(SitePaths sitePaths, LoggerAudit loggerAudit) {
+ this.sitePaths = sitePaths;
+ this.loggerAudit = loggerAudit;
+ }
+
+ SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
+
+ @Option(name = "--from", usage = "transform logs from <YYYY-MM-DD>")
+ private String from;
+
+ @Option(name = "--until", usage = "transform logs until <YYYY-MM-DD>")
+ private String until;
+
+ @Override
+ public void run() {
+
+ Date dateFrom;
+ try {
+ dateFrom = format.parse(from);
+ } catch (Exception e) {
+ stderr.print("Invalid 'from' format: " + from + ", expected format <YYYY-MM-DD>");
+ return;
+ }
+ Date dateUntil;
+ try {
+ dateUntil = format.parse(until);
+ } catch (Exception e) {
+ stderr.print("Invalid 'until' format: " + until + ", expected format <YYYY-MM-DD>");
+ return;
+ }
+
+ if (dateFrom.after(dateUntil)) {
+ stderr.print("'from' cannot be after 'until'");
+ return;
+ }
+
+ Date currentDate = dateFrom;
+ while (currentDate.compareTo(dateUntil) <= 0) {
+ transformHttpdLogs(format.format(currentDate));
+ transformSshdLogs(format.format(currentDate));
+
+ currentDate = getTomorrowDate(currentDate);
+ }
+
+ stdout.print("Transformed HTTP and SSH logs from " + from + " until " + until + "!\n");
+ }
+
+ private Date getTomorrowDate(Date currentDate) {
+ Calendar c = Calendar.getInstance();
+ c.setTime(currentDate);
+ c.add(Calendar.DAY_OF_MONTH, 1);
+ return c.getTime();
+ }
+
+ private void transformSshdLogs(String currentDateString) {
+ transformLogs(currentDateString, SSHLog.logFilenameBase(), SSHLog::createFromLog);
+ }
+
+ private void transformHttpdLogs(String currentDateString) {
+ transformLogs(currentDateString, HTTPLog.logFilenameBase(), HTTPLog::createFromLog);
+ }
+
+ private void transformLogs(
+ String currentDateString,
+ String fileType,
+ Function<String, Optional<? extends TransformableLog>> createTransformable) {
+ // Log format example: httpd_log.2019-01-19.gz
+ String logFileName = sitePaths.logs_dir + "/" + fileType + "." + currentDateString + ".gz";
+ String auditLogFileName = sitePaths.logs_dir + "/audit_log." + currentDateString + ".log";
+
+ stdout.print("Transforming: " + logFileName + " => " + auditLogFileName + " ...\n");
+ stdout.flush();
+
+ try {
+ GZIPInputStream gzis = new GZIPInputStream(new FileInputStream(logFileName));
+ BufferedReader input = new BufferedReader(new InputStreamReader(gzis));
+
+ PrintWriter pw =
+ new PrintWriter(
+ Files.newBufferedWriter(
+ Paths.get(auditLogFileName),
+ StandardOpenOption.CREATE,
+ StandardOpenOption.WRITE,
+ StandardOpenOption.APPEND));
+
+ input
+ .lines()
+ .map(createTransformable)
+ .map(
+ maybeTransformableLog ->
+ maybeTransformableLog.flatMap(
+ transformableLog -> transformableLog.toAuditLog(loggerAudit)))
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .forEach(pw::println);
+ pw.flush();
+ } catch (FileNotFoundException fnfe) {
+ stderr.print("Cannot find '" + logFileName + "'. Skipping!\n");
+ } catch (IOException e) {
+ stderr.print("Error: " + e.getMessage() + "!\n");
+ }
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/TransformableAuditLogType.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/TransformableAuditLogType.java
new file mode 100644
index 0000000..8a2b589
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/TransformableAuditLogType.java
@@ -0,0 +1,21 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License 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.googlesource.gerrit.plugins.auditsl4j;
+
+public enum TransformableAuditLogType {
+ HttpAuditEvent,
+ ExtendedHttpAuditEvent,
+ SshAuditEvent
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/TransformableLog.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/TransformableLog.java
new file mode 100644
index 0000000..8c07866
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/TransformableLog.java
@@ -0,0 +1,21 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License 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.googlesource.gerrit.plugins.auditsl4j;
+
+import java.util.Optional;
+
+public interface TransformableLog {
+ Optional<String> toAuditLog(LoggerAudit loggerAudit);
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/logsource/HTTPLog.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/logsource/HTTPLog.java
new file mode 100644
index 0000000..781e8bc
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/logsource/HTTPLog.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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.googlesource.gerrit.plugins.auditsl4j.logsource;
+
+import static com.google.gerrit.server.AuditEvent.UNKNOWN_SESSION_ID;
+
+import com.google.gerrit.server.AccessPath;
+import com.google.gerrit.server.audit.HttpAuditEvent;
+import com.googlesource.gerrit.plugins.auditsl4j.AuditUser;
+import com.googlesource.gerrit.plugins.auditsl4j.LoggerAudit;
+import com.googlesource.gerrit.plugins.auditsl4j.TransformableAuditLogType;
+import com.googlesource.gerrit.plugins.auditsl4j.TransformableLog;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class HTTPLog implements TransformableLog {
+ private static final Logger log = LoggerFactory.getLogger(HTTPLog.class);
+ private String ip;
+ private String user;
+ private String method;
+ private String timestamp;
+ private String resource;
+ private String protocol;
+ private Integer status;
+ private String contentLength;
+ private String referrer;
+ private String userAgent;
+
+ public HTTPLog(
+ String ip,
+ String user,
+ String timestamp,
+ String method,
+ String resource,
+ String protocol,
+ Integer status,
+ String contentLength,
+ String referrer,
+ String userAgent) {
+ this.ip = ip;
+ this.user = user;
+ this.method = method;
+ this.timestamp = timestamp;
+ this.resource = resource;
+ this.protocol = protocol;
+ this.status = status;
+ this.contentLength = contentLength;
+ this.referrer = referrer;
+ this.userAgent = userAgent;
+ }
+
+ public static Optional<HTTPLog> createFromLog(String line) {
+ // HTTP log example:
+ // 104.32.164.100 - - [24/Jan/2019:00:00:03 +0000] "GET /plugins/events-log/ HTTP/1.1" 404 9 -
+ // "Apache-HttpClient/4.5.3 (Java/1.8.0_191)"
+ Matcher a =
+ Pattern.compile(
+ "^(?<ip>.*?)\\s-\\s(?<user>.*?)\\s\\["
+ + "(?<timestamp>.*?)\\]\\s\""
+ + "(?<method>\\w+)\\s(?<resource>.*?)\\s(?<protocol>.*?)\"\\s"
+ + "(?<status>\\d+)\\s(?<contentLength>\\d+|-)\\s(?<referrer>.*?)\\s(?<userAgent>.*?)$")
+ .matcher(line);
+ if (a.matches()) {
+ try {
+ return Optional.of(
+ new HTTPLog(
+ a.group("ip"),
+ a.group("user"),
+ a.group("timestamp"),
+ a.group("method"),
+ a.group("resource"),
+ a.group("protocol"),
+ Integer.parseInt(a.group("status")),
+ a.group("contentLength"),
+ a.group("referrer"),
+ a.group("userAgent")));
+ } catch (Exception e) {
+ log.error("Something wrong while parsing line: " + line);
+ }
+ } else {
+ log.error("Can't extract any info from line: " + line);
+ }
+ return Optional.empty();
+ }
+
+ private AuditUser getAuditUser() {
+ AuditUser au = new AuditUser();
+ au.setUserName(this.user);
+ au.setAccessPath(getAccessPath());
+ return au;
+ }
+
+ private AccessPath getAccessPath() {
+ Matcher a = Pattern.compile("^\"git.*?").matcher(this.userAgent);
+ return a.matches() ? AccessPath.GIT : AccessPath.REST_API;
+ }
+
+ public Optional<Long> getWhen() {
+ SimpleDateFormat format = new SimpleDateFormat("dd/MMM/yyyy:hh:mm:ss Z");
+ try {
+ return Optional.of(format.parse(this.timestamp).getTime());
+ } catch (ParseException pe) {
+ log.error(
+ "Can't parse timestamp: '" + this.timestamp + "'. Error message: " + pe.getMessage());
+ }
+ return Optional.empty();
+ }
+
+ public Optional<String> toAuditLog(LoggerAudit loggerAudit) {
+ return getWhen()
+ .map(
+ when ->
+ new HttpAuditEvent(
+ UNKNOWN_SESSION_ID,
+ this.getAuditUser(),
+ this.resource,
+ when,
+ null,
+ this.method,
+ null,
+ this.status,
+ null))
+ .map(
+ httpAuditEvent -> {
+ AuditUser au = getAuditUser();
+ if (au.getAccessPath() == AccessPath.REST_API) {
+ return loggerAudit.getAuditString(
+ httpAuditEvent, TransformableAuditLogType.ExtendedHttpAuditEvent);
+ } else {
+ return loggerAudit.getAuditString(
+ httpAuditEvent, TransformableAuditLogType.HttpAuditEvent);
+ }
+ });
+ }
+
+ public static String logFilenameBase() {
+ return "httpd_log";
+ }
+
+ public String getIp() {
+ return ip;
+ }
+
+ public String getUser() {
+ return user;
+ }
+
+ public String getMethod() {
+ return method;
+ }
+
+ public String getTimestamp() {
+ return timestamp;
+ }
+
+ public String getResource() {
+ return resource;
+ }
+
+ public String getProtocol() {
+ return protocol;
+ }
+
+ public Integer getStatus() {
+ return status;
+ }
+
+ public String getContentLength() {
+ return contentLength;
+ }
+
+ public String getReferrer() {
+ return referrer;
+ }
+
+ public String getUserAgent() {
+ return userAgent;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/logsource/SSHLog.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/logsource/SSHLog.java
new file mode 100644
index 0000000..03d9bed
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/logsource/SSHLog.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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.googlesource.gerrit.plugins.auditsl4j.logsource;
+
+import com.google.gerrit.server.AccessPath;
+import com.google.gerrit.server.audit.SshAuditEvent;
+import com.googlesource.gerrit.plugins.auditsl4j.AuditUser;
+import com.googlesource.gerrit.plugins.auditsl4j.LoggerAudit;
+import com.googlesource.gerrit.plugins.auditsl4j.TransformableAuditLogType;
+import com.googlesource.gerrit.plugins.auditsl4j.TransformableLog;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SSHLog implements TransformableLog {
+ private static final Logger log = LoggerFactory.getLogger(SSHLog.class);
+
+ private String user;
+ private String session;
+ private String timestamp;
+ private String accountId;
+ private String command;
+ private String waitTime;
+ private String execTime;
+ private String result;
+
+ public SSHLog(
+ String timestamp,
+ String session,
+ String user,
+ String accountId,
+ String command,
+ String waitTime,
+ String execTime,
+ String result) {
+ this.user = user;
+ this.session = session;
+ this.timestamp = timestamp;
+ this.accountId = accountId;
+ this.command = command;
+ this.waitTime = waitTime;
+ this.execTime = execTime;
+ this.result = result;
+ }
+
+ public static Optional<SSHLog> createFromLog(String line) {
+ Matcher authCommand =
+ Pattern.compile(
+ "^\\[(?<timestamp>.*?)\\]\\s(?<session>.*?)\\s"
+ + "(?<user>.*?)\\s(?<accountId>.*?)\\s(?<command>LOGOUT|LOGIN)(:?\\sFROM.*?)?$")
+ .matcher(line);
+
+ Matcher authFailure =
+ Pattern.compile(
+ "^\\[(?<timestamp>.*?)\\]\\s(?<session>.*?)\\s(?<user>.*?)\\s(?<command>AUTH FAILURE)(:?\\sFROM.*?)?$")
+ .matcher(line);
+
+ Matcher nonAuthCommand =
+ Pattern.compile(
+ "^\\[(?<timestamp>.*?)\\]\\s(?<session>.*?)\\s(?<user>.*?)\\s(?<accountId>.*?)\\s(?<command>.*?)(\\s(?<waitTime>\\d+ms)\\s(?<execTime>\\d+ms)\\s(?<result>.*?))?$")
+ .matcher(line);
+
+ if (authCommand.matches()) {
+ try {
+ return Optional.of(
+ new SSHLog(
+ authCommand.group("timestamp"),
+ authCommand.group("session"),
+ authCommand.group("user"),
+ authCommand.group("accountId"),
+ authCommand.group("command"),
+ null,
+ null,
+ "0"));
+ } catch (Exception e) {
+ log.error("Auth command match, but something wrong while parsing line: " + line);
+ }
+ } else if (authFailure.matches()) {
+ try {
+ return Optional.of(
+ new SSHLog(
+ authFailure.group("timestamp"),
+ authFailure.group("session"),
+ authFailure.group("user"),
+ null,
+ authFailure.group("command"),
+ null,
+ null,
+ "0"));
+ } catch (Exception e) {
+ log.error("Auth failure command match, but something wrong while parsing line: " + line);
+ }
+
+ } else if (nonAuthCommand.matches()) {
+ try {
+ return Optional.of(
+ new SSHLog(
+ nonAuthCommand.group("timestamp"),
+ nonAuthCommand.group("session"),
+ nonAuthCommand.group("user"),
+ nonAuthCommand.group("accountId"),
+ nonAuthCommand.group("command"),
+ nonAuthCommand.group("waitTime"),
+ nonAuthCommand.group("execTime"),
+ nonAuthCommand.group("result") != null ? nonAuthCommand.group("result") : "0"));
+ } catch (Exception e) {
+ log.error("Non Auth command match, but something wrong while parsing line: " + line);
+ }
+ } else {
+ log.error("Can't extract any info from line: " + line);
+ }
+ return Optional.empty();
+ }
+
+ private AuditUser getAuditUser() {
+ AuditUser au = new AuditUser();
+ au.setUserName(this.user);
+ au.setAccessPath(AccessPath.SSH_COMMAND);
+ return au;
+ }
+
+ private Optional<Long> getWhen() {
+ // Timestamp format example: 2019-01-23 12:44:04,723 +0100
+ SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS Z");
+ try {
+ return Optional.of(format.parse(this.timestamp).getTime());
+ } catch (ParseException pe) {
+ log.error(
+ "Can't parse timestamp: '" + this.timestamp + "'. Error message: " + pe.getMessage());
+ }
+ return Optional.empty();
+ }
+
+ public Optional<String> toAuditLog(LoggerAudit loggerAudit) {
+ return getWhen()
+ .map(
+ when ->
+ new SshAuditEvent(
+ this.session, getAuditUser(), this.command, when, null, this.result))
+ .map(
+ sshAuditEvent ->
+ loggerAudit.getAuditString(sshAuditEvent, TransformableAuditLogType.SshAuditEvent));
+ }
+
+ public static String logFilenameBase() {
+ return "sshd_log";
+ }
+
+ public String getUser() {
+ return user;
+ }
+
+ public String getSession() {
+ return session;
+ }
+
+ public String getTimestamp() {
+ return timestamp;
+ }
+
+ public String getAccountId() {
+ return accountId;
+ }
+
+ public String getCommand() {
+ return command;
+ }
+
+ public String getWaitTime() {
+ return waitTime;
+ }
+
+ public String getExecTime() {
+ return execTime;
+ }
+
+ public String getResult() {
+ return result;
+ }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/auditsl4j/logsource/HttpLogTest.java b/src/test/java/com/googlesource/gerrit/plugins/auditsl4j/logsource/HttpLogTest.java
new file mode 100644
index 0000000..756c5cb
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/auditsl4j/logsource/HttpLogTest.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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.googlesource.gerrit.plugins.auditsl4j.logsource;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.googlesource.gerrit.plugins.auditsl4j.AuditRendererToJson;
+import com.googlesource.gerrit.plugins.auditsl4j.AuditWriterToStringList;
+import com.googlesource.gerrit.plugins.auditsl4j.LoggerAudit;
+import java.util.Optional;
+import org.junit.Test;
+
+public class HttpLogTest {
+
+ LoggerAudit loggerAudit =
+ new LoggerAudit(new AuditWriterToStringList(), new AuditRendererToJson());
+
+ @Test
+ public void succesullyParseHttpLog() {
+ String ip = "104.32.164.100";
+ String user = "myUser";
+ String method = "GET";
+ String timestamp = "24/Jan/2019:00:00:03 +0000";
+ String resource = "/plugins/events-log/";
+ String protocol = "HTTP/1.1";
+ Integer status = 404;
+ String contentLenght = "9";
+ String referrer = "ciccio";
+ String userAgent = "\"Apache-HttpClient/4.5.3 (Java/1.8.0_191)\"";
+ // 104.32.164.100 - - [24/Jan/2019:00:00:03 +0000] "GET /plugins/events-log/ HTTP/1.1" 404 9 -
+ // "Apache-HttpClient/4.5.3 (Java/1.8.0_191)"
+ String logLine =
+ String.format(
+ "%s - %s [%s] \"%s %s %s\" %d %s %s %s",
+ ip,
+ user,
+ timestamp,
+ method,
+ resource,
+ protocol,
+ status,
+ contentLenght,
+ referrer,
+ userAgent);
+
+ HTTPLog expected =
+ new HTTPLog(
+ ip,
+ user,
+ timestamp,
+ method,
+ resource,
+ protocol,
+ status,
+ contentLenght,
+ referrer,
+ userAgent);
+ Optional<HTTPLog> maybeHTTPLog = HTTPLog.createFromLog(logLine);
+ assertTrue("Didn't create HTTPLog", maybeHTTPLog.isPresent());
+ HTTPLog gotHTTPLog = maybeHTTPLog.get();
+ assertEquals(expected.getIp(), gotHTTPLog.getIp());
+ assertEquals(expected.getUser(), gotHTTPLog.getUser());
+ assertEquals(expected.getMethod(), gotHTTPLog.getMethod());
+ assertEquals(expected.getTimestamp(), gotHTTPLog.getTimestamp());
+ assertEquals(expected.getResource(), gotHTTPLog.getResource());
+ assertEquals(expected.getReferrer(), gotHTTPLog.getReferrer());
+ assertEquals(expected.getProtocol(), gotHTTPLog.getProtocol());
+ assertEquals(expected.getStatus(), gotHTTPLog.getStatus());
+ assertEquals(expected.getContentLength(), gotHTTPLog.getContentLength());
+ assertEquals(expected.getUserAgent(), gotHTTPLog.getUserAgent());
+ }
+
+ @Test
+ public void handleIPV6ParseHttpLog() {
+ String logLine =
+ "2405:204:a313:675::17a:b0b1 - - [19/Jan/2019:18:15:02 +0000] \"GET /plugins/codemirror-editor/static/codemirror_editor.js HTTP/1.1\" 200 1498 \"https://gerrithub.io/plugins/codemirror-editor/static/codemirror_editor.html\" \"Mozilla/5.0 (Linux; Android 4.2.1; en-us; Nexus 5 Build/JOP40D) AppleWebKit/535.19 (KHTML, like Gecko; googleweblight) Chrome/38.0.1025.166 Mobile Safari/535.19\"";
+ Optional<HTTPLog> maybeHTTPLog = HTTPLog.createFromLog(logLine);
+ assertTrue("Didn't create HTTPLog", maybeHTTPLog.isPresent());
+ assertEquals("2405:204:a313:675::17a:b0b1", maybeHTTPLog.get().getIp());
+ }
+
+ @Test(expected = Test.None.class /* no exception expected */)
+ public void handleNonNumericalContentLengthParseHttpLog() {
+ String logLine =
+ "171.13.14.52 - - [19/Jan/2019:00:00:46 +0000] \"HEAD /Documentation/index.html HTTP/1.1\" 200 - - \"Mozilla/5.0 (Windows NT 10.0 WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36\"";
+ HTTPLog.createFromLog(logLine);
+ }
+
+ @Test
+ public void invalidHttpLog() {
+ assertEquals(Optional.empty(), HTTPLog.createFromLog("invalid http log"));
+ }
+
+ @Test(expected = Test.None.class /* no exception expected */)
+ public void correctlyTransform_HttpAuditEvent() throws Exception {
+ HTTPLog auditHTTPLog =
+ new HTTPLog(
+ "10.10.100.108",
+ "anyUser",
+ "19/Jan/2019:00:00:00 +0000",
+ "amnyResource",
+ "anyProtocol",
+ "-",
+ 200,
+ "-",
+ "-",
+ "\"git/1.8.3.1\"");
+
+ Optional<String> maybeAuditLog = auditHTTPLog.toAuditLog(loggerAudit);
+ assertTrue("Didn't Audit Log", maybeAuditLog.isPresent());
+
+ String auditLog = maybeAuditLog.get();
+ assertTrue(auditLog.contains("\"type\":\"HttpAuditEvent\""));
+ assertTrue(auditLog.contains("\"access_path\":\"GIT\""));
+ }
+
+ @Test(expected = Test.None.class /* no exception expected */)
+ public void correctlyTransform_ExtendedHttpAuditEvent() throws Exception {
+ HTTPLog auditHTTPLog =
+ new HTTPLog(
+ "10.10.100.108",
+ "anyUser",
+ "19/Jan/2019:00:00:00 +0000",
+ "amnyResource",
+ "anyProtocol",
+ "-",
+ 200,
+ "-",
+ "-",
+ "\"anyUserAgent\"");
+
+ Optional<String> maybeAuditLog = auditHTTPLog.toAuditLog(loggerAudit);
+ assertTrue("Didn't Audit Log", maybeAuditLog.isPresent());
+
+ String auditLog = maybeAuditLog.get();
+ assertTrue(auditLog.contains("\"type\":\"ExtendedHttpAuditEvent\""));
+ assertTrue(auditLog.contains("\"access_path\":\"REST_API\""));
+ }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/auditsl4j/logsource/SSHLogTest.java b/src/test/java/com/googlesource/gerrit/plugins/auditsl4j/logsource/SSHLogTest.java
new file mode 100644
index 0000000..835104f
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/auditsl4j/logsource/SSHLogTest.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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.googlesource.gerrit.plugins.auditsl4j.logsource;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.googlesource.gerrit.plugins.auditsl4j.AuditRendererToJson;
+import com.googlesource.gerrit.plugins.auditsl4j.AuditWriterToStringList;
+import com.googlesource.gerrit.plugins.auditsl4j.LoggerAudit;
+import java.util.Optional;
+import org.junit.Test;
+
+public class SSHLogTest {
+
+ LoggerAudit loggerAudit =
+ new LoggerAudit(new AuditWriterToStringList(), new AuditRendererToJson());
+
+ @Test
+ public void successfullyParseSshLog() {
+ String user = "myUser";
+ String timestamp = "2019-01-23 12:43:53,115 +0100";
+ String session = "b015fbe2";
+ String accountId = "a/1000000";
+ String command = "audit-sl4j.import.--from.rewew.--until.sdfds";
+ String waitTime = "2ms";
+ String execTime = "2ms";
+ String result = "FAIL";
+
+ // [2019-01-23 12:44:04,723 +0100] b015fbe2 admin a/1000000
+ // audit-sl4j.import.--from.rewew.--until.sdfds 2ms 2ms 0
+ String logLine =
+ String.format(
+ "[%s] %s %s %s %s %s %s %s",
+ timestamp, session, user, accountId, command, waitTime, execTime, result);
+
+ SSHLog expected =
+ new SSHLog(timestamp, session, user, accountId, command, waitTime, execTime, result);
+
+ Optional<SSHLog> maybeSshLog = SSHLog.createFromLog(logLine);
+ assertTrue("Didn't create SSHLog", maybeSshLog.isPresent());
+ SSHLog gotSSHLog = maybeSshLog.get();
+ assertEquals(expected.getTimestamp(), gotSSHLog.getTimestamp());
+ assertEquals(expected.getSession(), gotSSHLog.getSession());
+ assertEquals(expected.getUser(), gotSSHLog.getUser());
+ assertEquals(expected.getAccountId(), gotSSHLog.getAccountId());
+ assertEquals(expected.getCommand(), gotSSHLog.getCommand());
+ assertEquals(expected.getWaitTime(), gotSSHLog.getWaitTime());
+ assertEquals(expected.getExecTime(), gotSSHLog.getExecTime());
+ assertEquals(expected.getResult(), gotSSHLog.getResult());
+ }
+
+ @Test
+ public void successfullyParseSshLogWithParameters() {
+ String logLine =
+ "[2019-01-20 00:20:24,277 +0000] e4e82e5e spdk-bot a/1011203 gerrit.query.--format.json.--current-patch-set.status:open project:spdk/spdk.github.io label:Verified=0 NOT is:draft 1ms 1ms 0";
+ Optional<SSHLog> maybeSshLog = SSHLog.createFromLog(logLine);
+ assertTrue("Didn't create SSHLog", maybeSshLog.isPresent());
+ assertEquals(
+ "gerrit.query.--format.json.--current-patch-set.status:open project:spdk/spdk.github.io label:Verified=0 NOT is:draft",
+ maybeSshLog.get().getCommand());
+ assertEquals("0", maybeSshLog.get().getResult());
+ }
+
+ @Test
+ public void handleLogout() {
+ String logLine = "[2019-01-23 12:44:26,665 +0100] 70e3031f admin a/1000000 LOGOUT";
+ Optional<SSHLog> maybeSshLog = SSHLog.createFromLog(logLine);
+ assertTrue("Didn't create SSHLog", maybeSshLog.isPresent());
+ assertEquals("LOGOUT", maybeSshLog.get().getCommand());
+ assertEquals("0", maybeSshLog.get().getResult());
+ }
+
+ @Test
+ public void handleLogin() {
+ String logLine =
+ "[2019-01-01 00:00:05,613 +0000] e126989b spdk-bot a/1011203 LOGIN FROM 172.19.0.1";
+ Optional<SSHLog> maybeSshLog = SSHLog.createFromLog(logLine);
+ assertTrue("Didn't create SSHLog", maybeSshLog.isPresent());
+ assertEquals("LOGIN", maybeSshLog.get().getCommand());
+ assertEquals("0", maybeSshLog.get().getResult());
+ }
+
+ @Test
+ public void handleNonAuthCommandWithoutTiming() {
+ String logLine =
+ "[2018-09-22 15:44:30,539 +0000] ce2a2263 vogella-jenkins a/1012807 gerrit.review.426428,1.--message.Build Failed";
+ Optional<SSHLog> maybeSshLog = SSHLog.createFromLog(logLine);
+ assertTrue("Didn't create SSHLog", maybeSshLog.isPresent());
+ assertEquals("gerrit.review.426428,1.--message.Build Failed", maybeSshLog.get().getCommand());
+ assertEquals("0", maybeSshLog.get().getResult());
+ }
+
+ @Test
+ public void handleCommandWithMultiSpaces() {
+ String logLine =
+ "[2018-09-22 16:28:30,668 +0000] 61e0f7b9 vogella-jenkins a/1012807 gerrit.review.426428,2.--message.Build Started http://build.vogella.com:8080/job/learn.vogella.com-Gerrit/1323/ .--verified.0.--code-review.0 1ms 303ms 0";
+ Optional<SSHLog> maybeSshLog = SSHLog.createFromLog(logLine);
+ assertTrue("Didn't create SSHLog", maybeSshLog.isPresent());
+ assertEquals(
+ "gerrit.review.426428,2.--message.Build Started http://build.vogella.com:8080/job/learn.vogella.com-Gerrit/1323/ .--verified.0.--code-review.0",
+ maybeSshLog.get().getCommand());
+ assertEquals("0", maybeSshLog.get().getResult());
+ }
+
+ @Test
+ public void handleAuthFailure() {
+ String logLine =
+ "[2018-09-03 18:14:43,831 +0000] f540bf46 - AUTH FAILURE FROM 172.19.0.1 user-not-found";
+ Optional<SSHLog> maybeSshLog = SSHLog.createFromLog(logLine);
+ assertTrue("Didn't create SSHLog", maybeSshLog.isPresent());
+ assertEquals("AUTH FAILURE", maybeSshLog.get().getCommand());
+ assertEquals("0", maybeSshLog.get().getResult());
+ }
+
+ @Test(expected = Test.None.class /* no exception expected */)
+ public void correctlyTransform_SshAuditEvent() throws Exception {
+ SSHLog SSHLog =
+ new SSHLog(
+ "2019-01-23 12:44:26,665 +0100",
+ "70e3031f",
+ "admin",
+ "a/1000000",
+ "LOGOUT",
+ "2ms",
+ "2ms",
+ "0");
+
+ Optional<String> maybeAuditLog = SSHLog.toAuditLog(loggerAudit);
+ assertTrue("Didn't Audit Log", maybeAuditLog.isPresent());
+
+ String auditLog = maybeAuditLog.get();
+ assertTrue("'type' not matched: " + auditLog, auditLog.contains("\"type\":\"SshAuditEvent\""));
+ assertTrue(
+ "'access_path' not matched: " + auditLog,
+ auditLog.contains("\"access_path\":\"SSH_COMMAND\""));
+ assertTrue("'when' not matched: " + auditLog, auditLog.contains("\"when\":1548243866665"));
+ }
+}