Audit hooks on JSON/RPC and SSH commands using Gerrit plugins.

New @Audit annotation to enable invocation of
AuditService injected on GerritGlobalModule.

Annotation applies to JSON/RPC interfaces whilst
on the SSH side audit is "hooked" directly into
SshLog class.

Enables the integration of Audit plugins through
the implementation of audit-listeners.
Dynamic loading and unloading of audit plugins is supported
through the @Extension/@Listener association and
automatically loaded and unloaded using Plugin
module self-registration.

In order to implement a new AuditListener implementation
you only to:

1) Define an implementation of AuditListener and annotate
as:

@Listener
public class MyAuditTrail extends AuditListener

2) Define a Plugin Module to bind your Audit implementation
in the configure() method as:

DynamicSet.bind(binder(), AuditListener.class)
  .to(MyAuditTrail.class);

Change-Id: Iaa26c4687a4ef4cbe27fe8396a5e0b8f6627536f
Signed-off-by: Luca Milanesio <luca.milanesio@gmail.com>
This commit is contained in:
Luca Milanesio
2012-04-13 11:12:18 +01:00
committed by Edwin Kempin
parent d5e87c3aad
commit 27ba2ac5e6
20 changed files with 592 additions and 10 deletions

View File

@@ -228,7 +228,7 @@ class CommandFactoryProvider implements Provider<CommandFactory> {
}
/** Split a command line into a string array. */
static String[] split(String commandLine) {
static public String[] split(String commandLine) {
final List<String> list = new ArrayList<String>();
boolean inquote = false;
boolean inDblQuote = false;

View File

@@ -154,4 +154,8 @@ final class DispatchCommand extends BaseCommand {
usage.append("\n");
return usage.toString();
}
public String getCommandName() {
return commandName;
}
}

View File

@@ -15,6 +15,8 @@
package com.google.gerrit.sshd;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.audit.AuditEvent;
import com.google.gerrit.audit.AuditService;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PeerDaemonUser;
@@ -40,6 +42,7 @@ import org.eclipse.jgit.util.QuotedString;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
@@ -58,12 +61,14 @@ class SshLog implements LifecycleListener {
private final Provider<SshSession> session;
private final Provider<Context> context;
private final AsyncAppender async;
private final AuditService auditService;
@Inject
SshLog(final Provider<SshSession> session, final Provider<Context> context,
final SitePaths site, @GerritServerConfig Config config) {
final SitePaths site, @GerritServerConfig Config config, AuditService auditService) {
this.session = session;
this.context = context;
this.auditService = auditService;
final DailyRollingFileAppender dst = new DailyRollingFileAppender();
dst.setName(LOG_NAME);
@@ -96,6 +101,7 @@ class SshLog implements LifecycleListener {
void onLogin() {
async.append(log("LOGIN FROM " + session.get().getRemoteAddressAsString()));
audit("0", "LOGIN", new String[] {});
}
void onAuthFail(final SshSession sd) {
@@ -121,6 +127,7 @@ class SshLog implements LifecycleListener {
}
async.append(event);
audit("FAIL", "AUTH", new String[] {sd.getRemoteAddressAsString()});
}
void onExecute(int exitValue) {
@@ -158,10 +165,18 @@ class SshLog implements LifecycleListener {
event.setProperty(P_STATUS, status);
async.append(event);
audit(status, getCommand(commandLine), CommandFactoryProvider.split(commandLine));
}
private String getCommand(String commandLine) {
commandLine = commandLine.trim();
int spacePos = commandLine.indexOf(' ');
return (spacePos > 0 ? commandLine.substring(0, spacePos):commandLine);
}
void onLogout() {
async.append(log("LOGOUT"));
audit("0", "LOGOUT", new String[] {});
}
private LoggingEvent log(final String msg) {
@@ -192,7 +207,6 @@ class SshLog implements LifecycleListener {
} else if (user instanceof PeerDaemonUser) {
userName = PeerDaemonUser.USER_NAME;
}
event.setProperty(P_USER_NAME, userName);
@@ -400,4 +414,44 @@ class SshLog implements LifecycleListener {
public void setLogger(Logger logger) {
}
}
void audit(Object result, String commandName, String[] args) {
final Context ctx = context.get();
final String sid = extractSessionId(ctx);
final long created = extractCreated(ctx);
final String what = extractWhat(commandName, args);
auditService.dispatch(new AuditEvent(sid, extractCurrentUser(ctx), "ssh:"
+ what, created, Arrays.asList(args), result));
}
private String extractWhat(String commandName, String[] args) {
String result = commandName;
if ("gerrit".equals(commandName)) {
if (args.length > 1)
result = "gerrit"+"."+args[1];
}
return result;
}
private long extractCreated(final Context ctx) {
return (ctx != null) ? ctx.created : System.currentTimeMillis();
}
private CurrentUser extractCurrentUser(final Context ctx) {
if (ctx != null) {
SshSession session = ctx.getSession();
return (session == null) ? null : session.getCurrentUser();
} else {
return null;
}
}
private String extractSessionId(final Context ctx) {
if (ctx != null) {
SshSession session = ctx.getSession();
return (session == null) ? null : IdGenerator.format(session.getSessionId());
} else {
return null;
}
}
}