Store SSH keys in git

The public SSH keys of a user are now stored in an authorized_keys
file in the All-Users repository in the refs/users/CD/ABCD branch of
the user.

Storing SSH keys in an authorized_keys file is the standard way for
SSH to store public keys. Each key is stored on a separate line.

The order of the keys in the file determines the sequence numbers of
the keys.

Invalid keys are marked with the prefix '# INVALID'.

To keep the sequence numbers intact when a key is deleted, a
'# DELETED' line is inserted at the position where the key was
deleted.

Other comment lines are ignored on read, and are not written back when
the file is modified.

Supporting a 2-step live migration for a multi-master Gerrit
installation is not needed since the googlesource.com instances do not
use SSH and there are no other multi-master installations.

On creation of an SSH key, RFC 4716 style keys need to be converted to
OpenSSH style keys. Also before adding a key it should be checked that
the key is parseable. Both of this requires classes from SSH libs that
are only available in the SSH layer. This is why the SSH key creation
must be done there. So far this was done in SshKeyCacheImpl, but since
SshKeyCacheImpl needs VersionedAuthorizedKeys to load keys and to mark
keys as invalid, VersionedAuthorizedKeys cannot not depend on
SshKeyCacheImpl as this would be a cyclic dependency. Instead split
out the SSH key creation from SshKeyCacheImpl into SshKeyCreatorImpl.
This way SshKeyCacheImpl depends on VersionedAuthorizedKeys and
VersionedAuthorizedKeys depends on SshKeyCreatorImpl, and there is no
dependency circle.

Change-Id: I8fcc3c0f27e034fc2c8e8ae3612068099075467d
Signed-off-by: Edwin Kempin <ekempin@google.com>
This commit is contained in:
Edwin Kempin
2016-04-07 14:00:17 +02:00
parent 3cf19f686a
commit 07952c069a
31 changed files with 1136 additions and 221 deletions

View File

@@ -34,7 +34,6 @@ import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
import com.google.gerrit.reviewdb.client.AccountSshKey;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.CreateAccount.Input;
@@ -48,7 +47,9 @@ import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import org.apache.commons.validator.routines.EmailValidator;
import org.eclipse.jgit.errors.ConfigInvalidException;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
@@ -73,37 +74,45 @@ public class CreateAccount implements RestModifyView<TopLevelResource, Input> {
private final ReviewDb db;
private final Provider<IdentifiedUser> currentUser;
private final GroupsCollection groupsCollection;
private final VersionedAuthorizedKeys.Accessor authorizedKeys;
private final SshKeyCache sshKeyCache;
private final AccountCache accountCache;
private final AccountByEmailCache byEmailCache;
private final AccountLoader.Factory infoLoader;
private final DynamicSet<AccountExternalIdCreator> externalIdCreators;
private final String username;
private final AuditService auditService;
private final String username;
@Inject
CreateAccount(ReviewDb db, Provider<IdentifiedUser> currentUser,
GroupsCollection groupsCollection, SshKeyCache sshKeyCache,
AccountCache accountCache, AccountByEmailCache byEmailCache,
CreateAccount(ReviewDb db,
Provider<IdentifiedUser> currentUser,
GroupsCollection groupsCollection,
VersionedAuthorizedKeys.Accessor authorizedKeys,
SshKeyCache sshKeyCache,
AccountCache accountCache,
AccountByEmailCache byEmailCache,
AccountLoader.Factory infoLoader,
DynamicSet<AccountExternalIdCreator> externalIdCreators,
@Assisted String username, AuditService auditService) {
AuditService auditService,
@Assisted String username) {
this.db = db;
this.currentUser = currentUser;
this.groupsCollection = groupsCollection;
this.authorizedKeys = authorizedKeys;
this.sshKeyCache = sshKeyCache;
this.accountCache = accountCache;
this.byEmailCache = byEmailCache;
this.infoLoader = infoLoader;
this.externalIdCreators = externalIdCreators;
this.username = username;
this.auditService = auditService;
this.username = username;
}
@Override
public Response<AccountInfo> apply(TopLevelResource rsrc, Input input)
throws BadRequestException, ResourceConflictException,
UnprocessableEntityException, OrmException {
UnprocessableEntityException, OrmException, IOException,
ConfigInvalidException {
if (input == null) {
input = new Input();
}
@@ -119,7 +128,6 @@ public class CreateAccount implements RestModifyView<TopLevelResource, Input> {
Set<AccountGroup.Id> groups = parseGroups(input.groups);
Account.Id id = new Account.Id(db.nextAccountId());
AccountSshKey key = createSshKey(id, input.sshKey);
AccountExternalId extUser =
new AccountExternalId(id, new AccountExternalId.Key(
@@ -178,10 +186,6 @@ public class CreateAccount implements RestModifyView<TopLevelResource, Input> {
a.setPreferredEmail(input.email);
db.accounts().insert(Collections.singleton(a));
if (key != null) {
db.accountSshKeys().insert(Collections.singleton(key));
}
for (AccountGroup.Id groupId : groups) {
AccountGroupMember m =
new AccountGroupMember(new AccountGroupMember.Key(id, groupId));
@@ -190,7 +194,15 @@ public class CreateAccount implements RestModifyView<TopLevelResource, Input> {
db.accountGroupMembers().insert(Collections.singleton(m));
}
sshKeyCache.evict(username);
if (input.sshKey != null) {
try {
authorizedKeys.addKey(id, input.sshKey);
sshKeyCache.evict(username);
} catch (InvalidSshKeyException e) {
throw new BadRequestException(e.getMessage());
}
}
accountCache.evictByUsername(username);
byEmailCache.evict(input.email);
@@ -212,18 +224,6 @@ public class CreateAccount implements RestModifyView<TopLevelResource, Input> {
return groupIds;
}
private AccountSshKey createSshKey(Account.Id id, String sshKey)
throws BadRequestException {
if (sshKey == null) {
return null;
}
try {
return sshKeyCache.create(new AccountSshKey.Id(id, 1), sshKey.trim());
} catch (InvalidSshKeyException e) {
throw new BadRequestException(e.getMessage());
}
}
private AccountExternalId.Key getEmailKey(String email) {
return new AccountExternalId.Key(AccountExternalId.SCHEME_MAILTO, email);
}