src/main/distrib/data/gitblit.properties | ●●●●● patch | view | raw | blame | history | |
src/main/java/com/gitblit/transport/ssh/FileKeyManager.java | ●●●●● patch | view | raw | blame | history | |
src/main/java/com/gitblit/transport/ssh/IKeyManager.java | ●●●●● patch | view | raw | blame | history | |
src/main/java/com/gitblit/transport/ssh/NullKeyManager.java | ●●●●● patch | view | raw | blame | history | |
src/main/java/com/gitblit/transport/ssh/SshDaemon.java | ●●●●● patch | view | raw | blame | history | |
src/main/java/com/gitblit/transport/ssh/SshKeyAuthenticator.java | ●●●●● patch | view | raw | blame | history | |
src/main/java/com/gitblit/transport/ssh/SshPasswordAuthenticator.java | ●●●●● patch | view | raw | blame | history |
src/main/distrib/data/gitblit.properties
@@ -110,7 +110,16 @@ # RESTART REQUIRED git.sshBindInterface = # Directory for storing user SSH keys. # Specify the SSH key manager to use for retrieving, storing, and removing # SSH keys. # # Valid key managers are: # com.gitblit.transport.ssh.FileKeyManager # # SINCE 1.5.0 git.sshKeysManager = com.gitblit.transport.ssh.FileKeyManager # Directory for storing user SSH keys when using the FileKeyManager. # # SINCE 1.5.0 git.sshKeysFolder= ${baseFolder}/ssh src/main/java/com/gitblit/transport/ssh/FileKeyManager.java
New file @@ -0,0 +1,154 @@ /* * 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; } } src/main/java/com/gitblit/transport/ssh/IKeyManager.java
New file @@ -0,0 +1,39 @@ /* * 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); } src/main/java/com/gitblit/transport/ssh/NullKeyManager.java
New file @@ -0,0 +1,66 @@ /* * 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; } } src/main/java/com/gitblit/transport/ssh/SshDaemon.java
@@ -21,6 +21,8 @@ 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; @@ -41,6 +43,10 @@ import com.gitblit.utils.IdGenerator; 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 @@ -65,9 +71,9 @@ private final AtomicBoolean run; @SuppressWarnings("unused") private final IGitblit gitblit; private final SshServer sshd; private final ObjectGraph injector; /** * Construct the Gitblit SSH daemon. @@ -76,11 +82,14 @@ */ 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)) { @@ -94,7 +103,7 @@ 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()); @@ -176,4 +185,51 @@ } } } 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); } } } src/main/java/com/gitblit/transport/ssh/SshKeyAuthenticator.java
@@ -15,29 +15,20 @@ */ 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; /** * @@ -46,7 +37,9 @@ */ public class SshKeyAuthenticator implements PublickeyAuthenticator { protected final IGitblit gitblit; protected final IKeyManager keyManager; protected final IAuthenticationManager authManager; LoadingCache<String, List<PublicKey>> sshKeyCache = CacheBuilder .newBuilder(). @@ -54,37 +47,13 @@ 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 @@ -115,7 +84,7 @@ // 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; } src/main/java/com/gitblit/transport/ssh/SshPasswordAuthenticator.java
@@ -20,7 +20,7 @@ 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; /** @@ -30,16 +30,16 @@ */ 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);