David Ostrovsky
2014-03-16 5d58a05a9843ec90d06ca42061ff638418f73687
Add SSH daemon test
3 files added
6 files modified
277 ■■■■■ changed files
src/main/distrib/data/gitblit.properties 5 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/transport/ssh/SshDaemon.java 49 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/transport/ssh/commands/DispatchCommand.java 2 ●●● patch | view | raw | blame | history
src/test/config/test-gitblit.properties 2 ●●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/BogusPublicKeyAuthenticator.java 39 ●●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/GitBlitSuite.java 14 ●●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/SshDaemonTest.java 90 ●●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/SshUtils.java 74 ●●●●● patch | view | raw | blame | history
src/main/distrib/data/gitblit.properties
@@ -129,6 +129,11 @@
# 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://.
src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java
@@ -73,7 +73,7 @@
        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);
src/main/java/com/gitblit/transport/ssh/SshDaemon.java
@@ -34,6 +34,7 @@
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 @@
            addr = new InetSocketAddress(bindInterface, port);
        }
        CachingPublicKeyAuthenticator keyAuthenticator =
                new CachingPublicKeyAuthenticator(keyManager, gitblit);
        CachingPublicKeyAuthenticator keyAuthenticator =
                getPublicKeyAuthenticator(keyManager, gitblit);
        sshd = SshServer.setUpDefaultServer();
        sshd.setPort(addr.getPort());
@@ -120,6 +121,27 @@
        sshd.setCommandFactory(new SshCommandFactory(gitblit, keyAuthenticator, idGenerator));
        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<CachingPublicKeyAuthenticator> authClass =
                    (Class<CachingPublicKeyAuthenticator>) 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) {
@@ -200,6 +222,29 @@
        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<? 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.
src/main/java/com/gitblit/transport/ssh/commands/DispatchCommand.java
@@ -79,7 +79,7 @@
                    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;
        }
src/test/config/test-gitblit.properties
@@ -7,6 +7,8 @@
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
src/test/java/com/gitblit/tests/BogusPublicKeyAuthenticator.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.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;
    }
}
src/test/java/com/gitblit/tests/GitBlitSuite.java
@@ -61,7 +61,7 @@
        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 @@
    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 @@
                        "\"" + GitBlitSuite.REPOSITORIES.getAbsolutePath() + "\"", "--userService",
                        GitBlitSuite.USERSCONF.getAbsolutePath(), "--settings", GitBlitSuite.SETTINGS.getAbsolutePath(),
                        "--baseFolder", "data");
                // doesn't work
                //, "--sshPort", "" + sshPort);
            }
        });
src/test/java/com/gitblit/tests/SshDaemonTest.java
New file
@@ -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);
     }
}
src/test/java/com/gitblit/tests/SshUtils.java
New file
@@ -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();
        }
    }
}