X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=blobdiff_plain;f=opendaylight%2Fnetconf%2Fnetconf-ssh%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fcontroller%2Fnetconf%2Fssh%2Fosgi%2FNetconfSSHActivator.java;h=c686bcbc66fcb849da3f357bdd3bea2df7f62f2e;hp=5b8803001ca9a33f71d2cacab990f03891a6891e;hb=351a78c9840c5b98a478b91ffd50befad998eb0e;hpb=87837c5398976e1f44418e9f161efea9d5fa4e7c diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/osgi/NetconfSSHActivator.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/osgi/NetconfSSHActivator.java index 5b8803001c..c686bcbc66 100644 --- a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/osgi/NetconfSSHActivator.java +++ b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/osgi/NetconfSSHActivator.java @@ -7,13 +7,20 @@ */ package org.opendaylight.controller.netconf.ssh.osgi; -import com.google.common.base.Optional; -import org.apache.commons.io.IOUtils; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.base.Preconditions; +import java.io.File; +import java.io.IOException; +import java.net.InetSocketAddress; + +import org.apache.commons.io.FilenameUtils; +import org.opendaylight.controller.netconf.auth.AuthConstants; +import org.opendaylight.controller.netconf.auth.AuthProvider; import org.opendaylight.controller.netconf.ssh.NetconfSSHServer; -import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider; import org.opendaylight.controller.netconf.ssh.authentication.PEMGenerator; import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil; -import org.opendaylight.controller.usermanager.IUserManager; +import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil.InfixProp; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; @@ -22,128 +29,144 @@ import org.osgi.util.tracker.ServiceTrackerCustomizer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.net.InetSocketAddress; +import com.google.common.base.Optional; +import com.google.common.base.Strings; + +import io.netty.channel.EventLoopGroup; +import io.netty.channel.local.LocalAddress; +import io.netty.channel.nio.NioEventLoopGroup; /** * Activator for netconf SSH bundle which creates SSH bridge between netconf client and netconf server. Activator * starts SSH Server in its own thread. This thread is closed when activator calls stop() method. Server opens socket - * and listen for client connections. Each client connection creation is handled in separate - * {@link org.opendaylight.controller.netconf.ssh.threads.SocketThread} thread. + * and listens for client connections. Each client connection creation is handled in separate + * {@link org.opendaylight.controller.netconf.ssh.threads.Handshaker} thread. * This thread creates two additional threads {@link org.opendaylight.controller.netconf.ssh.threads.IOThread} * forwarding data from/to client.IOThread closes servers session and server connection when it gets -1 on input stream. * {@link org.opendaylight.controller.netconf.ssh.threads.IOThread}'s run method waits for -1 on input stream to finish. * All threads are daemons. - **/ -public class NetconfSSHActivator implements BundleActivator{ + */ +public class NetconfSSHActivator implements BundleActivator { + private static final Logger logger = LoggerFactory.getLogger(NetconfSSHActivator.class); + private static AuthProviderTracker authProviderTracker; private NetconfSSHServer server; - private static final Logger logger = LoggerFactory.getLogger(NetconfSSHActivator.class); - private static final String EXCEPTION_MESSAGE = "Netconf ssh bridge is not available."; - private IUserManager iUserManager; - private BundleContext context = null; - private ServiceTrackerCustomizer customizer = new ServiceTrackerCustomizer(){ - @Override - public IUserManager addingService(ServiceReference reference) { - logger.trace("Service {} added, let there be SSH bridge.", reference); - iUserManager = context.getService(reference); - try { - onUserManagerFound(iUserManager); - } catch (Exception e) { - logger.trace("Can't start SSH server due to {}",e); - } - return iUserManager; + @Override + public void start(final BundleContext bundleContext) throws IOException { + server = startSSHServer(bundleContext); + } + + @Override + public void stop(final BundleContext context) throws IOException { + if (server != null) { + server.close(); } - @Override - public void modifiedService(ServiceReference reference, IUserManager service) { - logger.trace("Replacing modified service {} in netconf SSH.", reference); - server.addUserManagerService(service); + + if(authProviderTracker != null) { + authProviderTracker.stop(); } - @Override - public void removedService(ServiceReference reference, IUserManager service) { - logger.trace("Removing service {} from netconf SSH. " + - "SSH won't authenticate users until IUserManager service will be started.", reference); - removeUserManagerService(); + } + + private static NetconfSSHServer startSSHServer(final BundleContext bundleContext) throws IOException { + final Optional maybeSshSocketAddress = NetconfConfigUtil.extractNetconfServerAddress(bundleContext, + InfixProp.ssh); + + if (maybeSshSocketAddress.isPresent() == false) { + logger.trace("SSH bridge not configured"); + return null; } - }; + final InetSocketAddress sshSocketAddress = maybeSshSocketAddress.get(); + logger.trace("Starting netconf SSH bridge at {}", sshSocketAddress); - @Override - public void start(BundleContext context) { - this.context = context; - listenForManagerService(); + final LocalAddress localAddress = NetconfConfigUtil.getNetconfLocalAddress(); + + final String path = FilenameUtils.separatorsToSystem(NetconfConfigUtil.getPrivateKeyPath(bundleContext)); + checkState(!Strings.isNullOrEmpty(path), "Path to ssh private key is blank. Reconfigure %s", NetconfConfigUtil.getPrivateKeyKey()); + final String privateKeyPEMString = PEMGenerator.readOrGeneratePK(new File(path)); + + final EventLoopGroup bossGroup = new NioEventLoopGroup(); + final NetconfSSHServer server = NetconfSSHServer.start(sshSocketAddress.getPort(), localAddress, bossGroup, privateKeyPEMString.toCharArray()); + + authProviderTracker = new AuthProviderTracker(bundleContext, server); + + return server; } - @Override - public void stop(BundleContext context) throws IOException { - if (server != null){ - server.stop(); - logger.trace("Netconf SSH bridge is down ..."); - } + private static Thread runNetconfSshThread(final NetconfSSHServer server) { + final Thread serverThread = new Thread(server, "netconf SSH server thread"); + serverThread.setDaemon(true); + serverThread.start(); + logger.trace("Netconf SSH bridge up and running."); + return serverThread; } - private void startSSHServer() throws IllegalStateException, IOException { - logger.trace("Starting netconf SSH bridge."); - Optional sshSocketAddressOptional = NetconfConfigUtil.extractSSHNetconfAddress(context, EXCEPTION_MESSAGE); - InetSocketAddress tcpSocketAddress = NetconfConfigUtil.extractTCPNetconfAddress(context, - EXCEPTION_MESSAGE, true); - - if (sshSocketAddressOptional.isPresent()){ - String path = NetconfConfigUtil.getPrivateKeyPath(context); - path = path.replace("\\", "/"); // FIXME: shouldn't this convert lines to system dependent path separator? - if (path.equals("")){ - throw new IllegalStateException("Missing netconf.ssh.pk.path key in configuration file."); - } - File privateKeyFile = new File(path); - String privateKeyPEMString = null; - if (privateKeyFile.exists() == false) { - // generate & save to file - try { - privateKeyPEMString = PEMGenerator.generateTo(privateKeyFile); - } catch (Exception e) { - logger.error("Exception occured while generating PEM string {}",e); - } - } else { - // read from file - try (FileInputStream fis = new FileInputStream(path)) { - privateKeyPEMString = IOUtils.toString(fis); - } catch (IOException e) { - logger.error("Error reading RSA key from file '{}'", path); - throw new IllegalStateException("Error reading RSA key from file " + path); + private static class AuthProviderTracker implements ServiceTrackerCustomizer { + private final BundleContext bundleContext; + private final NetconfSSHServer server; + + private Integer maxPreference; + private Thread sshThread; + private final ServiceTracker listenerTracker; + + public AuthProviderTracker(final BundleContext bundleContext, final NetconfSSHServer server) { + this.bundleContext = bundleContext; + this.server = server; + listenerTracker = new ServiceTracker<>(bundleContext, AuthProvider.class, this); + listenerTracker.open(); + } + + @Override + public AuthProvider addingService(final ServiceReference reference) { + logger.trace("Service {} added", reference); + final AuthProvider authService = bundleContext.getService(reference); + final Integer newServicePreference = getPreference(reference); + if(isBetter(newServicePreference)) { + server.setAuthProvider(authService); + if(sshThread == null) { + sshThread = runNetconfSshThread(server); } } - AuthProvider authProvider = null; - try { - authProvider = new AuthProvider(iUserManager, privateKeyPEMString); - } catch (Exception e) { - logger.error("Error instantiating AuthProvider {}",e); + return authService; + } + + private Integer getPreference(final ServiceReference reference) { + final Object preferenceProperty = reference.getProperty(AuthConstants.SERVICE_PREFERENCE_KEY); + return preferenceProperty == null ? Integer.MIN_VALUE : Integer.valueOf(preferenceProperty.toString()); + } + + private boolean isBetter(final Integer newServicePreference) { + Preconditions.checkNotNull(newServicePreference); + if(maxPreference == null) { + return true; + } + + return newServicePreference > maxPreference; + } + + @Override + public void modifiedService(final ServiceReference reference, final AuthProvider service) { + final AuthProvider authService = bundleContext.getService(reference); + final Integer newServicePreference = getPreference(reference); + if(isBetter(newServicePreference)) { + logger.trace("Replacing modified service {} in netconf SSH.", reference); + server.setAuthProvider(authService); } - this.server = NetconfSSHServer.start(sshSocketAddressOptional.get().getPort(),tcpSocketAddress,authProvider); - - Thread serverThread = new Thread(server,"netconf SSH server thread"); - serverThread.setDaemon(true); - serverThread.start(); - logger.trace("Netconf SSH bridge up and running."); - } else { - logger.trace("No valid connection configuration for SSH bridge found."); - throw new IllegalStateException("No valid connection configuration for SSH bridge found."); } - } - private void onUserManagerFound(IUserManager userManager) throws IOException { - if (server!=null && server.isUp()){ - server.addUserManagerService(userManager); - } else { - startSSHServer(); + + @Override + public void removedService(final ServiceReference reference, final AuthProvider service) { + logger.trace("Removing service {} from netconf SSH. " + + "SSH won't authenticate users until AuthProvider service will be started.", reference); + maxPreference = null; + server.setAuthProvider(null); } - } - private void removeUserManagerService(){ - this.server.removeUserManagerService(); - } - private void listenForManagerService(){ - ServiceTracker listenerTracker = new ServiceTracker<>(context, IUserManager.class,customizer); - listenerTracker.open(); + + public void stop() { + listenerTracker.close(); + // sshThread should finish normally since sshServer.close stops processing + } + } }