2 * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.netconf.transport.ssh;
10 import static java.util.Objects.requireNonNull;
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;
35 * Our internal-use {@link SshServer}. We reuse all the properties and logic of an {@link SshServer}, but we never allow
38 final class TransportSshServer extends SshServer {
39 private TransportSshServer() {
44 * Guaranteed to throw an exception.
46 * @throws UnsupportedOperationException always
49 @Deprecated(forRemoval = true)
50 @DoNotCall("Always throws UnsupportedOperationException")
52 throw new UnsupportedOperationException();
56 * Guaranteed to throw an exception.
58 * @throws UnsupportedOperationException always
61 @Deprecated(forRemoval = true)
62 @DoNotCall("Always throws UnsupportedOperationException")
64 throw new UnsupportedOperationException();
68 * A {@link ServerBuilder} producing {@link TransportSshServer}s. Also hosts adaptation from
69 * {@code ietf-netconf-server.yang} configuration.
71 static final class Builder extends ServerBuilder {
72 private static final List<ChannelFactory> CHANNEL_FACTORIES = List.of(
73 TransportChannelSessionFactory.INSTANCE,
74 DirectTcpipFactory.INSTANCE);
76 private final NettyIoServiceFactoryFactory ioServiceFactory;
77 private final ScheduledExecutorService executorService;
79 private ServerFactoryManagerConfigurator configurator;
80 private ClientAuthentication clientAuthentication;
81 private ServerIdentity serverIdentity;
82 private Keepalives keepAlives;
84 Builder(final NettyIoServiceFactoryFactory ioServiceFactory, final ScheduledExecutorService executorService) {
85 this.ioServiceFactory = requireNonNull(ioServiceFactory);
86 this.executorService = requireNonNull(executorService);
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");
98 clientAuthentication = serverParams.getClientAuthentication();
103 Builder configurator(final ServerFactoryManagerConfigurator newConfigurator) {
104 configurator = newConfigurator;
109 * Guaranteed to throw an exception.
111 * @throws UnsupportedOperationException always
114 @Deprecated(forRemoval = true)
115 @DoNotCall("Always throws UnsupportedOperationException")
116 public TransportSshServer build() {
117 throw new UnsupportedOperationException();
121 * Guaranteed to throw an exception.
123 * @throws UnsupportedOperationException always
126 @Deprecated(forRemoval = true)
127 @DoNotCall("Always throws UnsupportedOperationException")
128 public TransportSshServer build(final boolean isFillWithDefaultValues) {
129 throw new UnsupportedOperationException();
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());
137 ConfigUtils.setKeepAlives(ret, null, null);
139 if (serverIdentity != null) {
140 setServerIdentity(ret, serverIdentity);
142 if (clientAuthentication != null) {
143 setClientAuthentication(ret, clientAuthentication);
145 if (configurator != null) {
146 configurator.configureServerFactoryManager(ret);
149 ret.setIoServiceFactoryFactory(ioServiceFactory);
150 ret.setScheduledExecutorService(executorService);
154 } catch (IllegalArgumentException e) {
155 throw new UnsupportedConfigurationException("Inconsistent client configuration", e);
158 ret.setSessionFactory(new TransportServerSessionFactory(ret));
163 protected ServerBuilder fillWithDefaultValues() {
164 if (channelFactories == null) {
165 channelFactories = CHANNEL_FACTORIES;
167 if (factory == null) {
168 factory = TransportSshServer::new;
170 return super.fillWithDefaultValues();
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");
179 final var serverHostKeyPairs = ConfigUtils.extractServerHostKeys(hostKey);
180 if (!serverHostKeyPairs.isEmpty()) {
181 server.setKeyPairProvider(KeyPairProvider.wrap(serverHostKeyPairs));
185 private static void setClientAuthentication(final TransportSshServer server,
186 final ClientAuthentication clientAuthentication) throws UnsupportedConfigurationException {
187 final var users = clientAuthentication.getUsers();
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());
203 final var hostBased = value.getHostbased();
204 if (hostBased != null) {
205 hostBasedMapBuilder.put(username,
206 ConfigUtils.extractPublicKeys(hostBased.getInlineOrTruststore()));
208 final var publicKey = value.getPublicKeys();
209 if (publicKey != null) {
210 publicKeyMapBuilder.put(username,
211 ConfigUtils.extractPublicKeys(publicKey.getInlineOrTruststore()));
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));
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));
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));
234 server.setUserAuthFactories(authFactoriesBuilder.build());