Merge branch 'stable-2.15' into stable-2.16

* stable-2.15:
  Set version to 2.15.7
  Trigger audit for GIT over Http commands

Change-Id: If5451ef6b21df9219b06dd045f1138c5367fc3b2
This commit is contained in:
Antonio Barone 2018-11-13 15:58:16 -08:00 committed by David Pursehouse
commit 98ecbb1cfe
16 changed files with 370 additions and 9 deletions

View File

@ -128,6 +128,7 @@ import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.testing.ConfigSuite;
import com.google.gerrit.testing.FakeEmailSender;
import com.google.gerrit.testing.FakeEmailSender.Message;
import com.google.gerrit.testing.FakeGroupAuditService;
import com.google.gerrit.testing.NoteDbMode;
import com.google.gerrit.testing.SshMode;
import com.google.gerrit.testing.TempFileUtil;
@ -244,6 +245,7 @@ public abstract class AbstractDaemonTest {
@Inject protected ChangeNoteUtil changeNoteUtil;
@Inject protected ChangeResource.Factory changeResourceFactory;
@Inject protected FakeEmailSender sender;
@Inject protected FakeGroupAuditService auditService;
@Inject protected GerritApi gApi;
@Inject protected GitRepositoryManager repoManager;
@Inject protected GroupBackend groupBackend;

View File

@ -43,6 +43,7 @@ import com.google.gerrit.server.util.OneOffRequestContext;
import com.google.gerrit.server.util.SocketUtil;
import com.google.gerrit.server.util.SystemLog;
import com.google.gerrit.testing.FakeEmailSender;
import com.google.gerrit.testing.FakeGroupAuditService;
import com.google.gerrit.testing.InMemoryDatabase;
import com.google.gerrit.testing.InMemoryRepositoryManager;
import com.google.gerrit.testing.NoteDbChecker;
@ -355,6 +356,7 @@ public class GerritServer implements AutoCloseable {
},
site);
daemon.setEmailModuleForTesting(new FakeEmailSender.Module());
daemon.setAuditEventModuleForTesting(new FakeGroupAuditService.Module());
daemon.setAdditionalSysModuleForTesting(testSysModule);
daemon.setEnableSshd(desc.useSsh());
daemon.setSlave(isSlave(baseConfig));

View File

@ -0,0 +1,94 @@
// Copyright (C) 2018 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.audit;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroupById;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.Collection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
public class AuditServiceImpl implements AuditService {
private static final Logger log = LoggerFactory.getLogger(AuditServiceImpl.class);
private final DynamicSet<AuditListener> auditListeners;
private final DynamicSet<GroupMemberAuditListener> groupMemberAuditListeners;
@Inject
public AuditServiceImpl(
DynamicSet<AuditListener> auditListeners,
DynamicSet<GroupMemberAuditListener> groupMemberAuditListeners) {
this.auditListeners = auditListeners;
this.groupMemberAuditListeners = groupMemberAuditListeners;
}
@Override
public void dispatch(AuditEvent action) {
for (AuditListener auditListener : auditListeners) {
auditListener.onAuditableAction(action);
}
}
@Override
public void dispatchAddAccountsToGroup(Account.Id actor, Collection<AccountGroupMember> added) {
for (GroupMemberAuditListener auditListener : groupMemberAuditListeners) {
try {
auditListener.onAddAccountsToGroup(actor, added);
} catch (RuntimeException e) {
log.error("failed to log add accounts to group event", e);
}
}
}
@Override
public void dispatchDeleteAccountsFromGroup(
Account.Id actor, Collection<AccountGroupMember> removed) {
for (GroupMemberAuditListener auditListener : groupMemberAuditListeners) {
try {
auditListener.onDeleteAccountsFromGroup(actor, removed);
} catch (RuntimeException e) {
log.error("failed to log delete accounts from group event", e);
}
}
}
@Override
public void dispatchAddGroupsToGroup(Account.Id actor, Collection<AccountGroupById> added) {
for (GroupMemberAuditListener auditListener : groupMemberAuditListeners) {
try {
auditListener.onAddGroupsToGroup(actor, added);
} catch (RuntimeException e) {
log.error("failed to log add groups to group event", e);
}
}
}
@Override
public void dispatchDeleteGroupsFromGroup(
Account.Id actor, Collection<AccountGroupById> removed) {
for (GroupMemberAuditListener auditListener : groupMemberAuditListeners) {
try {
auditListener.onDeleteGroupsFromGroup(actor, removed);
} catch (RuntimeException e) {
log.error("failed to log delete groups from group event", e);
}
}
}
}

