summaryrefslogtreecommitdiffstats
path: root/java/com/google/gerrit/sshd/SshLog.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/google/gerrit/sshd/SshLog.java')
-rw-r--r--java/com/google/gerrit/sshd/SshLog.java340
1 files changed, 340 insertions, 0 deletions
diff --git a/java/com/google/gerrit/sshd/SshLog.java b/java/com/google/gerrit/sshd/SshLog.java
new file mode 100644
index 0000000000..5fb75c8d21
--- /dev/null
+++ b/java/com/google/gerrit/sshd/SshLog.java
@@ -0,0 +1,340 @@
+// Copyright (C) 2009 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.google.gerrit.sshd;
+
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.MultimapBuilder;
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.PeerDaemonUser;
+import com.google.gerrit.server.audit.SshAuditEvent;
+import com.google.gerrit.server.config.ConfigKey;
+import com.google.gerrit.server.config.ConfigUpdatedEvent;
+import com.google.gerrit.server.config.ConfigUpdatedEvent.ConfigUpdateEntry;
+import com.google.gerrit.server.config.ConfigUpdatedEvent.UpdateResult;
+import com.google.gerrit.server.config.GerritConfigListener;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.group.GroupAuditService;
+import com.google.gerrit.server.ioutil.HexFormat;
+import com.google.gerrit.server.util.SystemLog;
+import com.google.gerrit.server.util.time.TimeUtil;
+import com.google.gerrit.sshd.SshScope.Context;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import org.apache.log4j.AsyncAppender;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.spi.LoggingEvent;
+import org.eclipse.jgit.lib.Config;
+
+@Singleton
+class SshLog implements LifecycleListener, GerritConfigListener {
+ private static final Logger log = Logger.getLogger(SshLog.class);
+ private static final String LOG_NAME = "sshd_log";
+ private static final String P_SESSION = "session";
+ private static final String P_USER_NAME = "userName";
+ private static final String P_ACCOUNT_ID = "accountId";
+ private static final String P_WAIT = "queueWaitTime";
+ private static final String P_EXEC = "executionTime";
+ private static final String P_STATUS = "status";
+ private static final String P_AGENT = "agent";
+
+ private final Provider<SshSession> session;
+ private final Provider<Context> context;
+ private volatile AsyncAppender async;
+ private final GroupAuditService auditService;
+ private final SystemLog systemLog;
+
+ private final Object lock = new Object();
+
+ @Inject
+ SshLog(
+ final Provider<SshSession> session,
+ final Provider<Context> context,
+ SystemLog systemLog,
+ @GerritServerConfig Config config,
+ GroupAuditService auditService) {
+ this.session = session;
+ this.context = context;
+ this.auditService = auditService;
+ this.systemLog = systemLog;
+
+ if (config.getBoolean("sshd", "requestLog", true)) {
+ enableLogging();
+ }
+ }
+
+ /** @return true if a change in state has occurred */
+ public boolean enableLogging() {
+ synchronized (lock) {
+ if (async == null) {
+ async = systemLog.createAsyncAppender(LOG_NAME, new SshLogLayout());
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /** @return true if a change in state has occurred */
+ public boolean disableLogging() {
+ synchronized (lock) {
+ if (async != null) {
+ async.close();
+ async = null;
+ return true;
+ }
+ return false;
+ }
+ }
+
+ @Override
+ public void start() {}
+
+ @Override
+ public void stop() {
+ disableLogging();
+ }
+
+ void onLogin() {
+ LoggingEvent entry = log("LOGIN FROM " + session.get().getRemoteAddressAsString());
+ if (async != null) {
+ async.append(entry);
+ }
+ audit(context.get(), "0", "LOGIN");
+ }
+
+ void onAuthFail(SshSession sd) {
+ final LoggingEvent event =
+ new LoggingEvent( //
+ Logger.class.getName(), // fqnOfCategoryClass
+ log, // logger
+ TimeUtil.nowMs(), // when
+ Level.INFO, // level
+ "AUTH FAILURE FROM " + sd.getRemoteAddressAsString(), // message text
+ "SSHD", // thread name
+ null, // exception information
+ null, // current NDC string
+ null, // caller location
+ null // MDC properties
+ );
+
+ event.setProperty(P_SESSION, id(sd.getSessionId()));
+ event.setProperty(P_USER_NAME, sd.getUsername());
+
+ final String error = sd.getAuthenticationError();
+ if (error != null) {
+ event.setProperty(P_STATUS, error);
+ }
+ if (async != null) {
+ async.append(event);
+ }
+ audit(null, "FAIL", "AUTH");
+ }
+
+ void onExecute(DispatchCommand dcmd, int exitValue, SshSession sshSession) {
+ final Context ctx = context.get();
+ ctx.finished = TimeUtil.nowMs();
+
+ String cmd = extractWhat(dcmd);
+
+ final LoggingEvent event = log(cmd);
+ event.setProperty(P_WAIT, (ctx.started - ctx.created) + "ms");
+ event.setProperty(P_EXEC, (ctx.finished - ctx.started) + "ms");
+
+ final String status;
+ switch (exitValue) {
+ case BaseCommand.STATUS_CANCEL:
+ status = "killed";
+ break;
+
+ case BaseCommand.STATUS_NOT_FOUND:
+ status = "not-found";
+ break;
+
+ case BaseCommand.STATUS_NOT_ADMIN:
+ status = "not-admin";
+ break;
+
+ default:
+ status = String.valueOf(exitValue);
+ break;
+ }
+ event.setProperty(P_STATUS, status);
+ String peerAgent = sshSession.getPeerAgent();
+ if (peerAgent != null) {
+ event.setProperty(P_AGENT, peerAgent);
+ }
+
+ if (async != null) {
+ async.append(event);
+ }
+ audit(context.get(), status, dcmd);
+ }
+
+ private ListMultimap<String, ?> extractParameters(DispatchCommand dcmd) {
+ if (dcmd == null) {
+ return MultimapBuilder.hashKeys(0).arrayListValues(0).build();
+ }
+ String[] cmdArgs = dcmd.getArguments();
+ String paramName = null;
+ int argPos = 0;
+ ListMultimap<String, String> parms = MultimapBuilder.hashKeys().arrayListValues().build();
+ for (int i = 2; i < cmdArgs.length; i++) {
+ String arg = cmdArgs[i];
+ // -- stop parameters parsing
+ if (arg.equals("--")) {
+ for (i++; i < cmdArgs.length; i++) {
+ parms.put("$" + argPos++, cmdArgs[i]);
+ }
+ break;
+ }
+ // --param=value
+ int eqPos = arg.indexOf('=');
+ if (arg.startsWith("--") && eqPos > 0) {
+ parms.put(arg.substring(0, eqPos), arg.substring(eqPos + 1));
+ continue;
+ }
+ // -p value or --param value
+ if (arg.startsWith("-")) {
+ if (paramName != null) {
+ parms.put(paramName, null);
+ }
+ paramName = arg;
+ continue;
+ }
+ // value
+ if (paramName == null) {
+ parms.put("$" + argPos++, arg);
+ } else {
+ parms.put(paramName, arg);
+ paramName = null;
+ }
+ }
+ if (paramName != null) {
+ parms.put(paramName, null);
+ }
+ return parms;
+ }
+
+ void onLogout() {
+ LoggingEvent entry = log("LOGOUT");
+ if (async != null) {
+ async.append(entry);
+ }
+ audit(context.get(), "0", "LOGOUT");
+ }
+
+ private LoggingEvent log(String msg) {
+ final SshSession sd = session.get();
+ final CurrentUser user = sd.getUser();
+
+ final LoggingEvent event =
+ new LoggingEvent( //
+ Logger.class.getName(), // fqnOfCategoryClass
+ log, // logger
+ TimeUtil.nowMs(), // when
+ Level.INFO, // level
+ msg, // message text
+ "SSHD", // thread name
+ null, // exception information
+ null, // current NDC string
+ null, // caller location
+ null // MDC properties
+ );
+
+ event.setProperty(P_SESSION, id(sd.getSessionId()));
+
+ String userName = "-";
+ String accountId = "-";
+
+ if (user != null && user.isIdentifiedUser()) {
+ IdentifiedUser u = user.asIdentifiedUser();
+ userName = u.getUserName().orElse(null);
+ accountId = "a/" + u.getAccountId().toString();
+
+ } else if (user instanceof PeerDaemonUser) {
+ userName = PeerDaemonUser.USER_NAME;
+ }
+
+ event.setProperty(P_USER_NAME, userName);
+ event.setProperty(P_ACCOUNT_ID, accountId);
+
+ return event;
+ }
+
+ private static String id(int id) {
+ return HexFormat.fromInt(id);
+ }
+
+ void audit(Context ctx, Object result, String cmd) {
+ audit(ctx, result, cmd, null);
+ }
+
+ void audit(Context ctx, Object result, DispatchCommand cmd) {
+ audit(ctx, result, extractWhat(cmd), extractParameters(cmd));
+ }
+
+ private void audit(Context ctx, Object result, String cmd, ListMultimap<String, ?> params) {
+ String sessionId;
+ CurrentUser currentUser;
+ long created;
+ if (ctx == null) {
+ sessionId = null;
+ currentUser = null;
+ created = TimeUtil.nowMs();
+ } else {
+ SshSession session = ctx.getSession();
+ sessionId = HexFormat.fromInt(session.getSessionId());
+ currentUser = session.getUser();
+ created = ctx.created;
+ }
+ auditService.dispatch(new SshAuditEvent(sessionId, currentUser, cmd, created, params, result));
+ }
+
+ private String extractWhat(DispatchCommand dcmd) {
+ if (dcmd == null) {
+ return "Command was already destroyed";
+ }
+ StringBuilder commandName = new StringBuilder(dcmd.getCommandName());
+ String[] args = dcmd.getArguments();
+ for (int i = 1; i < args.length; i++) {
+ commandName.append(".").append(args[i]);
+ }
+ return commandName.toString();
+ }
+
+ @Override
+ public Multimap<UpdateResult, ConfigUpdateEntry> configUpdated(ConfigUpdatedEvent event) {
+ ConfigKey sshdRequestLog = ConfigKey.create("sshd", "requestLog");
+ if (!event.isValueUpdated(sshdRequestLog)) {
+ return ConfigUpdatedEvent.NO_UPDATES;
+ }
+ boolean stateUpdated;
+ try {
+ boolean enabled = event.getNewConfig().getBoolean("sshd", "requestLog", true);
+ if (enabled) {
+ stateUpdated = enableLogging();
+ } else {
+ stateUpdated = disableLogging();
+ }
+ return stateUpdated ? event.accept(sshdRequestLog) : ConfigUpdatedEvent.NO_UPDATES;
+ } catch (IllegalArgumentException iae) {
+ return event.reject(sshdRequestLog);
+ }
+ }
+}