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 com.google.common.collect.ImmutableList;
11 import com.google.common.collect.ImmutableMap;
12 import com.google.errorprone.annotations.DoNotCall;
13 import java.security.PublicKey;
14 import java.util.List;
15 import org.opendaylight.netconf.shaded.sshd.common.keyprovider.KeyPairProvider;
16 import org.opendaylight.netconf.shaded.sshd.server.ServerBuilder;
17 import org.opendaylight.netconf.shaded.sshd.server.SshServer;
18 import org.opendaylight.netconf.shaded.sshd.server.auth.UserAuthFactory;
19 import org.opendaylight.netconf.shaded.sshd.server.auth.hostbased.UserAuthHostBasedFactory;
20 import org.opendaylight.netconf.shaded.sshd.server.auth.password.UserAuthPasswordFactory;
21 import org.opendaylight.netconf.shaded.sshd.server.auth.pubkey.UserAuthPublicKeyFactory;
22 import org.opendaylight.netconf.transport.api.UnsupportedConfigurationException;
23 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev230417.SshServerGrouping;
24 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev230417.ssh.server.grouping.ClientAuthentication;
25 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev230417.ssh.server.grouping.Keepalives;
26 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev230417.ssh.server.grouping.ServerIdentity;
29 * Our internal-use {@link SshServer}. We reuse all the properties and logic of an {@link SshServer}, but we never allow
32 final class TransportSshServer extends SshServer {
33 private TransportSshServer() {
38 * Guaranteed to throw an exception.
40 * @throws UnsupportedOperationException always
43 @Deprecated(forRemoval = true)
44 @DoNotCall("Always throws UnsupportedOperationException")
46 throw new UnsupportedOperationException();
50 * Guaranteed to throw an exception.
52 * @throws UnsupportedOperationException always
55 @Deprecated(forRemoval = true)
56 @DoNotCall("Always throws UnsupportedOperationException")
58 throw new UnsupportedOperationException();
62 * A {@link ServerBuilder} producing {@link TransportSshServer}s. Also hosts adaptation from
63 * {@code ietf-netconf-server.yang} configuration.
65 static final class Builder extends ServerBuilder {
66 private ServerFactoryManagerConfigurator configurator;
67 private ClientAuthentication clientAuthentication;
68 private ServerIdentity serverIdentity;
69 private Keepalives keepAlives;
71 Builder serverParams(final SshServerGrouping serverParams) throws UnsupportedConfigurationException {
72 if (serverParams != null) {
73 ConfigUtils.setTransportParams(this, serverParams.getTransportParams(),
74 TransportUtils::getServerKexFactories);
75 keepAlives = serverParams.getKeepalives();
76 serverIdentity = serverParams.getServerIdentity();
77 if (serverIdentity == null) {
78 throw new UnsupportedConfigurationException("Server identity configuration is required");
80 clientAuthentication = serverParams.getClientAuthentication();
85 Builder configurator(final ServerFactoryManagerConfigurator newConfigurator) {
86 configurator = newConfigurator;
91 * Guaranteed to throw an exception.
93 * @throws UnsupportedOperationException always
96 @Deprecated(forRemoval = true)
97 @DoNotCall("Always throws UnsupportedOperationException")
98 public TransportSshServer build() {
99 throw new UnsupportedOperationException();
103 * Guaranteed to throw an exception.
105 * @throws UnsupportedOperationException always
108 @Deprecated(forRemoval = true)
109 @DoNotCall("Always throws UnsupportedOperationException")
110 public TransportSshServer build(final boolean isFillWithDefaultValues) {
111 throw new UnsupportedOperationException();
114 TransportSshServer buildChecked() throws UnsupportedConfigurationException {
115 final var ret = (TransportSshServer) super.build(true);
116 if (keepAlives != null) {
117 ConfigUtils.setKeepAlives(ret, keepAlives.getMaxWait(), keepAlives.getMaxAttempts());
119 ConfigUtils.setKeepAlives(ret, null, null);
121 if (serverIdentity != null) {
122 setServerIdentity(ret, serverIdentity);
124 if (clientAuthentication != null) {
125 setClientAuthentication(ret, clientAuthentication);
127 if (configurator != null) {
128 configurator.configureServerFactoryManager(ret);
131 // FIXME: this is the default added by checkConfig(), but we really want to use an EventLoopGroup for this
132 // ret.setScheduledExecutorService(group);
136 } catch (IllegalArgumentException e) {
137 throw new UnsupportedConfigurationException("Inconsistent client configuration", e);
143 protected ServerBuilder fillWithDefaultValues() {
144 if (factory == null) {
145 factory = TransportSshServer::new;
147 return super.fillWithDefaultValues();
150 private static void setServerIdentity(final TransportSshServer server, final ServerIdentity serverIdentity)
151 throws UnsupportedConfigurationException {
152 final var hostKey = serverIdentity.getHostKey();
153 if (hostKey == null || hostKey.isEmpty()) {
154 throw new UnsupportedConfigurationException("Host keys is missing in server identity configuration");
156 final var serverHostKeyPairs = ConfigUtils.extractServerHostKeys(hostKey);
157 if (!serverHostKeyPairs.isEmpty()) {
158 server.setKeyPairProvider(KeyPairProvider.wrap(serverHostKeyPairs));
162 private static void setClientAuthentication(final TransportSshServer server,
163 final ClientAuthentication clientAuthentication) throws UnsupportedConfigurationException {
164 final var users = clientAuthentication.getUsers();
168 final var userMap = users.getUser();
169 if (userMap != null) {
170 final var passwordMapBuilder = ImmutableMap.<String, String>builder();
171 final var hostBasedMapBuilder = ImmutableMap.<String, List<PublicKey>>builder();
172 final var publicKeyMapBuilder = ImmutableMap.<String, List<PublicKey>>builder();
173 for (var entry : userMap.entrySet()) {
174 final var username = entry.getKey().getName();
175 final var value = entry.getValue();
176 final var password = value.getPassword();
177 if (password != null) {
178 passwordMapBuilder.put(username, password.getValue());
180 final var hostBased = value.getHostbased();
181 if (hostBased != null) {
182 hostBasedMapBuilder.put(username,
183 ConfigUtils.extractPublicKeys(hostBased.getInlineOrTruststore()));
185 final var publicKey = value.getPublicKeys();
186 if (publicKey != null) {
187 publicKeyMapBuilder.put(username,
188 ConfigUtils.extractPublicKeys(publicKey.getInlineOrTruststore()));
191 final var authFactoriesBuilder = ImmutableList.<UserAuthFactory>builder();
192 final var passwordMap = passwordMapBuilder.build();
193 if (!passwordMap.isEmpty()) {
194 authFactoriesBuilder.add(new UserAuthPasswordFactory());
195 server.setPasswordAuthenticator(new CryptHashPasswordAuthenticator(passwordMap));
197 final var hostBasedMap = hostBasedMapBuilder.build();
198 if (!hostBasedMap.isEmpty()) {
199 final var factory = new UserAuthHostBasedFactory();
200 factory.setSignatureFactories(server.getSignatureFactories());
201 authFactoriesBuilder.add(factory);
202 server.setHostBasedAuthenticator(new UserPublicKeyAuthenticator(hostBasedMap));
204 final var publicKeyMap = publicKeyMapBuilder.build();
205 if (!publicKeyMap.isEmpty()) {
206 final var factory = new UserAuthPublicKeyFactory();
207 factory.setSignatureFactories(server.getSignatureFactories());
208 authFactoriesBuilder.add(factory);
209 server.setPublickeyAuthenticator(new UserPublicKeyAuthenticator(publicKeyMap));
211 server.setUserAuthFactories(authFactoriesBuilder.build());