From: Ed Warnicke Date: Thu, 28 Aug 2014 16:08:13 +0000 (+0000) Subject: Merge "Bug 1636: Config Netconf Connector did not serialize service type" X-Git-Tag: release/helium~185 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=529fc21a08aa88b03e7dde2b679f6aba79639458;hp=f4565e1aa5ab95748e0469e4ce441ae9b7ab11eb Merge "Bug 1636: Config Netconf Connector did not serialize service type" --- diff --git a/opendaylight/commons/opendaylight/pom.xml b/opendaylight/commons/opendaylight/pom.xml index 8fd173c094..425e968cf7 100644 --- a/opendaylight/commons/opendaylight/pom.xml +++ b/opendaylight/commons/opendaylight/pom.xml @@ -1115,6 +1115,16 @@ ${netconf.version} test-jar + + org.opendaylight.controller + netconf-auth + ${netconf.version} + + + org.opendaylight.controller + netconf-usermanager + ${netconf.version} + org.opendaylight.controller netconf-ssh diff --git a/opendaylight/distribution/opendaylight/pom.xml b/opendaylight/distribution/opendaylight/pom.xml index 9a7677099d..fc8b4453ea 100644 --- a/opendaylight/distribution/opendaylight/pom.xml +++ b/opendaylight/distribution/opendaylight/pom.xml @@ -931,6 +931,14 @@ org.opendaylight.controller netconf-ssh + + org.opendaylight.controller + netconf-auth + + + org.opendaylight.controller + netconf-usermanager + org.opendaylight.controller netconf-tcp diff --git a/opendaylight/netconf/netconf-auth/pom.xml b/opendaylight/netconf/netconf-auth/pom.xml new file mode 100644 index 0000000000..e19359adb8 --- /dev/null +++ b/opendaylight/netconf/netconf-auth/pom.xml @@ -0,0 +1,31 @@ + + + + + 4.0.0 + + org.opendaylight.controller + netconf-subsystem + 0.2.5-SNAPSHOT + ../ + + netconf-auth + bundle + ${project.artifactId} + + + + + org.apache.felix + maven-bundle-plugin + + + + + diff --git a/opendaylight/netconf/netconf-auth/src/main/java/org/opendaylight/controller/netconf/auth/AuthConstants.java b/opendaylight/netconf/netconf-auth/src/main/java/org/opendaylight/controller/netconf/auth/AuthConstants.java new file mode 100644 index 0000000000..de5bdd6e71 --- /dev/null +++ b/opendaylight/netconf/netconf-auth/src/main/java/org/opendaylight/controller/netconf/auth/AuthConstants.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.auth; + +public class AuthConstants { + + /** + * This property should be set for every implementation of AuthService published to OSGi. + * Netconf SSH will pick the service with highest preference in case of multiple services present in OSGi. + */ + public static final String SERVICE_PREFERENCE_KEY = "preference"; +} diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/AuthProvider.java b/opendaylight/netconf/netconf-auth/src/main/java/org/opendaylight/controller/netconf/auth/AuthProvider.java similarity index 56% rename from opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/AuthProvider.java rename to opendaylight/netconf/netconf-auth/src/main/java/org/opendaylight/controller/netconf/auth/AuthProvider.java index 92f3861c05..0ae74239b3 100644 --- a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/AuthProvider.java +++ b/opendaylight/netconf/netconf-auth/src/main/java/org/opendaylight/controller/netconf/auth/AuthProvider.java @@ -6,11 +6,20 @@ * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.controller.netconf.ssh.authentication; +package org.opendaylight.controller.netconf.auth; +/** + * Authentication Service definition for netconf. + */ public interface AuthProvider { + /** + * Authenticate user by username/password. + * + * @param username username + * @param password password + * @return true if authentication is successful, false otherwise + */ boolean authenticated(String username, String password); - char[] getPEMAsCharArray(); } diff --git a/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITSecureTest.java b/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITSecureTest.java index a9e8dbe86b..e5e34548b3 100644 --- a/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITSecureTest.java +++ b/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITSecureTest.java @@ -14,12 +14,6 @@ import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; -import com.google.common.collect.Lists; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.GenericFutureListener; -import io.netty.util.concurrent.GlobalEventExecutor; import java.io.IOException; import java.io.InputStream; import java.lang.management.ManagementFactory; @@ -27,13 +21,16 @@ import java.net.InetSocketAddress; import java.util.Collection; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; + import junit.framework.Assert; + import org.junit.After; import org.junit.Before; import org.junit.Test; import org.opendaylight.controller.config.manager.impl.factoriesresolver.HardcodedModuleFactoriesResolver; import org.opendaylight.controller.config.spi.ModuleFactory; import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.auth.AuthProvider; import org.opendaylight.controller.netconf.client.NetconfClientDispatcher; import org.opendaylight.controller.netconf.client.NetconfClientDispatcherImpl; import org.opendaylight.controller.netconf.client.SimpleNetconfClientSessionListener; @@ -48,8 +45,6 @@ import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationServiceFact import org.opendaylight.controller.netconf.nettyutil.handler.ssh.authentication.AuthenticationHandler; import org.opendaylight.controller.netconf.nettyutil.handler.ssh.authentication.LoginPassword; import org.opendaylight.controller.netconf.ssh.NetconfSSHServer; -import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider; -import org.opendaylight.controller.netconf.ssh.authentication.AuthProviderImpl; import org.opendaylight.controller.netconf.ssh.authentication.PEMGenerator; import org.opendaylight.controller.netconf.util.messages.NetconfMessageUtil; import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil; @@ -57,6 +52,15 @@ import org.opendaylight.controller.netconf.util.test.XmlFileLoader; import org.opendaylight.controller.netconf.util.xml.XmlUtil; import org.opendaylight.protocol.framework.NeverReconnectStrategy; +import com.google.common.collect.Lists; + +import io.netty.channel.ChannelFuture; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; +import io.netty.util.concurrent.GlobalEventExecutor; + public class NetconfITSecureTest extends AbstractNetconfConfigTest { private static final InetSocketAddress tlsAddress = new InetSocketAddress("127.0.0.1", 12024); @@ -79,9 +83,13 @@ public class NetconfITSecureTest extends AbstractNetconfConfigTest { final NetconfServerDispatcher dispatchS = createDispatcher(factoriesListener); - dispatchS.createLocalServer(NetconfConfigUtil.getNetconfLocalAddress()).await(); - final EventLoopGroup bossGroup = new NioEventLoopGroup(); - sshServer = NetconfSSHServer.start(tlsAddress.getPort(), NetconfConfigUtil.getNetconfLocalAddress(), getAuthProvider(), bossGroup); + ChannelFuture s = dispatchS.createLocalServer(NetconfConfigUtil.getNetconfLocalAddress()); + s.await(); + EventLoopGroup bossGroup = new NioEventLoopGroup(); + + final char[] pem = PEMGenerator.generate().toCharArray(); + sshServer = NetconfSSHServer.start(tlsAddress.getPort(), NetconfConfigUtil.getNetconfLocalAddress(), bossGroup, pem); + sshServer.setAuthProvider(getAuthProvider()); } private NetconfServerDispatcher createDispatcher(final NetconfOperationServiceFactoryListenerImpl factoriesListener) { @@ -172,9 +180,8 @@ public class NetconfITSecureTest extends AbstractNetconfConfigTest { } public AuthProvider getAuthProvider() throws Exception { - final AuthProvider mock = mock(AuthProviderImpl.class); + AuthProvider mock = mock(AuthProvider.class); doReturn(true).when(mock).authenticated(anyString(), anyString()); - doReturn(PEMGenerator.generate().toCharArray()).when(mock).getPEMAsCharArray(); return mock; } diff --git a/opendaylight/netconf/netconf-ssh/pom.xml b/opendaylight/netconf/netconf-ssh/pom.xml index 8d48077f93..6dd23776ce 100644 --- a/opendaylight/netconf/netconf-ssh/pom.xml +++ b/opendaylight/netconf/netconf-ssh/pom.xml @@ -15,6 +15,10 @@ ${project.groupId} netconf-api + + + ${project.groupId} + netconf-auth ${project.groupId} @@ -32,10 +36,6 @@ org.bouncycastle bcprov-jdk15on - - org.opendaylight.controller - usermanager - org.opendaylight.controller.thirdparty ganymed @@ -44,6 +44,10 @@ org.apache.sshd sshd-core + + com.google.guava + guava + org.slf4j slf4j-api @@ -73,21 +77,7 @@ org.opendaylight.controller.netconf.ssh.osgi.NetconfSSHActivator - com.google.common.base, - ch.ethz.ssh2, - ch.ethz.ssh2.signature, - org.apache.commons.io, - org.opendaylight.controller.netconf.util.osgi, - org.opendaylight.controller.usermanager, - org.opendaylight.controller.sal.authorization, - org.opendaylight.controller.sal.utils, - org.osgi.framework, - org.osgi.util.tracker, - org.slf4j, - org.bouncycastle.openssl, - io.netty.bootstrap, io.netty.buffer, io.netty.channel, io.netty.channel.local, io.netty.channel.nio, - io.netty.handler.stream, io.netty.util.concurrent, org.apache.commons.lang3, - org.opendaylight.controller.netconf.util.messages + * diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/NetconfSSHServer.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/NetconfSSHServer.java index 670f50ddd0..86206a7d5c 100644 --- a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/NetconfSSHServer.java +++ b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/NetconfSSHServer.java @@ -7,9 +7,7 @@ */ package org.opendaylight.controller.netconf.ssh; -import com.google.common.annotations.VisibleForTesting; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.local.LocalAddress; +import com.google.common.base.Preconditions; import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; @@ -17,12 +15,20 @@ import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicLong; + import javax.annotation.concurrent.ThreadSafe; -import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider; + +import org.opendaylight.controller.netconf.auth.AuthProvider; import org.opendaylight.controller.netconf.ssh.threads.Handshaker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Optional; + +import io.netty.channel.EventLoopGroup; +import io.netty.channel.local.LocalAddress; + /** * Thread that accepts client connections. Accepted socket is forwarded to {@link org.opendaylight.controller.netconf.ssh.threads.Handshaker}, * which is executed in {@link #handshakeExecutor}. @@ -36,13 +42,15 @@ public final class NetconfSSHServer extends Thread implements AutoCloseable { private final ServerSocket serverSocket; private final LocalAddress localAddress; private final EventLoopGroup bossGroup; - private final AuthProvider authProvider; + private Optional authProvider = Optional.absent(); private final ExecutorService handshakeExecutor; + private final char[] pem; private volatile boolean up; - private NetconfSSHServer(int serverPort, LocalAddress localAddress, AuthProvider authProvider, EventLoopGroup bossGroup) throws IOException { + private NetconfSSHServer(final int serverPort, final LocalAddress localAddress, final EventLoopGroup bossGroup, final char[] pem) throws IOException { super(NetconfSSHServer.class.getSimpleName()); this.bossGroup = bossGroup; + this.pem = pem; logger.trace("Creating SSH server socket on port {}", serverPort); this.serverSocket = new ServerSocket(serverPort); if (serverSocket.isBound() == false) { @@ -50,17 +58,28 @@ public final class NetconfSSHServer extends Thread implements AutoCloseable { } logger.trace("Server socket created."); this.localAddress = localAddress; - this.authProvider = authProvider; this.up = true; handshakeExecutor = Executors.newFixedThreadPool(10); } - public static NetconfSSHServer start(int serverPort, LocalAddress localAddress, AuthProvider authProvider, EventLoopGroup bossGroup) throws IOException { - NetconfSSHServer netconfSSHServer = new NetconfSSHServer(serverPort, localAddress, authProvider, bossGroup); + public static NetconfSSHServer start(final int serverPort, final LocalAddress localAddress, final EventLoopGroup bossGroup, final char[] pemArray) throws IOException { + final NetconfSSHServer netconfSSHServer = new NetconfSSHServer(serverPort, localAddress, bossGroup, pemArray); netconfSSHServer.start(); return netconfSSHServer; } + public synchronized AuthProvider getAuthProvider() { + Preconditions.checkState(authProvider.isPresent(), "AuthenticationProvider is not set up, cannot authenticate user"); + return authProvider.get(); + } + + public synchronized void setAuthProvider(final AuthProvider authProvider) { + if(this.authProvider != null) { + logger.debug("Changing auth provider to {}", authProvider); + } + this.authProvider = Optional.fromNullable(authProvider); + } + @Override public void close() throws IOException { up = false; @@ -81,7 +100,7 @@ public final class NetconfSSHServer extends Thread implements AutoCloseable { Socket acceptedSocket = null; try { acceptedSocket = serverSocket.accept(); - } catch (IOException e) { + } catch (final IOException e) { if (up == false) { logger.trace("Exiting server thread", e); } else { @@ -90,18 +109,26 @@ public final class NetconfSSHServer extends Thread implements AutoCloseable { } if (acceptedSocket != null) { try { - Handshaker task = new Handshaker(acceptedSocket, localAddress, sessionIdCounter.incrementAndGet(), authProvider, bossGroup); + final Handshaker task = new Handshaker(acceptedSocket, localAddress, sessionIdCounter.incrementAndGet(), getAuthProvider(), bossGroup, pem); handshakeExecutor.submit(task); - } catch (IOException e) { + } catch (final IOException e) { logger.warn("Cannot set PEMHostKey, closing connection", e); - try { - acceptedSocket.close(); - } catch (IOException e1) { - logger.warn("Ignoring exception while closing socket", e); - } + closeSocket(acceptedSocket); + } catch (final IllegalStateException e) { + logger.warn("Cannot accept connection, closing", e); + closeSocket(acceptedSocket); } } } logger.debug("Server thread is exiting"); } + + private void closeSocket(final Socket acceptedSocket) { + try { + acceptedSocket.close(); + } catch (final IOException e) { + logger.warn("Ignoring exception while closing socket", e); + } + } + } 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 503e764409..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 @@ -9,26 +9,33 @@ package org.opendaylight.controller.netconf.ssh.osgi; import static com.google.common.base.Preconditions.checkState; -import com.google.common.base.Optional; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.local.LocalAddress; -import io.netty.channel.nio.NioEventLoopGroup; +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.apache.commons.lang3.StringUtils; +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.AuthProviderImpl; import org.opendaylight.controller.netconf.ssh.authentication.PEMGenerator; import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil; import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil.InfixProp; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.util.tracker.ServiceTracker; +import org.osgi.util.tracker.ServiceTrackerCustomizer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +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 @@ -41,6 +48,7 @@ import org.slf4j.LoggerFactory; */ public class NetconfSSHActivator implements BundleActivator { private static final Logger logger = LoggerFactory.getLogger(NetconfSSHActivator.class); + private static AuthProviderTracker authProviderTracker; private NetconfSSHServer server; @@ -50,39 +58,115 @@ public class NetconfSSHActivator implements BundleActivator { } @Override - public void stop(BundleContext context) throws IOException { + public void stop(final BundleContext context) throws IOException { if (server != null) { server.close(); } + + if(authProviderTracker != null) { + authProviderTracker.stop(); + } } - private static NetconfSSHServer startSSHServer(BundleContext bundleContext) throws IOException { - Optional maybeSshSocketAddress = NetconfConfigUtil.extractNetconfServerAddress(bundleContext, + 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; } - InetSocketAddress sshSocketAddress = maybeSshSocketAddress.get(); - logger.trace("Starting netconf SSH bridge at {}", sshSocketAddress); - LocalAddress localAddress = NetconfConfigUtil.getNetconfLocalAddress(); + final InetSocketAddress sshSocketAddress = maybeSshSocketAddress.get(); + logger.trace("Starting netconf SSH bridge at {}", sshSocketAddress); + + final LocalAddress localAddress = NetconfConfigUtil.getNetconfLocalAddress(); - String path = FilenameUtils.separatorsToSystem(NetconfConfigUtil.getPrivateKeyPath(bundleContext)); - checkState(StringUtils.isNotBlank(path), "Path to ssh private key is blank. Reconfigure %s", NetconfConfigUtil.getPrivateKeyKey()); - String privateKeyPEMString = PEMGenerator.readOrGeneratePK(new File(path)); + 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 AuthProvider authProvider = new AuthProviderImpl(privateKeyPEMString, bundleContext); - EventLoopGroup bossGroup = new NioEventLoopGroup(); - NetconfSSHServer server = NetconfSSHServer.start(sshSocketAddress.getPort(), localAddress, authProvider, bossGroup); + final EventLoopGroup bossGroup = new NioEventLoopGroup(); + final NetconfSSHServer server = NetconfSSHServer.start(sshSocketAddress.getPort(), localAddress, bossGroup, privateKeyPEMString.toCharArray()); + + authProviderTracker = new AuthProviderTracker(bundleContext, server); + + return server; + } + 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 server; + return serverThread; } + 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); + } + } + 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); + } + } + + @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); + } + + public void stop() { + listenerTracker.close(); + // sshThread should finish normally since sshServer.close stops processing + } + + } } diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/Handshaker.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/Handshaker.java index 3fffbb2d2c..eec6c3a097 100644 --- a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/Handshaker.java +++ b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/Handshaker.java @@ -10,6 +10,20 @@ package org.opendaylight.controller.netconf.ssh.threads; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +import javax.annotation.concurrent.NotThreadSafe; +import javax.annotation.concurrent.ThreadSafe; + +import org.opendaylight.controller.netconf.auth.AuthProvider; +import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import ch.ethz.ssh2.AuthenticationResult; import ch.ethz.ssh2.PtySettings; import ch.ethz.ssh2.ServerAuthenticationCallback; @@ -18,7 +32,9 @@ import ch.ethz.ssh2.ServerConnectionCallback; import ch.ethz.ssh2.ServerSession; import ch.ethz.ssh2.ServerSessionCallback; import ch.ethz.ssh2.SimpleServerSessionCallback; + import com.google.common.base.Supplier; + import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufProcessor; @@ -32,17 +48,6 @@ import io.netty.channel.EventLoopGroup; import io.netty.channel.local.LocalAddress; import io.netty.channel.local.LocalChannel; import io.netty.handler.stream.ChunkedStream; -import java.io.BufferedOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Socket; -import javax.annotation.concurrent.NotThreadSafe; -import javax.annotation.concurrent.ThreadSafe; -import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider; -import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * One instance represents per connection, responsible for ssh handshake. @@ -58,7 +63,7 @@ public class Handshaker implements Runnable { public Handshaker(Socket socket, LocalAddress localAddress, long sessionId, AuthProvider authProvider, - EventLoopGroup bossGroup) throws IOException { + EventLoopGroup bossGroup, final char[] pem) throws IOException { this.session = "Session " + sessionId; @@ -83,7 +88,7 @@ public class Handshaker implements Runnable { getGanymedAutoCloseable(ganymedConnection), localAddress, bossGroup); // initialize ganymed - ganymedConnection.setPEMHostKey(authProvider.getPEMAsCharArray(), null); + ganymedConnection.setPEMHostKey(pem, null); ganymedConnection.setAuthenticationCallback(serverAuthenticationCallback); ganymedConnection.setServerConnectionCallback(serverConnectionCallback); } diff --git a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/StubUserManager.java b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/StubUserManager.java deleted file mode 100644 index 6628310c97..0000000000 --- a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/StubUserManager.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.controller.netconf; - -import java.util.List; -import java.util.Map; -import java.util.Set; -import org.opendaylight.controller.sal.authorization.AuthResultEnum; -import org.opendaylight.controller.sal.authorization.UserLevel; -import org.opendaylight.controller.sal.utils.Status; -import org.opendaylight.controller.sal.utils.StatusCode; -import org.opendaylight.controller.usermanager.AuthorizationConfig; -import org.opendaylight.controller.usermanager.ISessionManager; -import org.opendaylight.controller.usermanager.IUserManager; -import org.opendaylight.controller.usermanager.ServerConfig; -import org.opendaylight.controller.usermanager.UserConfig; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.web.context.SecurityContextRepository; - -public class StubUserManager implements IUserManager{ - - - private static String user; - private static String password; - - public StubUserManager(String user, String password){ - StubUserManager.user = user; - StubUserManager.password = password; - } - @Override - public List getUserRoles(String userName) { - return null; - } - - @Override - public AuthResultEnum authenticate(String username, String password) { - if (StubUserManager.user.equals(username) && StubUserManager.password.equals(password)){ - return AuthResultEnum.AUTH_ACCEPT_LOC; - } - return AuthResultEnum.AUTH_REJECT_LOC; - } - - @Override - public Status addAAAServer(ServerConfig configObject) { - return null; - } - - @Override - public Status removeAAAServer(ServerConfig configObject) { - return null; - } - - @Override - public Status addLocalUser(UserConfig configObject) { - return new Status(StatusCode.SUCCESS); - } - - @Override - public Status modifyLocalUser(UserConfig configObject) { - return null; - } - - @Override - public Status removeLocalUser(UserConfig configObject) { - return null; - } - - @Override - public Status removeLocalUser(String userName) { - return null; - } - - @Override - public Status addAuthInfo(AuthorizationConfig AAAconf) { - return null; - } - - @Override - public Status removeAuthInfo(AuthorizationConfig AAAconf) { - return null; - } - - @Override - public List getAuthorizationList() { - return null; - } - - @Override - public Set getAAAProviderNames() { - return null; - } - - @Override - public Status changeLocalUserPassword(String user, String curPassword, String newPassword) { - return null; - } - - @Override - public List getAAAServerList() { - return null; - } - - @Override - public List getLocalUserList() { - return null; - } - - @Override - public Status saveLocalUserList() { - return null; - } - - @Override - public Status saveAAAServerList() { - return null; - } - - @Override - public Status saveAuthorizationList() { - return null; - } - - @Override - public void userLogout(String username) { - - } - - @Override - public void userTimedOut(String username) { - - } - - @Override - public Map> getUserLoggedIn() { - return null; - } - - @Override - public String getAccessDate(String user) { - return null; - } - - @Override - public UserLevel getUserLevel(String userName) { - return null; - } - - @Override - public List getUserLevels(String userName) { - return null; - } - - @Override - public SecurityContextRepository getSecurityContextRepo() { - return null; - } - - @Override - public ISessionManager getSessionManager() { - return null; - } - - @Override - public boolean isRoleInUse(String role) { - return false; - } - - @Override - public String getPassword(String username) { - return null; - } - - @Override - public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { - return null; - } - -} diff --git a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/SSHTest.java b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/SSHTest.java index b3478c3693..ce1400bbcb 100644 --- a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/SSHTest.java +++ b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/SSHTest.java @@ -27,12 +27,11 @@ import java.util.concurrent.TimeUnit; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.opendaylight.controller.netconf.auth.AuthProvider; import org.opendaylight.controller.netconf.netty.EchoClientHandler.State; import org.opendaylight.controller.netconf.nettyutil.handler.ssh.authentication.LoginPassword; import org.opendaylight.controller.netconf.nettyutil.handler.ssh.client.AsyncSshHandler; import org.opendaylight.controller.netconf.ssh.NetconfSSHServer; -import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider; -import org.opendaylight.controller.netconf.ssh.authentication.AuthProviderImpl; import org.opendaylight.controller.netconf.ssh.authentication.PEMGenerator; import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil; import org.slf4j.Logger; @@ -59,13 +58,13 @@ public class SSHTest { @Test public void test() throws Exception { new Thread(new EchoServer(), "EchoServer").start(); - AuthProvider authProvider = mock(AuthProviderImpl.class); - doReturn(PEMGenerator.generate().toCharArray()).when(authProvider).getPEMAsCharArray(); + AuthProvider authProvider = mock(AuthProvider.class); doReturn(true).when(authProvider).authenticated(anyString(), anyString()); doReturn("auth").when(authProvider).toString(); NetconfSSHServer netconfSSHServer = NetconfSSHServer.start(10831, NetconfConfigUtil.getNetconfLocalAddress(), - authProvider, new NioEventLoopGroup()); + new NioEventLoopGroup(), PEMGenerator.generate().toCharArray()); + netconfSSHServer.setAuthProvider(authProvider); InetSocketAddress address = netconfSSHServer.getLocalSocketAddress(); final EchoClientHandler echoClientHandler = connectClient(address); diff --git a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/ssh/authentication/SSHServerTest.java b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/ssh/authentication/SSHServerTest.java index 75d18566ee..1151abcdf2 100644 --- a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/ssh/authentication/SSHServerTest.java +++ b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/ssh/authentication/SSHServerTest.java @@ -23,7 +23,7 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.opendaylight.controller.netconf.StubUserManager; +import org.opendaylight.controller.netconf.auth.AuthProvider; import org.opendaylight.controller.netconf.ssh.NetconfSSHServer; import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil; import org.osgi.framework.BundleContext; @@ -55,16 +55,21 @@ public class SSHServerTest { doReturn(new ServiceReference[0]).when(mockedContext).getServiceReferences(anyString(), anyString()); logger.info("Creating SSH server"); - StubUserManager um = new StubUserManager(USER, PASSWORD); String pem; try (InputStream is = getClass().getResourceAsStream("/RSA.pk")) { pem = IOUtils.toString(is); } - AuthProviderImpl ap = new AuthProviderImpl(pem, mockedContext); - ap.setNullableUserManager(um); + + EventLoopGroup bossGroup = new NioEventLoopGroup(); NetconfSSHServer server = NetconfSSHServer.start(PORT, NetconfConfigUtil.getNetconfLocalAddress(), - ap, bossGroup); + bossGroup, pem.toCharArray()); + server.setAuthProvider(new AuthProvider() { + @Override + public boolean authenticated(final String username, final String password) { + return true; + } + }); sshServerThread = new Thread(server); sshServerThread.setDaemon(true); diff --git a/opendaylight/netconf/netconf-usermanager/pom.xml b/opendaylight/netconf/netconf-usermanager/pom.xml new file mode 100644 index 0000000000..f8c3e5a504 --- /dev/null +++ b/opendaylight/netconf/netconf-usermanager/pom.xml @@ -0,0 +1,52 @@ + + + + + 4.0.0 + + org.opendaylight.controller + netconf-subsystem + 0.2.5-SNAPSHOT + ../ + + netconf-usermanager + bundle + ${project.artifactId} + + + + org.opendaylight.controller + netconf-auth + + + org.opendaylight.controller + usermanager + + + com.google.guava + guava + + + + + + + org.apache.felix + maven-bundle-plugin + + + org.opendaylight.controller.netconf.auth.usermanager.AuthProviderActivator + + + + + + + + diff --git a/opendaylight/netconf/netconf-usermanager/src/main/java/org/opendaylight/controller/netconf/auth/usermanager/AuthProviderActivator.java b/opendaylight/netconf/netconf-usermanager/src/main/java/org/opendaylight/controller/netconf/auth/usermanager/AuthProviderActivator.java new file mode 100644 index 0000000000..528d8ff828 --- /dev/null +++ b/opendaylight/netconf/netconf-usermanager/src/main/java/org/opendaylight/controller/netconf/auth/usermanager/AuthProviderActivator.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.auth.usermanager; + +import java.util.Hashtable; +import org.opendaylight.controller.netconf.auth.AuthConstants; +import org.opendaylight.controller.netconf.auth.AuthProvider; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; + +public class AuthProviderActivator implements BundleActivator { + + public static final int PREFERENCE = 0; + private ServiceRegistration authProviderServiceRegistration; + + @Override + public void start(final BundleContext context) throws Exception { + final AuthProvider authProvider = new AuthProviderImpl(context); + // Set preference of this service to 0 + final Hashtable properties = new Hashtable<>(1); + properties.put(AuthConstants.SERVICE_PREFERENCE_KEY, PREFERENCE); + + authProviderServiceRegistration = context.registerService(AuthProvider.class, authProvider, properties); + } + + @Override + public void stop(final BundleContext context) throws Exception { + if(authProviderServiceRegistration != null) { + authProviderServiceRegistration.unregister(); + } + } +} diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/AuthProviderImpl.java b/opendaylight/netconf/netconf-usermanager/src/main/java/org/opendaylight/controller/netconf/auth/usermanager/AuthProviderImpl.java similarity index 65% rename from opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/AuthProviderImpl.java rename to opendaylight/netconf/netconf-usermanager/src/main/java/org/opendaylight/controller/netconf/auth/usermanager/AuthProviderImpl.java index 7543d17c06..d314c31b4f 100644 --- a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/AuthProviderImpl.java +++ b/opendaylight/netconf/netconf-usermanager/src/main/java/org/opendaylight/controller/netconf/auth/usermanager/AuthProviderImpl.java @@ -5,11 +5,9 @@ * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.controller.netconf.ssh.authentication; +package org.opendaylight.controller.netconf.auth.usermanager; -import static com.google.common.base.Preconditions.checkNotNull; - -import com.google.common.annotations.VisibleForTesting; +import org.opendaylight.controller.netconf.auth.AuthProvider; import org.opendaylight.controller.sal.authorization.AuthResultEnum; import org.opendaylight.controller.usermanager.IUserManager; import org.osgi.framework.BundleContext; @@ -19,40 +17,41 @@ import org.osgi.util.tracker.ServiceTrackerCustomizer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.annotations.VisibleForTesting; + +/** + * AuthProvider implementation delegating to AD-SAL UserManager instance. + */ public class AuthProviderImpl implements AuthProvider { private static final Logger logger = LoggerFactory.getLogger(AuthProviderImpl.class); - private final String pem; private IUserManager nullableUserManager; - public AuthProviderImpl(String pemCertificate, final BundleContext bundleContext) { - checkNotNull(pemCertificate, "Parameter 'pemCertificate' is null"); - pem = pemCertificate; + public AuthProviderImpl(final BundleContext bundleContext) { - ServiceTrackerCustomizer customizer = new ServiceTrackerCustomizer() { + final ServiceTrackerCustomizer customizer = new ServiceTrackerCustomizer() { @Override public IUserManager addingService(final ServiceReference reference) { - logger.trace("Service {} added", reference); + logger.trace("UerManager {} added", reference); nullableUserManager = bundleContext.getService(reference); return nullableUserManager; } @Override public void modifiedService(final ServiceReference reference, final IUserManager service) { - logger.trace("Replacing modified service {} in netconf SSH.", reference); + logger.trace("Replacing modified UerManager {}", reference); nullableUserManager = service; } @Override public void removedService(final ServiceReference reference, final IUserManager service) { - logger.trace("Removing service {} from netconf SSH. " + - "SSH won't authenticate users until IUserManager service will be started.", reference); + logger.trace("Removing UerManager {}. This AuthProvider will fail to authenticate every time", reference); synchronized (AuthProviderImpl.this) { nullableUserManager = null; } } }; - ServiceTracker listenerTracker = new ServiceTracker<>(bundleContext, IUserManager.class, customizer); + final ServiceTracker listenerTracker = new ServiceTracker<>(bundleContext, IUserManager.class, customizer); listenerTracker.open(); } @@ -61,23 +60,18 @@ public class AuthProviderImpl implements AuthProvider { * available, IllegalStateException is thrown. */ @Override - public synchronized boolean authenticated(String username, String password) { + public synchronized boolean authenticated(final String username, final String password) { if (nullableUserManager == null) { logger.warn("Cannot authenticate user '{}', user manager service is missing", username); throw new IllegalStateException("User manager service is not available"); } - AuthResultEnum authResult = nullableUserManager.authenticate(username, password); + final AuthResultEnum authResult = nullableUserManager.authenticate(username, password); logger.debug("Authentication result for user '{}' : {}", username, authResult); return authResult.equals(AuthResultEnum.AUTH_ACCEPT) || authResult.equals(AuthResultEnum.AUTH_ACCEPT_LOC); } - @Override - public char[] getPEMAsCharArray() { - return pem.toCharArray(); - } - @VisibleForTesting - void setNullableUserManager(IUserManager nullableUserManager) { + void setNullableUserManager(final IUserManager nullableUserManager) { this.nullableUserManager = nullableUserManager; } } diff --git a/opendaylight/netconf/pom.xml b/opendaylight/netconf/pom.xml index b1b410a1fc..8abf67ec8c 100644 --- a/opendaylight/netconf/pom.xml +++ b/opendaylight/netconf/pom.xml @@ -35,6 +35,8 @@ ietf-netconf-monitoring ietf-netconf-monitoring-extension netconf-connector-config + netconf-auth + netconf-usermanager