535acf84a735674203b2cab4709d9a7a90d5a09e
[netconf.git] / transport / transport-ssh / src / main / java / org / opendaylight / netconf / transport / ssh / TransportSshServer.java
1 /*
2  * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.netconf.transport.ssh;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.collect.ImmutableList;
13 import com.google.common.collect.ImmutableMap;
14 import com.google.errorprone.annotations.DoNotCall;
15 import java.security.PublicKey;
16 import java.util.List;
17 import java.util.concurrent.ScheduledExecutorService;
18 import org.opendaylight.netconf.shaded.sshd.common.channel.ChannelFactory;
19 import org.opendaylight.netconf.shaded.sshd.common.keyprovider.KeyPairProvider;
20 import org.opendaylight.netconf.shaded.sshd.netty.NettyIoServiceFactoryFactory;
21 import org.opendaylight.netconf.shaded.sshd.server.ServerBuilder;
22 import org.opendaylight.netconf.shaded.sshd.server.SshServer;
23 import org.opendaylight.netconf.shaded.sshd.server.auth.UserAuthFactory;
24 import org.opendaylight.netconf.shaded.sshd.server.auth.hostbased.UserAuthHostBasedFactory;
25 import org.opendaylight.netconf.shaded.sshd.server.auth.password.UserAuthPasswordFactory;
26 import org.opendaylight.netconf.shaded.sshd.server.auth.pubkey.UserAuthPublicKeyFactory;
27 import org.opendaylight.netconf.shaded.sshd.server.forward.DirectTcpipFactory;
28 import org.opendaylight.netconf.transport.api.UnsupportedConfigurationException;
29 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev231228.SshServerGrouping;
30 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev231228.ssh.server.grouping.ClientAuthentication;
31 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev231228.ssh.server.grouping.Keepalives;
32 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev231228.ssh.server.grouping.ServerIdentity;
33
34 /**
35  * Our internal-use {@link SshServer}. We reuse all the properties and logic of an {@link SshServer}, but we never allow
36  * it to be started.
37  */
38 final class TransportSshServer extends SshServer {
39     private TransportSshServer() {
40         // Hidden on purpose
41     }
42
43     /**
44      * Guaranteed to throw an exception.
45      *
46      * @throws UnsupportedOperationException always
47      */
48     @Override
49     @Deprecated(forRemoval = true)
50     @DoNotCall("Always throws UnsupportedOperationException")
51     public void start() {
52         throw new UnsupportedOperationException();
53     }
54
55     /**
56      * Guaranteed to throw an exception.
57      *
58      * @throws UnsupportedOperationException always
59      */
60     @Override
61     @Deprecated(forRemoval = true)
62     @DoNotCall("Always throws UnsupportedOperationException")
63     public void stop() {
64         throw new UnsupportedOperationException();
65     }
66
67     /**
68      * A {@link ServerBuilder} producing {@link TransportSshServer}s. Also hosts adaptation from
69      * {@code ietf-netconf-server.yang} configuration.
70      */
71     static final class Builder extends ServerBuilder {
72         private static final List<ChannelFactory> CHANNEL_FACTORIES = List.of(
73             TransportChannelSessionFactory.INSTANCE,
74             DirectTcpipFactory.INSTANCE);
75
76         private final NettyIoServiceFactoryFactory ioServiceFactory;
77         private final ScheduledExecutorService executorService;
78
79         private ServerFactoryManagerConfigurator configurator;
80         private ClientAuthentication clientAuthentication;
81         private ServerIdentity serverIdentity;
82         private Keepalives keepAlives;
83
84         Builder(final NettyIoServiceFactoryFactory ioServiceFactory, final ScheduledExecutorService executorService) {
85             this.ioServiceFactory = requireNonNull(ioServiceFactory);
86             this.executorService = requireNonNull(executorService);
87         }
88
89         Builder serverParams(final SshServerGrouping serverParams) throws UnsupportedConfigurationException {
90             if (serverParams != null) {
91                 ConfigUtils.setTransportParams(this, serverParams.getTransportParams(),
92                     TransportUtils::getServerKexFactories);
93                 keepAlives = serverParams.getKeepalives();
94                 serverIdentity = serverParams.getServerIdentity();
95                 if (serverIdentity == null) {
96                     throw new UnsupportedConfigurationException("Server identity configuration is required");
97                 }
98                 clientAuthentication = serverParams.getClientAuthentication();
99             }
100             return this;
101         }
102
103         Builder configurator(final ServerFactoryManagerConfigurator newConfigurator) {
104             configurator = newConfigurator;
105             return this;
106         }
107
108         /**
109          * Guaranteed to throw an exception.
110          *
111          * @throws UnsupportedOperationException always
112          */
113         @Override
114         @Deprecated(forRemoval = true)
115         @DoNotCall("Always throws UnsupportedOperationException")
116         public TransportSshServer build() {
117             throw new UnsupportedOperationException();
118         }
119
120         /**
121          * Guaranteed to throw an exception.
122          *
123          * @throws UnsupportedOperationException always
124          */
125         @Override
126         @Deprecated(forRemoval = true)
127         @DoNotCall("Always throws UnsupportedOperationException")
128         public TransportSshServer build(final boolean isFillWithDefaultValues) {
129             throw new UnsupportedOperationException();
130         }
131
132         TransportSshServer buildChecked() throws UnsupportedConfigurationException {
133             final var ret = (TransportSshServer) super.build(true);
134             if (keepAlives != null) {
135                 ConfigUtils.setKeepAlives(ret, keepAlives.getMaxWait(), keepAlives.getMaxAttempts());
136             } else {
137                 ConfigUtils.setKeepAlives(ret, null, null);
138             }
139             if (serverIdentity != null) {
140                 setServerIdentity(ret, serverIdentity);
141             }
142             if (clientAuthentication != null) {
143                 setClientAuthentication(ret, clientAuthentication);
144             }
145             if (configurator != null) {
146                 configurator.configureServerFactoryManager(ret);
147             }
148
149             ret.setIoServiceFactoryFactory(ioServiceFactory);
150             ret.setScheduledExecutorService(executorService);
151
152             try {
153                 ret.checkConfig();
154             } catch (IllegalArgumentException e) {
155                 throw new UnsupportedConfigurationException("Inconsistent client configuration", e);
156             }
157
158             ret.setSessionFactory(new TransportServerSessionFactory(ret));
159             return ret;
160         }
161
162         @Override
163         protected ServerBuilder fillWithDefaultValues() {
164             if (channelFactories == null) {
165                 channelFactories = CHANNEL_FACTORIES;
166             }
167             if (factory == null) {
168                 factory = TransportSshServer::new;
169             }
170             return super.fillWithDefaultValues();
171         }
172
173         private static void setServerIdentity(final TransportSshServer server, final ServerIdentity serverIdentity)
174                 throws UnsupportedConfigurationException {
175             final var hostKey = serverIdentity.getHostKey();
176             if (hostKey == null || hostKey.isEmpty()) {
177                 throw new UnsupportedConfigurationException("Host keys is missing in server identity configuration");
178             }
179             final var serverHostKeyPairs = ConfigUtils.extractServerHostKeys(hostKey);
180             if (!serverHostKeyPairs.isEmpty()) {
181                 server.setKeyPairProvider(KeyPairProvider.wrap(serverHostKeyPairs));
182             }
183         }
184
185         private static void setClientAuthentication(final TransportSshServer server,
186                 final ClientAuthentication clientAuthentication) throws UnsupportedConfigurationException {
187             final var users = clientAuthentication.getUsers();
188             if (users == null) {
189                 return;
190             }
191             final var userMap = users.getUser();
192             if (userMap != null) {
193                 final var passwordMapBuilder = ImmutableMap.<String, String>builder();
194                 final var hostBasedMapBuilder = ImmutableMap.<String, List<PublicKey>>builder();
195                 final var publicKeyMapBuilder = ImmutableMap.<String, List<PublicKey>>builder();
196                 for (var entry : userMap.entrySet()) {
197                     final var username = entry.getKey().getName();
198                     final var value = entry.getValue();
199                     final var password = value.getPassword();
200                     if (password != null) {
201                         passwordMapBuilder.put(username, password.getValue());
202                     }
203                     final var hostBased = value.getHostbased();
204                     if (hostBased != null) {
205                         hostBasedMapBuilder.put(username,
206                             ConfigUtils.extractPublicKeys(hostBased.getInlineOrTruststore()));
207                     }
208                     final var publicKey = value.getPublicKeys();
209                     if (publicKey != null) {
210                         publicKeyMapBuilder.put(username,
211                             ConfigUtils.extractPublicKeys(publicKey.getInlineOrTruststore()));
212                     }
213                 }
214                 final var authFactoriesBuilder = ImmutableList.<UserAuthFactory>builder();
215                 final var passwordMap = passwordMapBuilder.build();
216                 if (!passwordMap.isEmpty()) {
217                     authFactoriesBuilder.add(new UserAuthPasswordFactory());
218                     server.setPasswordAuthenticator(new CryptHashPasswordAuthenticator(passwordMap));
219                 }
220                 final var hostBasedMap = hostBasedMapBuilder.build();
221                 if (!hostBasedMap.isEmpty()) {
222                     final var factory = new UserAuthHostBasedFactory();
223                     factory.setSignatureFactories(server.getSignatureFactories());
224                     authFactoriesBuilder.add(factory);
225                     server.setHostBasedAuthenticator(new UserPublicKeyAuthenticator(hostBasedMap));
226                 }
227                 final var publicKeyMap = publicKeyMapBuilder.build();
228                 if (!publicKeyMap.isEmpty()) {
229                     final var factory = new UserAuthPublicKeyFactory();
230                     factory.setSignatureFactories(server.getSignatureFactories());
231                     authFactoriesBuilder.add(factory);
232                     server.setPublickeyAuthenticator(new UserPublicKeyAuthenticator(publicKeyMap));
233                 }
234                 server.setUserAuthFactories(authFactoriesBuilder.build());
235             }
236         }
237     }
238 }