Honeynode test tool
[transportpce.git] / tests / honeynode / netconf / src / main / java / io / fd / honeycomb / northbound / netconf / NetconfSshServerProvider.java
1 /*
2  * Copyright (c) 2017 Cisco and/or its affiliates.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at:
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package io.fd.honeycomb.northbound.netconf;
18
19 import com.google.common.net.InetAddresses;
20 import com.google.common.util.concurrent.ThreadFactoryBuilder;
21 import com.google.inject.Inject;
22 import io.fd.honeycomb.binding.init.ProviderTrait;
23 import io.fd.honeycomb.infra.distro.InitializationException;
24 import io.fd.honeycomb.northbound.CredentialsConfiguration;
25 import io.fd.honeycomb.northbound.NetconfConfiguration;
26 import io.netty.channel.ChannelFuture;
27 import io.netty.channel.local.LocalAddress;
28 import io.netty.channel.nio.NioEventLoopGroup;
29 import io.netty.util.concurrent.GenericFutureListener;
30 import io.netty.util.concurrent.GlobalEventExecutor;
31 import java.io.IOException;
32 import java.net.InetAddress;
33 import java.net.InetSocketAddress;
34 import java.util.concurrent.Executors;
35 import java.util.concurrent.ScheduledExecutorService;
36 import org.apache.sshd.server.keyprovider.PEMGeneratorHostKeyProvider;
37 import org.opendaylight.netconf.api.NetconfServerDispatcher;
38 import org.opendaylight.netconf.auth.AuthProvider;
39 import org.opendaylight.netconf.ssh.SshProxyServer;
40 import org.opendaylight.netconf.ssh.SshProxyServerConfigurationBuilder;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44
45 public final class NetconfSshServerProvider extends ProviderTrait<NetconfSshServerProvider.NetconfSshServer> {
46
47     private static final Logger LOG = LoggerFactory.getLogger(NetconfSshServerProvider.class);
48
49     // Use RSA for ssh server, see https://git.opendaylight.org/gerrit/#/c/60138/
50     private static final String DEFAULT_PRIVATE_KEY_PATH = null; // disable private key serialization
51     private static final String DEFAULT_ALGORITHM = "RSA";
52     private static final int DEFAULT_KEY_SIZE = 4096;
53
54     @Inject
55     private NetconfServerDispatcher dispatcher;
56     @Inject
57     private NetconfConfiguration cfgAttributes;
58     @Inject
59     private NioEventLoopGroup nettyThreadgroup;
60     @Inject
61     private CredentialsConfiguration credentialsCfg;
62
63     private ScheduledExecutorService pool =
64             Executors.newScheduledThreadPool(1, new ThreadFactoryBuilder().setNameFormat("netconf-ssh-%d").build());
65
66     @Override
67     protected NetconfSshServer create() {
68         if (!cfgAttributes.isNetconfSshEnabled()) {
69             LOG.info("NETCONF SSH disabled, skipping initialization");
70             return null;
71         }
72         LOG.info("Starting NETCONF SSH");
73
74         // TODO(HONEYCOMB-414):  the logic below is very similar to
75         // org.opendaylight.netconf.ssh.NetconfNorthboundSshServer (introduced in Carbon), so consider reusing it
76         // (requires fixing hardcoded private key path).
77         final InetAddress sshBindingAddress = InetAddresses.forString(cfgAttributes.netconfSshBindingAddress.get());
78         final InetSocketAddress bindingAddress =
79                 new InetSocketAddress(sshBindingAddress, cfgAttributes.netconfSshBindingPort.get());
80
81         LocalAddress localAddress = new LocalAddress(cfgAttributes.netconfSshBindingPort.toString());
82         ChannelFuture localServer = dispatcher.createLocalServer(localAddress);
83
84         final SshProxyServer sshProxyServer = new SshProxyServer(pool, nettyThreadgroup, GlobalEventExecutor.INSTANCE);
85
86         final SshProxyServerConfigurationBuilder sshConfigBuilder = new SshProxyServerConfigurationBuilder();
87         sshConfigBuilder.setBindingAddress(bindingAddress);
88         sshConfigBuilder.setLocalAddress(localAddress);
89         // Only simple authProvider checking ConfigAttributes, checking the config file
90         sshConfigBuilder.setAuthenticator(new SimplelAuthProvider(credentialsCfg));
91         sshConfigBuilder.setIdleTimeout(Integer.MAX_VALUE);
92         sshConfigBuilder.setKeyPairProvider(new PEMGeneratorHostKeyProvider(DEFAULT_PRIVATE_KEY_PATH,
93             DEFAULT_ALGORITHM, DEFAULT_KEY_SIZE));
94
95         localServer.addListener(new SshServerBinder(sshProxyServer, sshConfigBuilder, bindingAddress));
96
97         return new NetconfSshServer(localServer, sshProxyServer);
98     }
99
100     public static final class NetconfSshServer {
101         private ChannelFuture localServer;
102         private SshProxyServer sshProxyServer;
103
104         NetconfSshServer(final ChannelFuture localServer,
105                          final SshProxyServer sshProxyServer) {
106             this.localServer = localServer;
107             this.sshProxyServer = sshProxyServer;
108         }
109
110         public Object getLocalServer() {
111             return localServer;
112         }
113
114         public Object getSshProxyServer() {
115             return sshProxyServer;
116         }
117     }
118
119     private static final class SimplelAuthProvider implements AuthProvider {
120
121         private final CredentialsConfiguration cfgAttributes;
122
123         SimplelAuthProvider(final CredentialsConfiguration cfgAttributes) {
124             this.cfgAttributes = cfgAttributes;
125         }
126
127         @Override
128         public boolean authenticated(final String uname, final String passwd) {
129             return cfgAttributes.username.equals(uname) && cfgAttributes.password.equals(passwd);
130         }
131     }
132
133     private static final class SshServerBinder implements GenericFutureListener<ChannelFuture> {
134         private final SshProxyServer sshProxyServer;
135         private final SshProxyServerConfigurationBuilder sshConfigBuilder;
136         private final InetSocketAddress bindingAddress;
137
138         SshServerBinder(final SshProxyServer sshProxyServer,
139                         final SshProxyServerConfigurationBuilder sshConfigBuilder,
140                         final InetSocketAddress bindingAddress) {
141             this.sshProxyServer = sshProxyServer;
142             this.sshConfigBuilder = sshConfigBuilder;
143             this.bindingAddress = bindingAddress;
144         }
145
146         @Override
147         public void operationComplete(final ChannelFuture future) {
148             if (future.isDone() && !future.isCancelled()) {
149                 try {
150                     sshProxyServer.bind(sshConfigBuilder.createSshProxyServerConfiguration());
151                     LOG.info("Netconf SSH endpoint started successfully at {}", bindingAddress);
152                 } catch (final IOException e) {
153                     throw new InitializationException("Unable to start SSH netconf server", e);
154                 }
155
156             } else {
157                 LOG.warn("Unable to start SSH netconf server at {}", bindingAddress, future.cause());
158                 throw new InitializationException("Unable to start SSH netconf server", future.cause());
159             }
160
161         }
162
163     }
164 }