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.server;
10 import static org.mockito.ArgumentMatchers.any;
11 import static org.mockito.Mockito.timeout;
12 import static org.mockito.Mockito.verify;
14 import com.google.common.util.concurrent.ListenableFuture;
15 import io.netty.channel.Channel;
16 import io.netty.channel.EventLoopGroup;
17 import java.net.InetAddress;
18 import java.net.ServerSocket;
19 import java.security.KeyPairGenerator;
20 import java.util.List;
22 import java.util.concurrent.TimeUnit;
23 import org.junit.jupiter.api.AfterAll;
24 import org.junit.jupiter.api.BeforeAll;
25 import org.junit.jupiter.api.BeforeEach;
26 import org.junit.jupiter.api.Test;
27 import org.junit.jupiter.api.extension.ExtendWith;
28 import org.mockito.Mock;
29 import org.mockito.junit.jupiter.MockitoExtension;
30 import org.opendaylight.netconf.server.api.NetconfServerFactory;
31 import org.opendaylight.netconf.shaded.sshd.server.auth.password.UserAuthPasswordFactory;
32 import org.opendaylight.netconf.shaded.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
33 import org.opendaylight.netconf.transport.api.TransportChannel;
34 import org.opendaylight.netconf.transport.api.TransportChannelListener;
35 import org.opendaylight.netconf.transport.ssh.SSHServer;
36 import org.opendaylight.netconf.transport.ssh.SSHTransportStackFactory;
37 import org.opendaylight.netconf.transport.tcp.NettyTransportSupport;
38 import org.opendaylight.netconf.transport.tcp.TCPClient;
39 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.crypt.hash.rev140806.CryptHash;
40 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev230417.RsaPrivateKeyFormat;
41 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev230417.SubjectPublicKeyInfoFormat;
42 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev230417.asymmetric.key.pair.grouping._private.key.type.CleartextPrivateKeyBuilder;
43 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev230417.password.grouping.password.type.CleartextPasswordBuilder;
44 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Host;
45 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IetfInetUtil;
46 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.PortNumber;
47 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.keystore.rev230417.inline.or.keystore.asymmetric.key.grouping.inline.or.keystore.InlineBuilder;
48 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.keystore.rev230417.inline.or.keystore.asymmetric.key.grouping.inline.or.keystore.inline.InlineDefinitionBuilder;
49 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.client.rev230417.netconf.client.initiate.stack.grouping.transport.ssh.ssh.SshClientParameters;
50 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.client.rev230417.netconf.client.initiate.stack.grouping.transport.ssh.ssh.SshClientParametersBuilder;
51 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.client.rev230417.netconf.client.initiate.stack.grouping.transport.ssh.ssh.TcpClientParametersBuilder;
52 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.server.rev230417.netconf.server.listen.stack.grouping.transport.ssh.ssh.SshServerParametersBuilder;
53 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.server.rev230417.netconf.server.listen.stack.grouping.transport.ssh.ssh.TcpServerParametersBuilder;
54 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev230417.ssh.client.grouping.ClientIdentityBuilder;
55 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev230417.ssh.client.grouping.client.identity.PasswordBuilder;
56 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev230417.ssh.server.grouping.ClientAuthenticationBuilder;
57 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev230417.ssh.server.grouping.ServerIdentity;
58 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev230417.ssh.server.grouping.ServerIdentityBuilder;
59 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev230417.ssh.server.grouping.client.authentication.UsersBuilder;
60 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev230417.ssh.server.grouping.client.authentication.users.UserBuilder;
61 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev230417.ssh.server.grouping.server.identity.HostKeyBuilder;
62 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev230417.ssh.server.grouping.server.identity.host.key.host.key.type.PublicKeyBuilder;
63 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tcp.client.rev230417.TcpClientGrouping;
64 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.tcp.server.rev230417.TcpServerGrouping;
65 import org.opendaylight.yangtools.yang.common.Uint16;
67 @ExtendWith(MockitoExtension.class)
68 class NetconfServerFactoryImplTest {
69 private static final String USERNAME = "username";
70 private static final String PASSWORD = "pa$$w0rd";
71 private static final String RSA = "RSA";
73 private static SSHTransportStackFactory FACTORY;
75 private static EventLoopGroup clientGroup;
77 private ServerChannelInitializer serverChannelInitializer;
79 private TransportChannelListener clientListener;
81 private NetconfServerFactory factory;
82 private TcpServerGrouping tcpServerParams;
83 private TcpClientGrouping tcpClientParams;
86 static void beforeAll() {
87 FACTORY = new SSHTransportStackFactory("testThreads", 0);
88 clientGroup = NettyTransportSupport.newEventLoopGroup("client");
92 static void afterAll() {
94 clientGroup.shutdownGracefully();
98 void beforeEach() throws Exception {
99 factory = new NetconfServerFactoryImpl(serverChannelInitializer, FACTORY);
101 // create temp socket to get available port for test
102 final var socket = new ServerSocket(0);
103 final var address = IetfInetUtil.ipAddressFor(InetAddress.getLoopbackAddress());
104 final var port = new PortNumber(Uint16.valueOf(socket.getLocalPort()));
107 tcpServerParams = new TcpServerParametersBuilder().setLocalAddress(address).setLocalPort(port).build();
109 new TcpClientParametersBuilder().setRemoteAddress(new Host(address)).setRemotePort(port).build();
113 void tcpServer() throws Exception {
114 final var server = factory.createTcpServer(tcpServerParams).get(1, TimeUnit.SECONDS);
116 final var client = TCPClient.connect(clientListener,
117 NettyTransportSupport.newBootstrap().group(clientGroup), tcpClientParams).get(1, TimeUnit.SECONDS);
119 verify(serverChannelInitializer, timeout(1000L)).initialize(any(Channel.class), any());
120 verify(clientListener, timeout(1000L)).onTransportChannelEstablished(any(TransportChannel.class));
122 client.shutdown().get(1, TimeUnit.SECONDS);
125 server.shutdown().get(1, TimeUnit.SECONDS);
130 void sshServer() throws Exception {
131 final var user = new UserBuilder().setName(USERNAME).setPassword(new CryptHash("$0$" + PASSWORD)).build();
132 final var sshServerConfig = new SshServerParametersBuilder()
133 .setServerIdentity(buildSshServerIdentityWithKeyPair())
134 .setClientAuthentication(
135 new ClientAuthenticationBuilder().setUsers(
136 new UsersBuilder().setUser(Map.of(user.key(), user)).build()
140 assertSshServer(factory.createSshServer(tcpServerParams, sshServerConfig), sshClientParams());
144 void sshServerExtInitializer() throws Exception {
145 assertSshServer(factory.createSshServer(tcpServerParams, null, factoryManager -> {
146 factoryManager.setUserAuthFactories(List.of(new UserAuthPasswordFactory()));
147 factoryManager.setPasswordAuthenticator((username, password, session) -> true);
148 factoryManager.setKeyPairProvider(new SimpleGeneratorHostKeyProvider());
149 }), sshClientParams());
152 void assertSshServer(final ListenableFuture<SSHServer> serverFuture, final SshClientParameters sshClientParams)
154 final var server = serverFuture.get(2, TimeUnit.SECONDS);
156 final var client = FACTORY.connectClient("netconf", clientListener, tcpClientParams, sshClientParams)
157 .get(2, TimeUnit.SECONDS);
159 // FIXME commented line requires netconf client to trigger netconf subsystem initialization on server
160 // verify(serverChannelInitializer, timeout(10_000L)).initialize(any(Channel.class), any());
161 verify(clientListener, timeout(10_000L)).onTransportChannelEstablished(any(TransportChannel.class));
163 client.shutdown().get(2, TimeUnit.SECONDS);
166 server.shutdown().get(2, TimeUnit.SECONDS);
170 private static ServerIdentity buildSshServerIdentityWithKeyPair() throws Exception {
171 final var keyPair = KeyPairGenerator.getInstance(RSA).generateKeyPair();
172 final var inlineDef = new InlineDefinitionBuilder()
173 .setPublicKeyFormat(SubjectPublicKeyInfoFormat.VALUE)
174 .setPublicKey(keyPair.getPublic().getEncoded())
175 .setPrivateKeyFormat(RsaPrivateKeyFormat.VALUE)
177 new CleartextPrivateKeyBuilder().setCleartextPrivateKey(
178 keyPair.getPrivate().getEncoded()
181 final var inline = new InlineBuilder().setInlineDefinition(inlineDef).build();
182 var publicKey = new PublicKeyBuilder().setPublicKey(
183 new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev230417
184 .ssh.server.grouping.server.identity.host.key.host.key.type._public.key
185 .PublicKeyBuilder().setInlineOrKeystore(inline).build()
187 return new ServerIdentityBuilder().setHostKey(
188 List.of(new HostKeyBuilder().setName("test-name").setHostKeyType(publicKey).build())
192 private static SshClientParameters sshClientParams() {
193 return new SshClientParametersBuilder().setClientIdentity(
194 new ClientIdentityBuilder().setUsername(USERNAME).setPassword(
195 new PasswordBuilder().setPasswordType(
196 new CleartextPasswordBuilder().setCleartextPassword(PASSWORD).build()