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