BUG-1617 Extract AuthProvider from netconf ssh 18/10318/1
authorMaros Marsalek <mmarsale@cisco.com>
Tue, 26 Aug 2014 14:53:07 +0000 (16:53 +0200)
committerMaros Marsalek <mmarsale@cisco.com>
Tue, 26 Aug 2014 14:57:38 +0000 (16:57 +0200)
Change-Id: Ib6f47c0dca5b4b3604b639ae8bca3565f8dd934c
Signed-off-by: Maros Marsalek <mmarsale@cisco.com>
17 files changed:
opendaylight/commons/opendaylight/pom.xml
opendaylight/distribution/opendaylight/pom.xml
opendaylight/netconf/netconf-auth/pom.xml [new file with mode: 0644]
opendaylight/netconf/netconf-auth/src/main/java/org/opendaylight/controller/netconf/auth/AuthConstants.java [new file with mode: 0644]
opendaylight/netconf/netconf-auth/src/main/java/org/opendaylight/controller/netconf/auth/AuthProvider.java [moved from opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/AuthProvider.java with 56% similarity]
opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITSecureTest.java
opendaylight/netconf/netconf-ssh/pom.xml
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/NetconfSSHServer.java
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/osgi/NetconfSSHActivator.java
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/Handshaker.java
opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/StubUserManager.java [deleted file]
opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/SSHTest.java
opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/ssh/authentication/SSHServerTest.java
opendaylight/netconf/netconf-usermanager/pom.xml [new file with mode: 0644]
opendaylight/netconf/netconf-usermanager/src/main/java/org/opendaylight/controller/netconf/auth/usermanager/AuthProviderActivator.java [new file with mode: 0644]
opendaylight/netconf/netconf-usermanager/src/main/java/org/opendaylight/controller/netconf/auth/usermanager/AuthProviderImpl.java [moved from opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/AuthProviderImpl.java with 65% similarity]
opendaylight/netconf/pom.xml

index 8fd173c094db829670ea200c194f8b71aa96249e..425e968cf7ee9255cde33a7440834ac0ae05a576 100644 (file)
         <version>${netconf.version}</version>
         <type>test-jar</type>
       </dependency>
+        <dependency>
+            <groupId>org.opendaylight.controller</groupId>
+            <artifactId>netconf-auth</artifactId>
+            <version>${netconf.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.controller</groupId>
+            <artifactId>netconf-usermanager</artifactId>
+            <version>${netconf.version}</version>
+        </dependency>
       <dependency>
         <groupId>org.opendaylight.controller</groupId>
         <artifactId>netconf-ssh</artifactId>
index 9a7677099dd8be04b4357b707c135a8279153a64..fc8b4453eadc48f4fecdae144b29ddf822e9967d 100644 (file)
           <groupId>org.opendaylight.controller</groupId>
           <artifactId>netconf-ssh</artifactId>
         </dependency>
+        <dependency>
+          <groupId>org.opendaylight.controller</groupId>
+          <artifactId>netconf-auth</artifactId>
+        </dependency>
+        <dependency>
+          <groupId>org.opendaylight.controller</groupId>
+          <artifactId>netconf-usermanager</artifactId>
+        </dependency>
         <dependency>
           <groupId>org.opendaylight.controller</groupId>
           <artifactId>netconf-tcp</artifactId>
diff --git a/opendaylight/netconf/netconf-auth/pom.xml b/opendaylight/netconf/netconf-auth/pom.xml
new file mode 100644 (file)
index 0000000..e19359a
--- /dev/null
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.opendaylight.controller</groupId>
+        <artifactId>netconf-subsystem</artifactId>
+        <version>0.2.5-SNAPSHOT</version>
+        <relativePath>../</relativePath>
+    </parent>
+    <artifactId>netconf-auth</artifactId>
+    <packaging>bundle</packaging>
+    <name>${project.artifactId}</name>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
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 (file)
index 0000000..de5bdd6
--- /dev/null
@@ -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";
+}
@@ -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();
 }
index a9e8dbe86b06b76087c902521022ff06e239b870..e5e34548b3a4e7b4f0d6049920cb9cde56a3a25d 100644 (file)
@@ -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;
     }
 
index 8d48077f93174dbf211d4ac5be0568a9b6cfab9b..6dd23776ce0c0b2f642ffb71d833b577bae10fa8 100644 (file)
     <dependency>
       <groupId>${project.groupId}</groupId>
       <artifactId>netconf-api</artifactId>
+    </dependency>
+      <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>netconf-auth</artifactId>
     </dependency>
     <dependency>
       <groupId>${project.groupId}</groupId>
       <groupId>org.bouncycastle</groupId>
       <artifactId>bcprov-jdk15on</artifactId>
     </dependency>
-    <dependency>
-      <groupId>org.opendaylight.controller</groupId>
-      <artifactId>usermanager</artifactId>
-    </dependency>
     <dependency>
       <groupId>org.opendaylight.controller.thirdparty</groupId>
       <artifactId>ganymed</artifactId>
       <groupId>org.apache.sshd</groupId>
       <artifactId>sshd-core</artifactId>
     </dependency>
