From: Mathieu Baudier Date: Wed, 6 Jul 2022 14:23:40 +0000 (+0200) Subject: Introduce CMS SSH server X-Git-Tag: v2.3.10~134 X-Git-Url: https://git.argeo.org/?a=commitdiff_plain;h=85e2381d80c313e2d7467e7d022d54036f736a1d;p=lgpl%2Fargeo-commons.git Introduce CMS SSH server --- diff --git a/org.argeo.cms.ssh/.project b/org.argeo.cms.ssh/.project index 6b0c9dd49..d46b3afef 100644 --- a/org.argeo.cms.ssh/.project +++ b/org.argeo.cms.ssh/.project @@ -20,6 +20,11 @@ + + org.eclipse.pde.ds.core.builder + + + org.eclipse.pde.PluginNature diff --git a/org.argeo.cms.ssh/OSGI-INF/cmsSshServer.xml b/org.argeo.cms.ssh/OSGI-INF/cmsSshServer.xml new file mode 100644 index 000000000..aa2d8db2d --- /dev/null +++ b/org.argeo.cms.ssh/OSGI-INF/cmsSshServer.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/org.argeo.cms.ssh/bnd.bnd b/org.argeo.cms.ssh/bnd.bnd index e69de29bb..e708391a7 100644 --- a/org.argeo.cms.ssh/bnd.bnd +++ b/org.argeo.cms.ssh/bnd.bnd @@ -0,0 +1,7 @@ +Import-Package: \ +org.apache.sshd.server.forward,\ +org.apache.sshd.common.forward,\ +* + +Service-Component: \ +OSGI-INF/cmsSshServer.xml diff --git a/org.argeo.cms.ssh/build.properties b/org.argeo.cms.ssh/build.properties index 17273d1ff..1e3132aac 100644 --- a/org.argeo.cms.ssh/build.properties +++ b/org.argeo.cms.ssh/build.properties @@ -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 index 000000000..7246d603d --- /dev/null +++ b/org.argeo.cms.ssh/src/org/argeo/cms/ssh/CmsSshServer.java @@ -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; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/CmsDeployProperty.java b/org.argeo.cms/src/org/argeo/cms/CmsDeployProperty.java index 639e73e37..ee3ec040d 100644 --- a/org.argeo.cms/src/org/argeo/cms/CmsDeployProperty.java +++ b/org.argeo.cms/src/org/argeo/cms/CmsDeployProperty.java @@ -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. */