ae23b251540f1f3a3896d620c9b323c72f4dd08b
[netconf.git] / protocol / netconf-server / src / test / java / org / opendaylight / netconf / server / NetconfServerFactoryImplTest.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.server;
9
10 import static org.mockito.ArgumentMatchers.any;
11 import static org.mockito.Mockito.timeout;
12 import static org.mockito.Mockito.verify;
13
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;
21 import java.util.Map;
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;
66
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";
72
73     private static SSHTransportStackFactory FACTORY;
74
75     private static EventLoopGroup clientGroup;
76     @Mock
77     private ServerChannelInitializer serverChannelInitializer;
78     @Mock
79     private TransportChannelListener clientListener;
80
81     private NetconfServerFactory factory;
82     private TcpServerGrouping tcpServerParams;
83     private TcpClientGrouping tcpClientParams;
84
85     @BeforeAll
86     static void beforeAll() {
87         FACTORY = new SSHTransportStackFactory("testThreads", 0);
88         clientGroup = NettyTransportSupport.newEventLoopGroup("client");
89     }
90
91     @AfterAll
92     static void afterAll() {
93         FACTORY.close();
94         clientGroup.shutdownGracefully();
95     }
96
97     @BeforeEach
98     void beforeEach() throws Exception {
99         factory = new NetconfServerFactoryImpl(serverChannelInitializer, FACTORY);
100
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()));
105         socket.close();
106
107         tcpServerParams = new TcpServerParametersBuilder().setLocalAddress(address).setLocalPort(port).build();
108         tcpClientParams =
109             new TcpClientParametersBuilder().setRemoteAddress(new Host(address)).setRemotePort(port).build();
110     }
111
112     @Test
113     void tcpServer() throws Exception {
114         final var server = factory.createTcpServer(tcpServerParams).get(1, TimeUnit.SECONDS);
115         try {
116             final var client = TCPClient.connect(clientListener,
117                 NettyTransportSupport.newBootstrap().group(clientGroup), tcpClientParams).get(1, TimeUnit.SECONDS);
118             try {
119                 verify(serverChannelInitializer, timeout(1000L)).initialize(any(Channel.class), any());
120                 verify(clientListener, timeout(1000L)).onTransportChannelEstablished(any(TransportChannel.class));
121             } finally {
122                 client.shutdown().get(1, TimeUnit.SECONDS);
123             }
124         } finally {
125             server.shutdown().get(1, TimeUnit.SECONDS);
126         }
127     }
128
129     @Test
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()
137                 ).build()
138             ).build();
139
140         assertSshServer(factory.createSshServer(tcpServerParams, sshServerConfig), sshClientParams());
141     }
142
143     @Test
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());
150     }
151
152     void assertSshServer(final ListenableFuture<SSHServer> serverFuture, final SshClientParameters sshClientParams)
153         throws Exception {
154         final var server = serverFuture.get(2, TimeUnit.SECONDS);
155         try {
156             final var client = FACTORY.connectClient("netconf", clientListener, tcpClientParams, sshClientParams)
157                 .get(2, TimeUnit.SECONDS);
158             try {
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));
162             } finally {
163                 client.shutdown().get(2, TimeUnit.SECONDS);
164             }
165         } finally {
166             server.shutdown().get(2, TimeUnit.SECONDS);
167         }
168     }
169
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)
176             .setPrivateKeyType(
177                 new CleartextPrivateKeyBuilder().setCleartextPrivateKey(
178                     keyPair.getPrivate().getEncoded()
179                 ).build()
180             ).build();
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()
186         ).build();
187         return new ServerIdentityBuilder().setHostKey(
188             List.of(new HostKeyBuilder().setName("test-name").setHostKeyType(publicKey).build())
189         ).build();
190     }
191
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()
197                 ).build()
198             ).build()
199         ).build();
200     }
201 }