Introduce CMS SSH server
authorMathieu Baudier <mbaudier@argeo.org>
Wed, 6 Jul 2022 14:23:40 +0000 (16:23 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Wed, 6 Jul 2022 14:23:40 +0000 (16:23 +0200)
org.argeo.cms.ssh/.project
org.argeo.cms.ssh/OSGI-INF/cmsSshServer.xml [new file with mode: 0644]
org.argeo.cms.ssh/bnd.bnd
org.argeo.cms.ssh/build.properties
org.argeo.cms.ssh/src/org/argeo/cms/ssh/CmsSshServer.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/CmsDeployProperty.java

index 6b0c9dd49273aaeb1333c5668f429515756b646d..d46b3afefa4c2950f9cfbb443c7508735caf5b9e 100644 (file)
                        <arguments>
                        </arguments>
                </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ds.core.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
        </buildSpec>
        <natures>
                <nature>org.eclipse.pde.PluginNature</nature>
diff --git a/org.argeo.cms.ssh/OSGI-INF/cmsSshServer.xml b/org.argeo.cms.ssh/OSGI-INF/cmsSshServer.xml
new file mode 100644 (file)
index 0000000..aa2d8db
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" name="CMS SSH Server" immediate="true">
+   <implementation class="org.argeo.cms.ssh.CmsSshServer"/>
+   <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
+</scr:component>
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e708391a70b41bc7535c393b4d9f5b92a98ac188 100644 (file)
@@ -0,0 +1,7 @@
+Import-Package: \
+org.apache.sshd.server.forward,\
+org.apache.sshd.common.forward,\
+*
+
+Service-Component: \
+OSGI-INF/cmsSshServer.xml
index 17273d1ff17e95cb3e7eebe3f08a7436f7152482..1e3132aac043913b2160b83e3ba462269bc91071 100644 (file)
@@ -1,9 +1,9 @@
-source.. = src/
 output.. = bin/
 bin.includes = META-INF/,\
-               .
+               .,\
+               OSGI-INF/
+source.. = src/
 additional.bundles = org.apache.sshd.common,\
                      org.apache.sshd.core,\
                      org.slf4j.api,\
                      org.argeo.ext.slf4j
-               
\ No newline at end of file
diff --git a/org.argeo.cms.ssh/src/org/argeo/cms/ssh/CmsSshServer.java b/org.argeo.cms.ssh/src/org/argeo/cms/ssh/CmsSshServer.java
new file mode 100644 (file)
index 0000000..7246d60
--- /dev/null
@@ -0,0 +1,142 @@
+package org.argeo.cms.ssh;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+
+import org.apache.sshd.common.forward.PortForwardingEventListener;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.util.net.SshdSocketAddress;
+import org.apache.sshd.scp.server.ScpCommandFactory;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.auth.gss.GSSAuthenticator;
+import org.apache.sshd.server.config.keys.DefaultAuthorizedKeysAuthenticator;
+import org.apache.sshd.server.forward.AcceptAllForwardingFilter;
+import org.apache.sshd.server.jaas.JaasPasswordAuthenticator;
+import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
+import org.apache.sshd.server.shell.InteractiveProcessShellFactory;
+import org.apache.sshd.sftp.server.SftpSubsystemFactory;
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsState;
+import org.argeo.cms.CmsDeployProperty;
+
+public class CmsSshServer {
+       private final static CmsLog log = CmsLog.getLog(CmsSshServer.class);
+       private static final String DEFAULT_SSH_HOST_KEY_PATH = CmsConstants.NODE + '/' + CmsConstants.NODE + ".ser";
+
+       private CmsState cmsState;
+       private SshServer sshd = null;
+
+       private int port;
+       private String host;
+
+       public void start() {
+               String portStr = cmsState.getDeployProperty(CmsDeployProperty.SSHD_PORT.getProperty());
+               if (portStr == null)
+                       return; // ignore
+               port = Integer.parseInt(portStr);
+
+               host = cmsState.getDeployProperty(CmsDeployProperty.HOST.getProperty());
+
+               Path hostKeyPath = cmsState.getDataPath(DEFAULT_SSH_HOST_KEY_PATH);
+
+               try {
+                       sshd = SshServer.setUpDefaultServer();
+                       sshd.setPort(port);
+                       if (host != null)
+                               sshd.setHost(host);
+                       if (hostKeyPath == null)
+                               throw new IllegalStateException("An SSH server key must be set");
+                       sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(hostKeyPath));
+                       // sshd.setShellFactory(new ProcessShellFactory(new String[] { "/bin/sh", "-i",
+                       // "-l" }));
+//                     String[] shellCommand = OS.LOCAL.getDefaultShellCommand();
+                       // FIXME transfer args
+//             sshd.setShellFactory(new ProcessShellFactory(shellCommand));
+                       sshd.setShellFactory(InteractiveProcessShellFactory.INSTANCE);
+                       sshd.setCommandFactory(new ScpCommandFactory());
+
+                       // tunnels
+                       sshd.setForwardingFilter(AcceptAllForwardingFilter.INSTANCE);
+                       // sshd.setForwardingFilter(ForwardingFilter.asForwardingFilter(null, null,
+                       // TcpForwardingFilter.DEFAULT));
+                       // sshd.setForwarderFactory(DefaultForwarderFactory.INSTANCE);
+//                     TcpForwardingFilter tcpForwardingFilter = sshd.getTcpForwardingFilter();
+                       sshd.addPortForwardingEventListener(new PortForwardingEventListener() {
+
+                               @Override
+                               public void establishingExplicitTunnel(Session session, SshdSocketAddress local,
+                                               SshdSocketAddress remote, boolean localForwarding) throws IOException {
+                                       log.debug("Establishing tunnel " + local + ", " + remote);
+                               }
+
+                               @Override
+                               public void establishedExplicitTunnel(Session session, SshdSocketAddress local,
+                                               SshdSocketAddress remote, boolean localForwarding, SshdSocketAddress boundAddress,
+                                               Throwable reason) throws IOException {
+                                       log.debug("Established tunnel " + local + ", " + remote + ", " + boundAddress);
+                               }
+
+                               @Override
+                               public void establishingDynamicTunnel(Session session, SshdSocketAddress local) throws IOException {
+                                       log.debug("Establishing dynamic tunnel " + local);
+                               }
+
+                               @Override
+                               public void establishedDynamicTunnel(Session session, SshdSocketAddress local,
+                                               SshdSocketAddress boundAddress, Throwable reason) throws IOException {
+                                       log.debug("Established dynamic tunnel " + local);
+                               }
+
+                       });
+
+                       // Authentication
+                       //sshd.setPublickeyAuthenticator(new DefaultAuthorizedKeysAuthenticator(true));
+                       sshd.setPublickeyAuthenticator(null);
+                       // sshd.setKeyboardInteractiveAuthenticator(null);
+                       JaasPasswordAuthenticator jaasPasswordAuthenticator = new JaasPasswordAuthenticator();
+                       jaasPasswordAuthenticator.setDomain(CmsAuth.NODE.getLoginContextName());
+                       sshd.setPasswordAuthenticator(jaasPasswordAuthenticator);
+
+                       Path krb5keyTab = cmsState.getDataPath("node/krb5.keytab");
+                       if (Files.exists(krb5keyTab)) {
+                               // FIXME experimental
+                               GSSAuthenticator gssAuthenticator = new GSSAuthenticator();
+                               gssAuthenticator.setKeytabFile(cmsState.getDataPath("node/krb5.keytab").toString());
+                               gssAuthenticator.setServicePrincipalName("HTTP@" + host);
+                               sshd.setGSSAuthenticator(gssAuthenticator);
+                       }
+
+                       // SFTP
+                       sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
+
+                       // start
+                       sshd.start();
+
+                       log.debug(() -> "CMS SSH server started on port " + port + (host != null ? " of host " + host : ""));
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot start SSH server on port " + port, e);
+               }
+
+       }
+
+       public void stop() {
+               if (sshd == null)
+                       return;
+               try {
+                       sshd.stop();
+                       log.debug(() -> "CMS SSH server stopped on port " + port + (host != null ? " of host " + host : ""));
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot stop SSH server", e);
+               }
+
+       }
+
+       public void setCmsState(CmsState cmsState) {
+               this.cmsState = cmsState;
+       }
+
+}
index 639e73e37a59e2f5fecdff0b19f989624d1ec53a..ee3ec040da7a126cbee9fd96bc389ac41b40fb72 100644 (file)
@@ -67,6 +67,11 @@ public enum CmsDeployProperty {
        /** Whether web socket should be enables in web server. */
        WEBSOCKET_ENABLED("argeo.websocket.enabled"),
        //
+       // SSH
+       //
+       /** Request an HTTP server on this port. */
+       SSHD_PORT("argeo.sshd.port"),
+       //
        // INTERNATIONALIZATION
        //
        /** Locales enabled for this system, the first one is considered the default. */