# RESTART REQUIRED\r
git.sshBindInterface = \r
\r
-# Directory for storing user SSH keys.\r
+# Specify the SSH key manager to use for retrieving, storing, and removing\r
+# SSH keys.\r
+#\r
+# Valid key managers are:\r
+# com.gitblit.transport.ssh.FileKeyManager\r
+#\r
+# SINCE 1.5.0\r
+git.sshKeysManager = com.gitblit.transport.ssh.FileKeyManager\r
+\r
+# Directory for storing user SSH keys when using the FileKeyManager.\r
#\r
# SINCE 1.5.0\r
git.sshKeysFolder= ${baseFolder}/ssh\r
--- /dev/null
+/*
+ * Copyright 2014 gitblit.com.
+ *
+ * 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.gitblit.transport.ssh;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.PublicKey;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.sshd.common.util.Buffer;
+import org.eclipse.jgit.lib.Constants;
+
+import com.gitblit.Keys;
+import com.gitblit.manager.IRuntimeManager;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+
+/**
+ * Manages SSH keys on the filesystem.
+ *
+ * @author James Moger
+ *
+ */
+public class FileKeyManager implements IKeyManager {
+
+ protected final IRuntimeManager runtimeManager;
+
+ public FileKeyManager(IRuntimeManager runtimeManager) {
+ this.runtimeManager = runtimeManager;
+ }
+
+ @Override
+ public String toString() {
+ File dir = runtimeManager.getFileOrFolder(Keys.git.sshKeysFolder, "${baseFolder}/ssh");
+ return MessageFormat.format("{0} ({1})", getClass().getSimpleName(), dir);
+ }
+
+ @Override
+ public FileKeyManager start() {
+ return this;
+ }
+
+ @Override
+ public boolean isReady() {
+ return true;
+ }
+
+ @Override
+ public FileKeyManager stop() {
+ return this;
+ }
+
+ @Override
+ public List<PublicKey> getKeys(String username) {
+ try {
+ File keys = getKeystore(username);
+ if (!keys.exists()) {
+ return null;
+ }
+ if (keys.exists()) {
+ String str = Files.toString(keys, Charsets.ISO_8859_1);
+ String [] entries = str.split("\n");
+ List<PublicKey> list = new ArrayList<PublicKey>();
+ for (String entry : entries) {
+ if (entry.trim().length() == 0) {
+ // skip blanks
+ continue;
+ }
+ if (entry.charAt(0) == '#') {
+ // skip comments
+ continue;
+ }
+ final String[] parts = entry.split(" ");
+ final byte[] bin = Base64.decodeBase64(Constants.encodeASCII(parts[1]));
+ list.add(new Buffer(bin).getRawPublicKey());
+ }
+
+ if (list.isEmpty()) {
+ return null;
+ }
+ return list;
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Canot read ssh keys", e);
+ }
+ return null;
+ }
+
+ @Override
+ public boolean addKey(String username, String data) {
+ try {
+ File keys = getKeystore(username);
+ Files.append(data + '\n', keys, Charsets.ISO_8859_1);
+ return true;
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot add ssh key", e);
+ }
+ }
+
+ @Override
+ public boolean removeKey(String username, String data) {
+ try {
+ File keystore = getKeystore(username);
+ if (keystore.exists()) {
+ String str = Files.toString(keystore, Charsets.ISO_8859_1);
+ List<String> keep = new ArrayList<String>();
+ String [] entries = str.split("\n");
+ for (String entry : entries) {
+ if (entry.trim().length() == 0) {
+ // keep blanks
+ keep.add(entry);
+ continue;
+ }
+ if (entry.charAt(0) == '#') {
+ // keep comments
+ keep.add(entry);
+ continue;
+ }
+ final String[] parts = entry.split(" ");
+ if (!parts[1].equals(data)) {
+ keep.add(entry);
+ }
+ }
+ return true;
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot remove ssh key", e);
+ }
+ return false;
+ }
+
+ protected File getKeystore(String username) {
+ File dir = runtimeManager.getFileOrFolder(Keys.git.sshKeysFolder, "${baseFolder}/ssh");
+ dir.mkdirs();
+ File keys = new File(dir, username + ".keys");
+ return keys;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2014 gitblit.com.
+ *
+ * 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.gitblit.transport.ssh;
+
+import java.security.PublicKey;
+import java.util.List;
+
+/**
+ *
+ * @author James Moger
+ *
+ */
+public interface IKeyManager {
+
+ IKeyManager start();
+
+ boolean isReady();
+
+ IKeyManager stop();
+
+ List<PublicKey> getKeys(String username);
+
+ boolean addKey(String username, String data);
+
+ boolean removeKey(String username, String data);
+}
--- /dev/null
+/*
+ * Copyright 2014 gitblit.com.
+ *
+ * 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.gitblit.transport.ssh;
+
+import java.security.PublicKey;
+import java.util.List;
+
+/**
+ * Rejects all SSH key management requests.
+ *
+ * @author James Moger
+ *
+ */
+public class NullKeyManager implements IKeyManager {
+
+ public NullKeyManager() {
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+
+ @Override
+ public NullKeyManager start() {
+ return this;
+ }
+
+ @Override
+ public boolean isReady() {
+ return true;
+ }
+
+ @Override
+ public NullKeyManager stop() {
+ return this;
+ }
+
+ @Override
+ public List<PublicKey> getKeys(String username) {
+ return null;
+ }
+
+ @Override
+ public boolean addKey(String username, String data) {
+ return false;
+ }
+
+ @Override
+ public boolean removeKey(String username, String data) {
+ return false;
+ }
+}
import java.text.MessageFormat;
import java.util.concurrent.atomic.AtomicBoolean;
+import javax.inject.Singleton;
+
import org.apache.sshd.SshServer;
import org.apache.sshd.server.keyprovider.PEMGeneratorHostKeyProvider;
import org.eclipse.jgit.internal.JGitText;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.WorkQueue;
+import dagger.Module;
+import dagger.ObjectGraph;
+import dagger.Provides;
+
/**
* Manager for the ssh transport. Roughly analogous to the
* {@link com.gitblit.transport.git.GitDaemon} class.
private final AtomicBoolean run;
- @SuppressWarnings("unused")
private final IGitblit gitblit;
private final SshServer sshd;
+ private final ObjectGraph injector;
/**
* Construct the Gitblit SSH daemon.
*/
public SshDaemon(IGitblit gitblit, IdGenerator idGenerator) {
this.gitblit = gitblit;
-
+ this.injector = ObjectGraph.create(new SshModule());
+
IStoredSettings settings = gitblit.getSettings();
int port = settings.getInteger(Keys.git.sshPort, 0);
String bindInterface = settings.getString(Keys.git.sshBindInterface,
"localhost");
+ IKeyManager keyManager = getKeyManager();
+
InetSocketAddress addr;
if (StringUtils.isEmpty(bindInterface)) {
addr = new InetSocketAddress(port);
sshd.setHost(addr.getHostName());
sshd.setKeyPairProvider(new PEMGeneratorHostKeyProvider(new File(
gitblit.getBaseFolder(), HOST_KEY_STORE).getPath()));
- sshd.setPublickeyAuthenticator(new SshKeyAuthenticator(gitblit));
+ sshd.setPublickeyAuthenticator(new SshKeyAuthenticator(keyManager, gitblit));
sshd.setPasswordAuthenticator(new SshPasswordAuthenticator(gitblit));
sshd.setSessionFactory(new SshSessionFactory(idGenerator));
sshd.setFileSystemFactory(new DisabledFilesystemFactory());
}
}
}
+
+ protected IKeyManager getKeyManager() {
+ IKeyManager keyManager = null;
+ IStoredSettings settings = gitblit.getSettings();
+ String clazz = settings.getString(Keys.git.sshKeysManager, FileKeyManager.class.getName());
+ if (StringUtils.isEmpty(clazz)) {
+ clazz = FileKeyManager.class.getName();
+ }
+ try {
+ Class<? extends IKeyManager> managerClass = (Class<? extends IKeyManager>) Class.forName(clazz);
+ keyManager = injector.get(managerClass).start();
+ if (keyManager.isReady()) {
+ log.info("{} is ready.", keyManager);
+ } else {
+ log.warn("{} is disabled.", keyManager);
+ }
+ } catch (Exception e) {
+ log.error("failed to create ssh key manager " + clazz, e);
+ keyManager = injector.get(NullKeyManager.class).start();
+ }
+ return keyManager;
+ }
+
+ /**
+ * A nested Dagger graph is used for constructor dependency injection of
+ * complex classes.
+ *
+ * @author James Moger
+ *
+ */
+ @Module(
+ library = true,
+ injects = {
+ NullKeyManager.class,
+ FileKeyManager.class
+ }
+ )
+ class SshModule {
+
+ @Provides @Singleton NullKeyManager provideNullKeyManager() {
+ return new NullKeyManager();
+ }
+
+ @Provides @Singleton FileKeyManager provideFileKeyManager() {
+ return new FileKeyManager(SshDaemon.this.gitblit);
+ }
+ }
}
*/
package com.gitblit.transport.ssh;
-import java.io.File;
-import java.io.IOException;
import java.security.PublicKey;
-import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
-import org.apache.commons.codec.binary.Base64;
-import org.apache.sshd.common.util.Buffer;
import org.apache.sshd.server.PublickeyAuthenticator;
import org.apache.sshd.server.session.ServerSession;
-import org.eclipse.jgit.lib.Constants;
-import com.gitblit.Keys;
-import com.gitblit.manager.IGitblit;
+import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.models.UserModel;
-import com.google.common.base.Charsets;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
-import com.google.common.io.Files;
/**
*
*/
public class SshKeyAuthenticator implements PublickeyAuthenticator {
- protected final IGitblit gitblit;
+ protected final IKeyManager keyManager;
+
+ protected final IAuthenticationManager authManager;
LoadingCache<String, List<PublicKey>> sshKeyCache = CacheBuilder
.newBuilder().
maximumSize(100)
.build(new CacheLoader<String, List<PublicKey>>() {
public List<PublicKey> load(String username) {
- try {
- File dir = gitblit.getFileOrFolder(Keys.git.sshKeysFolder, "${baseFolder}/ssh");
- dir.mkdirs();
- File keys = new File(dir, username + ".keys");
- if (!keys.exists()) {
- return null;
- }
- if (keys.exists()) {
- String str = Files.toString(keys, Charsets.ISO_8859_1);
- String [] entries = str.split("\n");
- List<PublicKey> list = new ArrayList<PublicKey>();
- for (String entry : entries) {
- final String[] parts = entry.split(" ");
- final byte[] bin = Base64.decodeBase64(Constants.encodeASCII(parts[1]));
- list.add(new Buffer(bin).getRawPublicKey());
- }
-
- if (list.isEmpty()) {
- return null;
- }
- return list;
- }
- } catch (IOException e) {
- throw new RuntimeException("Canot read public key", e);
- }
- return null;
+ return keyManager.getKeys(username);
}
});
- public SshKeyAuthenticator(IGitblit gitblit) {
- this.gitblit = gitblit;
+ public SshKeyAuthenticator(IKeyManager keyManager, IAuthenticationManager authManager) {
+ this.keyManager = keyManager;
+ this.authManager = authManager;
}
@Override
// now that the key has been validated, check with the authentication
// manager to ensure that this user exists and can authenticate
sd.authenticationSuccess(username);
- UserModel user = gitblit.authenticate(sd);
+ UserModel user = authManager.authenticate(sd);
if (user != null) {
return true;
}
import org.apache.sshd.server.PasswordAuthenticator;
import org.apache.sshd.server.session.ServerSession;
-import com.gitblit.manager.IGitblit;
+import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.models.UserModel;
/**
*/
public class SshPasswordAuthenticator implements PasswordAuthenticator {
- protected final IGitblit gitblit;
+ protected final IAuthenticationManager authManager;
- public SshPasswordAuthenticator(IGitblit gitblit) {
- this.gitblit = gitblit;
+ public SshPasswordAuthenticator(IAuthenticationManager authManager) {
+ this.authManager = authManager;
}
@Override
public boolean authenticate(String username, String password, ServerSession session) {
username = username.toLowerCase(Locale.US);
- UserModel user = gitblit.authenticate(username, password.toCharArray());
+ UserModel user = authManager.authenticate(username, password.toCharArray());
if (user != null) {
SshSession sd = session.getAttribute(SshSession.KEY);
sd.authenticationSuccess(username);