1 package org
.argeo
.cms
.ssh
;
3 import java
.io
.IOException
;
4 import java
.io
.InputStream
;
6 import java
.net
.InetSocketAddress
;
7 import java
.nio
.charset
.StandardCharsets
;
8 import java
.nio
.file
.Files
;
9 import java
.nio
.file
.Path
;
10 import java
.nio
.file
.Paths
;
11 import java
.nio
.file
.StandardOpenOption
;
12 import java
.nio
.file
.attribute
.PosixFilePermission
;
13 import java
.security
.KeyPair
;
14 import java
.security
.KeyStore
;
15 import java
.security
.KeyStoreException
;
16 import java
.security
.NoSuchAlgorithmException
;
17 import java
.security
.NoSuchProviderException
;
18 import java
.security
.PrivateKey
;
19 import java
.security
.UnrecoverableKeyException
;
20 import java
.security
.cert
.CertificateException
;
21 import java
.util
.Collections
;
22 import java
.util
.HashSet
;
25 import org
.apache
.sshd
.common
.config
.keys
.PublicKeyEntry
;
26 import org
.apache
.sshd
.common
.forward
.PortForwardingEventListener
;
27 import org
.apache
.sshd
.common
.keyprovider
.KeyPairProvider
;
28 import org
.apache
.sshd
.common
.session
.Session
;
29 import org
.apache
.sshd
.common
.util
.net
.SshdSocketAddress
;
30 import org
.apache
.sshd
.scp
.server
.ScpCommandFactory
;
31 import org
.apache
.sshd
.server
.SshServer
;
32 import org
.apache
.sshd
.server
.auth
.gss
.GSSAuthenticator
;
33 import org
.apache
.sshd
.server
.config
.keys
.AuthorizedKeysAuthenticator
;
34 import org
.apache
.sshd
.server
.config
.keys
.DefaultAuthorizedKeysAuthenticator
;
35 import org
.apache
.sshd
.server
.forward
.AcceptAllForwardingFilter
;
36 import org
.apache
.sshd
.server
.jaas
.JaasPasswordAuthenticator
;
37 import org
.apache
.sshd
.server
.keyprovider
.SimpleGeneratorHostKeyProvider
;
38 import org
.apache
.sshd
.server
.shell
.InteractiveProcessShellFactory
;
39 import org
.apache
.sshd
.sftp
.server
.SftpSubsystemFactory
;
40 import org
.argeo
.api
.cms
.CmsAuth
;
41 import org
.argeo
.api
.cms
.CmsConstants
;
42 import org
.argeo
.api
.cms
.CmsLog
;
43 import org
.argeo
.api
.cms
.CmsSshd
;
44 import org
.argeo
.api
.cms
.CmsState
;
45 import org
.argeo
.cms
.CmsDeployProperty
;
47 public class CmsSshServer
implements CmsSshd
{
48 private final static CmsLog log
= CmsLog
.getLog(CmsSshServer
.class);
50 private CmsState cmsState
;
51 private SshServer sshd
= null;
57 String portStr
= cmsState
.getDeployProperty(CmsDeployProperty
.SSHD_PORT
.getProperty());
60 port
= Integer
.parseInt(portStr
);
62 host
= cmsState
.getDeployProperty(CmsDeployProperty
.HOST
.getProperty());
64 KeyPair nodeKeyPair
= loadNodeKeyPair();
68 String authorizedKeysStr
= cmsState
.getDeployProperty(CmsDeployProperty
.SSHD_AUTHORIZEDKEYS
.getProperty());
69 Path authorizedKeysPath
= authorizedKeysStr
!= null ? Paths
.get(authorizedKeysStr
)
70 : AuthorizedKeysAuthenticator
.getDefaultAuthorizedKeysFile();
71 if (authorizedKeysStr
!= null && !Files
.exists(authorizedKeysPath
)) {
72 Files
.createFile(authorizedKeysPath
);
73 Set
<PosixFilePermission
> posixPermissions
= new HashSet
<>();
74 posixPermissions
.add(PosixFilePermission
.OWNER_READ
);
75 posixPermissions
.add(PosixFilePermission
.OWNER_WRITE
);
76 Files
.setPosixFilePermissions(authorizedKeysPath
, posixPermissions
);
78 if (nodeKeyPair
!= null)
80 String openSsshPublicKey
= PublicKeyEntry
.toString(nodeKeyPair
.getPublic());
81 try (Writer writer
= Files
.newBufferedWriter(authorizedKeysPath
, StandardCharsets
.US_ASCII
,
82 StandardOpenOption
.APPEND
)) {
83 writer
.write(openSsshPublicKey
);
85 } catch (IOException e
) {
86 log
.error("Cannot add node public key to SSH authorized keys", e
);
91 sshd
= SshServer
.setUpDefaultServer();
97 if (nodeKeyPair
!= null) {
98 sshd
.setKeyPairProvider(KeyPairProvider
.wrap(nodeKeyPair
));
100 Path hostKeyPath
= cmsState
.getDataPath(DEFAULT_SSH_HOST_KEY_PATH
);
101 if (hostKeyPath
== null) // TODO deal with no data area?
102 throw new IllegalStateException("An SSH server key must be set");
103 sshd
.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(hostKeyPath
));
107 sshd
.setForwardingFilter(AcceptAllForwardingFilter
.INSTANCE
);
108 sshd
.addPortForwardingEventListener(new PortForwardingEventListener() {
111 public void establishingExplicitTunnel(Session session
, SshdSocketAddress local
,
112 SshdSocketAddress remote
, boolean localForwarding
) throws IOException
{
113 log
.debug("Establishing tunnel " + local
+ ", " + remote
);
117 public void establishedExplicitTunnel(Session session
, SshdSocketAddress local
,
118 SshdSocketAddress remote
, boolean localForwarding
, SshdSocketAddress boundAddress
,
119 Throwable reason
) throws IOException
{
120 log
.debug("Established tunnel " + local
+ ", " + remote
+ ", " + boundAddress
);
124 public void establishingDynamicTunnel(Session session
, SshdSocketAddress local
) throws IOException
{
125 log
.debug("Establishing dynamic tunnel " + local
);
129 public void establishedDynamicTunnel(Session session
, SshdSocketAddress local
,
130 SshdSocketAddress boundAddress
, Throwable reason
) throws IOException
{
131 log
.debug("Established dynamic tunnel " + local
);
137 // FIXME use strict, set proper permissions, etc.
138 sshd
.setPublickeyAuthenticator(
139 new DefaultAuthorizedKeysAuthenticator("user.name", authorizedKeysPath
, true));
140 // sshd.setPublickeyAuthenticator(null);
141 // sshd.setKeyboardInteractiveAuthenticator(null);
142 JaasPasswordAuthenticator jaasPasswordAuthenticator
= new JaasPasswordAuthenticator();
143 jaasPasswordAuthenticator
.setDomain(CmsAuth
.NODE
.getLoginContextName());
144 sshd
.setPasswordAuthenticator(jaasPasswordAuthenticator
);
146 boolean gssApi
= false;
148 Path krb5keyTab
= cmsState
.getDataPath("private/krb5.keytab");
149 if (Files
.exists(krb5keyTab
)) {
150 // FIXME experimental
151 GSSAuthenticator gssAuthenticator
= new GSSAuthenticator();
152 gssAuthenticator
.setKeytabFile(krb5keyTab
.toString());
153 gssAuthenticator
.setServicePrincipalName("HTTP@" + host
);
154 sshd
.setGSSAuthenticator(gssAuthenticator
);
159 // TODO make it configurable
160 sshd
.setShellFactory(InteractiveProcessShellFactory
.INSTANCE
);
161 // String[] shellCommand = OS.LOCAL.getDefaultShellCommand();
162 // StringJoiner command = new StringJoiner(" ");
163 // for (String str : shellCommand) {
166 // sshd.setShellFactory(new ProcessShellFactory(command.toString(), shellCommand));
167 sshd
.setCommandFactory(new ScpCommandFactory());
170 sshd
.setSubsystemFactories(Collections
.singletonList(new SftpSubsystemFactory()));
175 log
.info("# SSH " + (host
!= null ? host
+ ":" : "*:") + port
);
176 } catch (IOException e
) {
177 throw new RuntimeException("Cannot start SSH server on port " + port
, e
);
187 log
.debug(() -> "CMS SSH server stopped on port " + port
+ (host
!= null ?
" of host " + host
: ""));
188 } catch (IOException e
) {
189 throw new RuntimeException("Cannot stop SSH server", e
);
194 protected KeyPair
loadNodeKeyPair() {
196 char[] keyStorePassword
= cmsState
.getDeployProperty(CmsDeployProperty
.SSL_PASSWORD
.getProperty())
198 Path keyStorePath
= Paths
.get(cmsState
.getDeployProperty(CmsDeployProperty
.SSL_KEYSTORE
.getProperty()));
199 String keyStoreType
= cmsState
.getDeployProperty(CmsDeployProperty
.SSL_KEYSTORETYPE
.getProperty());
201 KeyStore store
= KeyStore
.getInstance(keyStoreType
, "SunJSSE");
202 try (InputStream fis
= Files
.newInputStream(keyStorePath
)) {
203 store
.load(fis
, keyStorePassword
);
205 return new KeyPair(store
.getCertificate(CmsConstants
.NODE
).getPublicKey(),
206 (PrivateKey
) store
.getKey(CmsConstants
.NODE
, keyStorePassword
));
207 } catch (IOException
| KeyStoreException
| NoSuchProviderException
| NoSuchAlgorithmException
208 | CertificateException
| IllegalArgumentException
| UnrecoverableKeyException e
) {
209 if (log
.isTraceEnabled())
210 log
.warn("Cannot add node public key to SSH authorized keys", e
);
212 log
.warn("Cannot add node public key to SSH authorized keys: " + e
);
218 public void setCmsState(CmsState cmsState
) {
219 this.cmsState
= cmsState
;
223 public InetSocketAddress
getAddress() {
224 return new InetSocketAddress(host
, port
);