All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.seedstack.seed.shell.internal.ShellPlugin Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (c) 2013-2015 by The SeedStack authors. All rights reserved.
 *
 * This file is part of SeedStack, An enterprise-oriented full development stack.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
package org.seedstack.seed.shell.internal;

import org.seedstack.seed.core.internal.application.ApplicationPlugin;
import io.nuun.kernel.api.Plugin;
import io.nuun.kernel.api.plugin.InitState;
import io.nuun.kernel.api.plugin.PluginException;
import io.nuun.kernel.api.plugin.context.Context;
import io.nuun.kernel.api.plugin.context.InitContext;
import io.nuun.kernel.core.AbstractPlugin;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ThreadContext;
import org.apache.sshd.SshServer;
import org.apache.sshd.common.Factory;
import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
import org.apache.sshd.common.keyprovider.ResourceKeyPairProvider;
import org.apache.sshd.common.util.Buffer;
import org.apache.sshd.server.Command;
import org.apache.sshd.server.CommandFactory;
import org.apache.sshd.server.UserAuth;
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
import org.apache.sshd.server.session.ServerSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.inject.Named;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * This plugin provides shell-style interaction over SSH with the SEED application.
 *
 * @author [email protected]
 */
public class ShellPlugin extends AbstractPlugin {
    private static final int SHELL_DEFAULT_PORT = 2222;
    private static final String SHELL_PLUGIN_CONFIGURATION_PREFIX = "org.seedstack.seed.shell";
    private static final Logger LOGGER = LoggerFactory.getLogger(ShellPlugin.class);

    private int port;
    private SshServer sshServer;

    @Inject
    private ShellFactory shellFactory;

    @Inject
    @Named("shellSecurityManager")
    private SecurityManager securityManager;

    @Override
    public String name() {
        return "seed-shell-plugin";
    }

    @Override
    public InitState init(InitContext initContext) {
        ApplicationPlugin applicationPlugin = (ApplicationPlugin) initContext.pluginsRequired().iterator().next();
        org.apache.commons.configuration.Configuration shellConfiguration = applicationPlugin.getApplication().getConfiguration().subset(ShellPlugin.SHELL_PLUGIN_CONFIGURATION_PREFIX);

        // No need to go further if shell is not enabled
        if (!shellConfiguration.getBoolean("enabled", false)) {
            LOGGER.info("Shell support is present in the classpath but not enabled");
            return InitState.INITIALIZED;
        }

        port = shellConfiguration.getInt("port", SHELL_DEFAULT_PORT);
        sshServer = SshServer.setUpDefaultServer();
        sshServer.setPort(port);

        String keyType = shellConfiguration.getString("key.type", "generated");
        if ("generated".equals(keyType)) {
            File storage;
            try {
                storage = applicationPlugin.getApplication().getStorageLocation("shell");
            } catch (IOException e) {
                throw new PluginException("Unable to acces storage location for context shell", e);
            }
            sshServer.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(new File(storage, "generate-key.ser").getAbsolutePath()));
        } else if ("file".equals(keyType)) {
            sshServer.setKeyPairProvider(new FileKeyPairProvider(new String[]{shellConfiguration.getString("key.location")}));
        } else if ("resource".equals(keyType)) {
            sshServer.setKeyPairProvider(new ResourceKeyPairProvider(new String[]{shellConfiguration.getString("key.location")}));
        }
        sshServer.setShellFactory(new Factory() {
            @Override
            public Command create() {
                return shellFactory.createInteractiveShell();
            }
        });

        sshServer.setCommandFactory(new CommandFactory() {
            @Override
            public Command createCommand(String command) {
                return shellFactory.createNonInteractiveShell(command);
            }
        });

        return InitState.INITIALIZED;
    }

    @Override
    public void start(Context context) {
        if (sshServer == null) {
            return;
        }

        List> userAuthFactories = new ArrayList>();
        userAuthFactories.add(new ShiroAuthFactory());
        sshServer.setUserAuthFactories(userAuthFactories);

        LOGGER.info("Starting SSH server on port " + this.port);
        try {
            sshServer.start();
        } catch (IOException e) {
            throw new PluginException("Unable to start SSH server on port " + this.port, e);
        }
    }

    @Override
    public void stop() {
        if (sshServer == null) {
            return;
        }

        LOGGER.info("Stopping SSH server");
        try {
            sshServer.stop();
        } catch (InterruptedException e) {
            throw new PluginException("Unable to cleanly stop SSH server", e);
        }
    }

    @Override
    public Object nativeUnitModule() {
        return new ShellModule();
    }

    @Override
    public Collection> requiredPlugins() {
        Collection> plugins = new ArrayList>();
        plugins.add(ApplicationPlugin.class);
        return plugins;
    }

    private final class ShiroAuthFactory implements NamedFactory {
        @Override
        public String getName() {
            return "password";
        }

        @Override
        public UserAuth create() {
            return new UserAuth() {
                @Override
                public Boolean auth(ServerSession session, String username, Buffer buffer) {
                    boolean newPassword = buffer.getBoolean();
                    if (newPassword) {
                        throw new IllegalStateException("password changes are not supported");
                    }

                    Subject subject = new Subject.Builder(securityManager).sessionId(session.getSessionId()).buildSubject();

                    try {
                        subject.login(new UsernamePasswordToken(username, buffer.getString()));
                    } catch (AuthenticationException e) {
                        LOGGER.warn("shell access denied to user " + username, e);
                        return false;
                    }

                    ThreadContext.bind(subject);
                    ThreadContext.bind(securityManager);

                    return true;
                }
            };
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy