From 5d58a05a9843ec90d06ca42061ff638418f73687 Mon Sep 17 00:00:00 2001 From: David Ostrovsky Date: Sun, 16 Mar 2014 22:55:30 +0100 Subject: [PATCH] Add SSH daemon test --- src/main/distrib/data/gitblit.properties | 5 ++ .../ssh/CachingPublicKeyAuthenticator.java | 2 +- .../com/gitblit/transport/ssh/SshDaemon.java | 49 +++++++++- .../ssh/commands/DispatchCommand.java | 2 +- src/test/config/test-gitblit.properties | 2 + .../tests/BogusPublicKeyAuthenticator.java | 39 ++++++++ .../java/com/gitblit/tests/GitBlitSuite.java | 14 ++- .../java/com/gitblit/tests/SshDaemonTest.java | 90 +++++++++++++++++++ src/test/java/com/gitblit/tests/SshUtils.java | 74 +++++++++++++++ 9 files changed, 272 insertions(+), 5 deletions(-) create mode 100644 src/test/java/com/gitblit/tests/BogusPublicKeyAuthenticator.java create mode 100644 src/test/java/com/gitblit/tests/SshDaemonTest.java create mode 100644 src/test/java/com/gitblit/tests/SshUtils.java diff --git a/src/main/distrib/data/gitblit.properties b/src/main/distrib/data/gitblit.properties index 64a52f5c..52bb252b 100644 --- a/src/main/distrib/data/gitblit.properties +++ b/src/main/distrib/data/gitblit.properties @@ -129,6 +129,11 @@ git.sshKeysFolder= ${baseFolder}/ssh # SINCE 1.5.0 git.sshBackend = NIO2 +# SSH public key authenticator +# +# SINCE 1.5.0 +git.sshPublicKeyAuthenticator = com.gitblit.transport.ssh.CachingPublicKeyAuthenticator + # Allow push/pull over http/https with JGit servlet. # If you do NOT want to allow Git clients to clone/push to Gitblit set this # to false. You might want to do this if you are only using ssh:// or git://. diff --git a/src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java b/src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java index ee1de591..7d6066c7 100644 --- a/src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java +++ b/src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java @@ -73,7 +73,7 @@ public class CachingPublicKeyAuthenticator implements PublickeyAuthenticator, return result; } - private boolean doAuthenticate(String username, PublicKey suppliedKey, + protected boolean doAuthenticate(String username, PublicKey suppliedKey, ServerSession session) { SshDaemonClient client = session.getAttribute(SshDaemonClient.KEY); Preconditions.checkState(client.getUser() == null); diff --git a/src/main/java/com/gitblit/transport/ssh/SshDaemon.java b/src/main/java/com/gitblit/transport/ssh/SshDaemon.java index c954b347..40a310e7 100644 --- a/src/main/java/com/gitblit/transport/ssh/SshDaemon.java +++ b/src/main/java/com/gitblit/transport/ssh/SshDaemon.java @@ -34,6 +34,7 @@ import org.slf4j.LoggerFactory; import com.gitblit.IStoredSettings; import com.gitblit.Keys; +import com.gitblit.manager.IAuthenticationManager; import com.gitblit.manager.IGitblit; import com.gitblit.utils.IdGenerator; import com.gitblit.utils.StringUtils; @@ -104,8 +105,8 @@ public class SshDaemon { addr = new InetSocketAddress(bindInterface, port); } - CachingPublicKeyAuthenticator keyAuthenticator = - new CachingPublicKeyAuthenticator(keyManager, gitblit); + CachingPublicKeyAuthenticator keyAuthenticator = + getPublicKeyAuthenticator(keyManager, gitblit); sshd = SshServer.setUpDefaultServer(); sshd.setPort(addr.getPort()); @@ -122,6 +123,27 @@ public class SshDaemon { run = new AtomicBoolean(false); } + private CachingPublicKeyAuthenticator getPublicKeyAuthenticator( + IKeyManager keyManager, IGitblit gitblit) { + IStoredSettings settings = gitblit.getSettings(); + String clazz = settings.getString(Keys.git.sshPublicKeyAuthenticator, + CachingPublicKeyAuthenticator.class.getName()); + if (StringUtils.isEmpty(clazz)) { + clazz = CachingPublicKeyAuthenticator.class.getName(); + } + try { + Class authClass = + (Class) Class.forName(clazz); + return authClass.getConstructor( + new Class[] { IKeyManager.class, + IAuthenticationManager.class }).newInstance( + keyManager, gitblit); + } catch (Exception e) { + log.error("failed to create ssh auth manager " + clazz, e); + } + return null; + } + public String formatUrl(String gituser, String servername, String repository) { if (sshd.getPort() == DEFAULT_PORT) { // standard port @@ -200,6 +222,29 @@ public class SshDaemon { return keyManager; } + @SuppressWarnings("unchecked") + protected IKeyManager getKeyAuthenticator() { + 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 managerClass = (Class) 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. diff --git a/src/main/java/com/gitblit/transport/ssh/commands/DispatchCommand.java b/src/main/java/com/gitblit/transport/ssh/commands/DispatchCommand.java index 3c041af6..8e13be03 100644 --- a/src/main/java/com/gitblit/transport/ssh/commands/DispatchCommand.java +++ b/src/main/java/com/gitblit/transport/ssh/commands/DispatchCommand.java @@ -79,7 +79,7 @@ public class DispatchCommand extends BaseCommand { CommandMetaData.class.getName())); } CommandMetaData meta = cmd.getAnnotation(CommandMetaData.class); - if (meta.admin() && user.canAdmin()) { + if (meta.admin() && user != null && user.canAdmin()) { log.debug(MessageFormat.format("excluding admin command {0} for {1}", meta.name(), user.username)); return; } diff --git a/src/test/config/test-gitblit.properties b/src/test/config/test-gitblit.properties index e636469e..7d8e9a79 100644 --- a/src/test/config/test-gitblit.properties +++ b/src/test/config/test-gitblit.properties @@ -7,6 +7,8 @@ git.repositoriesFolder = ${baseFolder}/git git.searchRepositoriesSubfolders = true git.enableGitServlet = true git.daemonPort = 8300 +git.sshPort = 29418 +git.sshPublicKeyAuthenticator = com.gitblit.tests.BogusPublicKeyAuthenticator groovy.scriptsFolder = src/main/distrib/data/groovy groovy.preReceiveScripts = blockpush groovy.postReceiveScripts = sendmail diff --git a/src/test/java/com/gitblit/tests/BogusPublicKeyAuthenticator.java b/src/test/java/com/gitblit/tests/BogusPublicKeyAuthenticator.java new file mode 100644 index 00000000..80be1a01 --- /dev/null +++ b/src/test/java/com/gitblit/tests/BogusPublicKeyAuthenticator.java @@ -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.tests; + +import java.security.PublicKey; + +import org.apache.sshd.server.session.ServerSession; + +import com.gitblit.manager.IAuthenticationManager; +import com.gitblit.transport.ssh.CachingPublicKeyAuthenticator; +import com.gitblit.transport.ssh.IKeyManager; + +public class BogusPublicKeyAuthenticator extends CachingPublicKeyAuthenticator { + + public BogusPublicKeyAuthenticator(IKeyManager keyManager, + IAuthenticationManager authManager) { + super(keyManager, authManager); + } + + @Override + protected boolean doAuthenticate(String username, PublicKey suppliedKey, + ServerSession session) { + // TODO(davido): put authenticated user in session + return true; + } +} diff --git a/src/test/java/com/gitblit/tests/GitBlitSuite.java b/src/test/java/com/gitblit/tests/GitBlitSuite.java index c015c847..17d609e7 100644 --- a/src/test/java/com/gitblit/tests/GitBlitSuite.java +++ b/src/test/java/com/gitblit/tests/GitBlitSuite.java @@ -61,7 +61,7 @@ import com.gitblit.utils.JGitUtils; MarkdownUtilsTest.class, JGitUtilsTest.class, SyndicationUtilsTest.class, DiffUtilsTest.class, MetricUtilsTest.class, X509UtilsTest.class, GitBlitTest.class, FederationTests.class, RpcTests.class, GitServletTest.class, GitDaemonTest.class, - GroovyScriptTest.class, LuceneExecutorTest.class, RepositoryModelTest.class, + GroovyScriptTest.class, LuceneExecutorTest.class, RepositoryModelTest.class, SshDaemonTest.class, FanoutServiceTest.class, Issue0259Test.class, Issue0271Test.class, HtpasswdAuthenticationTest.class, ModelUtilsTest.class, JnaUtilsTest.class, LdapSyncServiceTest.class, FileTicketServiceTest.class, BranchTicketServiceTest.class, RedisTicketServiceTest.class, AuthenticationManagerTest.class }) @@ -78,6 +78,16 @@ public class GitBlitSuite { static int port = 8280; static int gitPort = 8300; static int shutdownPort = 8281; + static int sshPort = 29418; + +// Overriding of keys doesn't seem to work +// static { +// try { +// sshPort = SshUtils.getFreePort(); +// } catch (Exception e) { +// e.printStackTrace(); +// } +// } public static String url = "http://localhost:" + port; public static String gitServletUrl = "http://localhost:" + port + "/git"; @@ -140,6 +150,8 @@ public class GitBlitSuite { "\"" + GitBlitSuite.REPOSITORIES.getAbsolutePath() + "\"", "--userService", GitBlitSuite.USERSCONF.getAbsolutePath(), "--settings", GitBlitSuite.SETTINGS.getAbsolutePath(), "--baseFolder", "data"); + // doesn't work + //, "--sshPort", "" + sshPort); } }); diff --git a/src/test/java/com/gitblit/tests/SshDaemonTest.java b/src/test/java/com/gitblit/tests/SshDaemonTest.java new file mode 100644 index 00000000..5294f691 --- /dev/null +++ b/src/test/java/com/gitblit/tests/SshDaemonTest.java @@ -0,0 +1,90 @@ +/* + * 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.tests; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.security.KeyPair; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.sshd.ClientChannel; +import org.apache.sshd.ClientSession; +import org.apache.sshd.SshClient; +import org.apache.sshd.common.KeyPairProvider; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.gitblit.Constants; + +public class SshDaemonTest extends GitblitUnitTest { + + private static final AtomicBoolean started = new AtomicBoolean(false); + private static KeyPair pair; + + @BeforeClass + public static void startGitblit() throws Exception { + started.set(GitBlitSuite.startGitblit()); + pair = SshUtils.createTestHostKeyProvider().loadKey(KeyPairProvider.SSH_RSA); + } + + @AfterClass + public static void stopGitblit() throws Exception { + if (started.get()) { + GitBlitSuite.stopGitblit(); + } + } + + @Test + public void testPublicKeyAuthentication() throws Exception { + SshClient client = SshClient.setUpDefaultClient(); + client.start(); + ClientSession session = client.connect("localhost", GitBlitSuite.sshPort).await().getSession(); + pair.getPublic().getEncoded(); + assertTrue(session.authPublicKey("admin", pair).await().isSuccess()); + } + + @Test + public void testVersionCommand() throws Exception { + SshClient client = SshClient.setUpDefaultClient(); + client.start(); + ClientSession session = client.connect("localhost", GitBlitSuite.sshPort).await().getSession(); + pair.getPublic().getEncoded(); + assertTrue(session.authPublicKey("admin", pair).await().isSuccess()); + + ClientChannel channel = session.createChannel(ClientChannel.CHANNEL_EXEC, "gitblit version"); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Writer w = new OutputStreamWriter(baos); + w.close(); + channel.setIn(new ByteArrayInputStream(baos.toByteArray())); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + channel.setOut(out); + channel.setErr(err); + channel.open(); + + channel.waitFor(ClientChannel.CLOSED, 0); + + String result = out.toString().trim(); + channel.close(false); + client.stop(); + + assertEquals(Constants.getGitBlitVersion(), result); + } +} diff --git a/src/test/java/com/gitblit/tests/SshUtils.java b/src/test/java/com/gitblit/tests/SshUtils.java new file mode 100644 index 00000000..9760f755 --- /dev/null +++ b/src/test/java/com/gitblit/tests/SshUtils.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.tests; + +import java.io.File; +import java.net.ServerSocket; +import java.net.URISyntaxException; +import java.net.URL; + +import org.apache.sshd.common.KeyPairProvider; +import org.apache.sshd.common.keyprovider.FileKeyPairProvider; +import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider; + +public class SshUtils { + + public static KeyPairProvider createTestHostKeyProvider() { + return new SimpleGeneratorHostKeyProvider("target/hostkey.rsa", "RSA"); + } + + public static FileKeyPairProvider createTestKeyPairProvider(String resource) { + return new FileKeyPairProvider(new String[] { getFile(resource) }); + } + + public static int getFreePort() throws Exception { + ServerSocket s = new ServerSocket(0); + try { + return s.getLocalPort(); + } finally { + s.close(); + } + } + + private static String getFile(String resource) { + URL url = SshUtils.class.getClassLoader().getResource(resource); + File f; + try { + f = new File(url.toURI()); + } catch(URISyntaxException e) { + f = new File(url.getPath()); + } + return f.toString(); + } + + public static void deleteRecursive(File file) { + if (file != null) { + if (file.isDirectory()) { + File[] children = file.listFiles(); + if (children != null) { + for (File child : children) { + deleteRecursive(child); + } + } + } + file.delete(); + } + } + +} -- 2.39.5