View File

@ -23,7 +23,7 @@ import java.lang.annotation.Target;
* Audit annotation for JSON/RPC interfaces.
*
* <p>Flag with @Audit all the JSON/RPC methods to be traced in audit-trail and submitted to the
* AuditService.
* GroupAuditService.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})

View File

@ -15,6 +15,8 @@
package com.google.gerrit.httpd;
import com.google.common.cache.Cache;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.gerrit.common.data.Capable;
import com.google.gerrit.extensions.registration.DynamicSet;
@ -23,6 +25,7 @@ import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.AccessPath;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.audit.HttpAuditEvent;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.git.DefaultAdvertiseRefsHook;
import com.google.gerrit.server.git.GitRepositoryManager;
@ -30,12 +33,14 @@ import com.google.gerrit.server.git.TransferConfig;
import com.google.gerrit.server.git.UploadPackInitializer;
import com.google.gerrit.server.git.receive.AsyncReceiveCommits;
import com.google.gerrit.server.git.validators.UploadValidators;
import com.google.gerrit.server.group.GroupAuditService;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.util.time.TimeUtil;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Provider;
@ -141,6 +146,30 @@ public class GitOverHttpServlet extends GitServlet {
addReceivePackFilter(receiveFilter);
}
private static String extractWhat(HttpServletRequest request) {
StringBuilder commandName = new StringBuilder(request.getRequestURL());
if (request.getQueryString() != null) {
commandName.append("?").append(request.getQueryString());
}
return commandName.toString();
}
private static ListMultimap<String, String> extractParameters(HttpServletRequest request) {
ListMultimap<String, String> multiMap = ArrayListMultimap.create();
if (request.getQueryString() != null) {
request
.getParameterMap()
.forEach(
(k, v) -> {
for (int i = 0; i < v.length; i++) {
multiMap.put(k, v[i]);
}
});
}
return multiMap;
}
static class Resolver implements RepositoryResolver<HttpServletRequest> {
private final GitRepositoryManager manager;
private final PermissionBackend permissionBackend;
@ -240,12 +269,19 @@ public class GitOverHttpServlet extends GitServlet {
static class UploadFilter implements Filter {
private final UploadValidators.Factory uploadValidatorsFactory;
private final PermissionBackend permissionBackend;
private final Provider<CurrentUser> userProvider;
private final GroupAuditService groupAuditService;
@Inject
UploadFilter(
UploadValidators.Factory uploadValidatorsFactory, PermissionBackend permissionBackend) {
UploadValidators.Factory uploadValidatorsFactory,
PermissionBackend permissionBackend,
Provider<CurrentUser> userProvider,
GroupAuditService groupAuditService) {
this.uploadValidatorsFactory = uploadValidatorsFactory;
this.permissionBackend = permissionBackend;
this.userProvider = userProvider;
this.groupAuditService = groupAuditService;
}
@Override
@ -268,7 +304,22 @@ public class GitOverHttpServlet extends GitServlet {
return;
} catch (PermissionBackendException e) {
throw new ServletException(e);
} finally {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
groupAuditService.dispatch(
new HttpAuditEvent(
httpRequest.getSession().getId(),
userProvider.get(),
extractWhat(httpRequest),
TimeUtil.nowMs(),
extractParameters(httpRequest),
httpRequest.getMethod(),
httpRequest,
httpResponse.getStatus(),
httpResponse));
}
// We use getRemoteHost() here instead of getRemoteAddr() because REMOTE_ADDR
// may have been overridden by a proxy server -- we'll try to avoid this.
UploadValidators uploadValidators =
@ -326,15 +377,18 @@ public class GitOverHttpServlet extends GitServlet {
private final Cache<AdvertisedObjectsCacheKey, Set<ObjectId>> cache;
private final PermissionBackend permissionBackend;
private final Provider<CurrentUser> userProvider;
private final GroupAuditService groupAuditService;
@Inject
ReceiveFilter(
@Named(ID_CACHE) Cache<AdvertisedObjectsCacheKey, Set<ObjectId>> cache,
PermissionBackend permissionBackend,
Provider<CurrentUser> userProvider) {
Provider<CurrentUser> userProvider,
GroupAuditService groupAuditService) {
this.cache = cache;
this.permissionBackend = permissionBackend;
this.userProvider = userProvider;
this.groupAuditService = groupAuditService;
}
@Override
@ -365,6 +419,20 @@ public class GitOverHttpServlet extends GitServlet {
return;
} catch (PermissionBackendException e) {
throw new RuntimeException(e);
} finally {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
groupAuditService.dispatch(
new HttpAuditEvent(
httpRequest.getSession().getId(),
userProvider.get(),
extractWhat(httpRequest),
TimeUtil.nowMs(),
extractParameters(httpRequest),
httpRequest.getMethod(),
httpRequest,
httpResponse.getStatus(),
httpResponse));
}
if (canUpload != Capable.OK) {

View File

@ -17,8 +17,8 @@ package com.google.gerrit.httpd;
import com.google.common.base.Strings;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.server.AuditEvent;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.audit.AuditEvent;
import com.google.gerrit.server.audit.AuditService;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.CanonicalWebUrl;

View File

@ -199,6 +199,7 @@ public class Daemon extends SiteProgram {
private AbstractModule luceneModule;
private Module emailModule;
private Module testSysModule;
private Module auditEventModule;
private Runnable serverStarted;
private IndexType indexType;
@ -319,6 +320,11 @@ public class Daemon extends SiteProgram {
emailModule = module;
}
@VisibleForTesting
public void setAuditEventModuleForTesting(Module module) {
auditEventModule = module;
}
@VisibleForTesting
public void setLuceneModule(LuceneIndexModule m) {
luceneModule = m;
@ -425,7 +431,6 @@ public class Daemon extends SiteProgram {
modules.add(cfgInjector.getInstance(GerritGlobalModule.class));
modules.add(new GerritApiModule());
modules.add(new PluginApiModule());
modules.add(new AuditModule());
modules.add(new SearchingChangeCacheImpl.Module(slave));
modules.add(new InternalAccountDirectory.Module());
@ -438,6 +443,11 @@ public class Daemon extends SiteProgram {
} else {
modules.add(new SmtpEmailSender.Module());
}
if (auditEventModule != null) {
modules.add(auditEventModule);
} else {
modules.add(new AuditModule());
}
modules.add(new SignedTokenEmailTokenVerifier.Module());
modules.add(new RestApiModule());
modules.add(new GpgModule(config));

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.audit;
package com.google.gerrit.server;
import static java.util.Objects.requireNonNull;
@ -20,7 +20,6 @@ import com.google.auto.value.AutoValue;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.util.time.TimeUtil;
public class AuditEvent {

View File

@ -15,6 +15,7 @@
package com.google.gerrit.server.audit;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
import com.google.gerrit.server.AuditEvent;
@ExtensionPoint
public interface AuditListener {

View File

@ -17,6 +17,7 @@ package com.google.gerrit.server.audit;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.AuditEvent;
import com.google.gerrit.server.audit.group.GroupAuditListener;
import com.google.gerrit.server.audit.group.GroupMemberAuditEvent;
import com.google.gerrit.server.audit.group.GroupSubgroupAuditEvent;
@ -39,6 +40,7 @@ public class AuditService implements GroupAuditService {
this.groupAuditListeners = groupAuditListeners;
}
@Override
public void dispatch(AuditEvent action) {
auditListeners.runEach(l -> l.onAuditableAction(action));
}

View File

@ -14,6 +14,7 @@
package com.google.gerrit.server.audit;
import com.google.common.collect.ListMultimap;
import com.google.gerrit.server.AuditEvent;
import com.google.gerrit.server.CurrentUser;
public class HttpAuditEvent extends AuditEvent {

View File

@ -15,6 +15,7 @@
package com.google.gerrit.server.audit;
import com.google.common.collect.ListMultimap;
import com.google.gerrit.server.AuditEvent;
import com.google.gerrit.server.CurrentUser;
public class SshAuditEvent extends AuditEvent {

View File

@ -18,9 +18,11 @@ import com.google.common.collect.ImmutableSet;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Account.Id;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.AuditEvent;
import java.sql.Timestamp;
public interface GroupAuditService {
void dispatch(AuditEvent action);
void dispatchAddMembers(
Account.Id actor,

View File

@ -275,8 +275,7 @@ class GroupRebuilder {
* Distinct event types.
*
* <p>Events at the same time by the same user are batched together by type. The types should
* correspond to the possible batch operations supported by {@link
* com.google.gerrit.server.audit.AuditService}.
* correspond to the possible batch operations supported by AuditService.
*/
enum Type {
ADD_MEMBER,

View File

@ -0,0 +1,112 @@
// Copyright (C) 2018 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.testing;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.AuditEvent;
import com.google.gerrit.server.audit.AuditListener;
import com.google.gerrit.server.audit.group.GroupAuditListener;
import com.google.gerrit.server.audit.group.GroupMemberAuditEvent;
import com.google.gerrit.server.audit.group.GroupSubgroupAuditEvent;
import com.google.gerrit.server.group.GroupAuditService;
import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
@Singleton
public class FakeGroupAuditService implements GroupAuditService {
private final PluginSetContext<GroupAuditListener> groupAuditListeners;
private final PluginSetContext<AuditListener> auditListeners;
public static class Module extends AbstractModule {
@Override
public void configure() {
DynamicSet.setOf(binder(), GroupAuditListener.class);
DynamicSet.setOf(binder(), AuditListener.class);
bind(GroupAuditService.class).to(FakeGroupAuditService.class);
}
}
@Inject
public FakeGroupAuditService(
PluginSetContext<GroupAuditListener> groupAuditListeners,
PluginSetContext<AuditListener> auditListeners) {
this.groupAuditListeners = groupAuditListeners;
this.auditListeners = auditListeners;
}
public List<AuditEvent> auditEvents = new ArrayList<>();
public void clearEvents() {
auditEvents.clear();
}
@Override
public void dispatch(AuditEvent action) {
auditEvents.add(action);
}
@Override
public void dispatchAddMembers(
Account.Id actor,
AccountGroup.UUID updatedGroup,
ImmutableSet<Account.Id> addedMembers,
Timestamp addedOn) {
GroupMemberAuditEvent event =
GroupMemberAuditEvent.create(actor, updatedGroup, addedMembers, addedOn);
groupAuditListeners.runEach(l -> l.onAddMembers(event));
}
@Override
public void dispatchDeleteMembers(
Account.Id actor,
AccountGroup.UUID updatedGroup,
ImmutableSet<Account.Id> deletedMembers,
Timestamp deletedOn) {
GroupMemberAuditEvent event =
GroupMemberAuditEvent.create(actor, updatedGroup, deletedMembers, deletedOn);
groupAuditListeners.runEach(l -> l.onDeleteMembers(event));
}
@Override
public void dispatchAddSubgroups(
Account.Id actor,
AccountGroup.UUID updatedGroup,
ImmutableSet<AccountGroup.UUID> addedSubgroups,
Timestamp addedOn) {
GroupSubgroupAuditEvent event =
GroupSubgroupAuditEvent.create(actor, updatedGroup, addedSubgroups, addedOn);
groupAuditListeners.runEach(l -> l.onAddSubgroups(event));
}
@Override
public void dispatchDeleteSubgroups(
Account.Id actor,
AccountGroup.UUID updatedGroup,
ImmutableSet<AccountGroup.UUID> deletedSubgroups,
Timestamp deletedOn) {
GroupSubgroupAuditEvent event =
GroupSubgroupAuditEvent.create(actor, updatedGroup, deletedSubgroups, deletedOn);
groupAuditListeners.runEach(l -> l.onDeleteSubgroups(event));
}
}

View File

@ -0,0 +1,68 @@
// Copyright (C) 2018 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.acceptance.git;
import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.server.AuditEvent;
import java.util.Collections;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.junit.Before;
import org.junit.Test;
public class GitOverHttpServletIT extends AbstractPushForReview {
@Before
public void beforeEach() throws Exception {
CredentialsProvider.setDefault(
new UsernamePasswordCredentialsProvider(admin.username, admin.httpPassword));
selectProtocol(AbstractPushForReview.Protocol.HTTP);
auditService.clearEvents();
}
@Test
public void receivePackAuditEventLog() throws Exception {
testRepo
.git()
.push()
.setRemote("origin")
.setRefSpecs(new RefSpec("HEAD:refs/for/master"))
.call();
// Git smart protocol makes two requests:
// https://github.com/git/git/blob/master/Documentation/technical/http-protocol.txt
assertThat(auditService.auditEvents.size()).isEqualTo(2);
AuditEvent e = auditService.auditEvents.get(1);
assertThat(e.who.getAccountId()).isEqualTo(admin.id);
assertThat(e.what).endsWith("/git-receive-pack");
assertThat(e.params).isEmpty();
}
@Test
public void uploadPackAuditEventLog() throws Exception {
testRepo.git().fetch().call();
assertThat(auditService.auditEvents.size()).isEqualTo(1);
AuditEvent e = auditService.auditEvents.get(0);
assertThat(e.who.toString()).isEqualTo("ANONYMOUS");
assertThat(e.params.get("service"))
.containsExactlyElementsIn(Collections.singletonList("git-upload-pack"));
assertThat(e.what).endsWith("service=git-upload-pack");
}
}