<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>
<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>
--- /dev/null
+<?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>
--- /dev/null
+/*
+ * 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";
+}
* 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();
}
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;
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;
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;
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);
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) {
}
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;
}
<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>
*/
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;
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}.
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) {
}
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;
Socket acceptedSocket = null;
try {
acceptedSocket = serverSocket.accept();
- } catch (IOException e) {
+ } catch (final IOException e) {
if (up == false) {
logger.trace("Exiting server thread", e);
} else {
}
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);
+ }
+ }
+
}
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
*/
public class NetconfSSHActivator implements BundleActivator {
private static final Logger logger = LoggerFactory.getLogger(NetconfSSHActivator.class);
+ private static AuthProviderTracker authProviderTracker;
private NetconfSSHServer server;
}
@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
+ }
+
+ }
}
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;
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;
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.
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;
getGanymedAutoCloseable(ganymedConnection), localAddress, bossGroup);
// initialize ganymed
- ganymedConnection.setPEMHostKey(authProvider.getPEMAsCharArray(), null);
+ ganymedConnection.setPEMHostKey(pem, null);
ganymedConnection.setAuthenticationCallback(serverAuthenticationCallback);
ganymedConnection.setServerConnectionCallback(serverConnectionCallback);
}
+++ /dev/null
-/*
- * 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;
- }
-
-}
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;
@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);
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;
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);
--- /dev/null
+<?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>
--- /dev/null
+/*
+ * 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();
+ }
+ }
+}
* 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;
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();
}
* 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;
}
}
<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>