+      <dependency>
+          <groupId>com.google.guava</groupId>
+          <artifactId>guava</artifactId>
+      </dependency>
     <dependency>
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-api</artifactId>
         <configuration>
           <instructions>
             <Bundle-Activator>org.opendaylight.controller.netconf.ssh.osgi.NetconfSSHActivator</Bundle-Activator>
-            <Import-Package>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</Import-Package>
+            <Import-Package>*</Import-Package>
           </instructions>
         </configuration>
       </plugin>
index 670f50ddd09f9b3b80d855ce244f4a4cce47da14..86206a7d5cad932c3b8510a7c2518d36c06c4f51 100644 (file)
@@ -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> 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);
+        }
+    }
+
 }
index 503e764409e37d848ffeaf444c5e34e529bd6849..c686bcbc66fcb849da3f357bdd3bea2df7f62f2e 100644 (file)
@@ -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<InetSocketAddress> maybeSshSocketAddress = NetconfConfigUtil.extractNetconfServerAddress(bundleContext,
+    private static NetconfSSHServer startSSHServer(final BundleContext bundleContext) throws IOException {
+        final Optional<InetSocketAddress> 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<AuthProvider, AuthProvider> {
+        private final BundleContext bundleContext;
+        private final NetconfSSHServer server;
+
+        private Integer maxPreference;
+        private Thread sshThread;
+        private final ServiceTracker<AuthProvider, AuthProvider> 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<AuthProvider> 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<AuthProvider> 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<AuthProvider> 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<AuthProvider> 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
+        }
+
+    }
 }
index 3fffbb2d2c7b03cf6249c000d8610adb386b1bba..eec6c3a0971d8a3369ee05a863540bed4b85f8b2 100644 (file)
@@ -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 (file)
index 6628310..0000000
+++ /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<String> 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<AuthorizationConfig> getAuthorizationList() {
-        return null;
-    }
-
-    @Override
-    public Set<String> getAAAProviderNames() {
-        return null;
-    }
-
-    @Override
-    public Status changeLocalUserPassword(String user, String curPassword, String newPassword) {
-        return null;
-    }
-
-    @Override
-    public List<ServerConfig> getAAAServerList() {
-        return null;
-    }
-
-    @Override
-    public List<UserConfig> 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<String, List<String>> getUserLoggedIn() {
-        return null;
-    }
-
-    @Override
-    public String getAccessDate(String user) {
-        return null;
-    }
-
-    @Override
-    public UserLevel getUserLevel(String userName) {
-        return null;
-    }
-
-    @Override
-    public List<UserLevel> 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;
-    }
-
-}
index b32e880537e06d44571875a3dda56c4a36dddf6f..80107943644c2f19fadb9804c1f63c30281fb610 100644 (file)
@@ -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,11 +58,11 @@ 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());
         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);
index 75d18566eec79c775d7861d86fd0aaf2ddda2dbb..1151abcdf2767de1aa5a70fd19aa3219f9b23b1c 100644 (file)
@@ -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 (file)
index 0000000..f8c3e5a
--- /dev/null
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.opendaylight.controller</groupId>
+        <artifactId>netconf-subsystem</artifactId>
+        <version>0.2.5-SNAPSHOT</version>
+        <relativePath>../</relativePath>
+    </parent>
+    <artifactId>netconf-usermanager</artifactId>
+    <packaging>bundle</packaging>
+    <name>${project.artifactId}</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.opendaylight.controller</groupId>
+            <artifactId>netconf-auth</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.controller</groupId>
+            <artifactId>usermanager</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <configuration>
+                    <instructions>
+                        <Bundle-Activator>org.opendaylight.controller.netconf.auth.usermanager.AuthProviderActivator</Bundle-Activator>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+
+</project>
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 (file)
index 0000000..528d8ff
--- /dev/null
@@ -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<AuthProvider> 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<String, Object> 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();
+        }
+    }
+}
@@ -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<IUserManager, IUserManager> customizer = new ServiceTrackerCustomizer<IUserManager, IUserManager>() {
+        final ServiceTrackerCustomizer<IUserManager, IUserManager> customizer = new ServiceTrackerCustomizer<IUserManager, IUserManager>() {
             @Override
             public IUserManager addingService(final ServiceReference<IUserManager> reference) {
-                logger.trace("Service {} added", reference);
+                logger.trace("UerManager {} added", reference);
                 nullableUserManager = bundleContext.getService(reference);
                 return nullableUserManager;
             }
 
             @Override
             public void modifiedService(final ServiceReference<IUserManager> 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<IUserManager> 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<IUserManager, IUserManager> listenerTracker = new ServiceTracker<>(bundleContext, IUserManager.class, customizer);
+        final ServiceTracker<IUserManager, IUserManager> 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;
     }
 }
index b1b410a1fcb9971cadcb0e973d2381cbbdf3d04e..8abf67ec8c5fe198315be84d8f8d5d9d424347e0 100644 (file)
@@ -35,6 +35,8 @@
     <module>ietf-netconf-monitoring</module>
     <module>ietf-netconf-monitoring-extension</module>
     <module>netconf-connector-config</module>
+    <module>netconf-auth</module>
+    <module>netconf-usermanager</module>
   </modules>
 
   <dependencies>