Add the entry for using TLS protocol type
[netconf.git] / netconf / netconf-topology / src / main / java / org / opendaylight / netconf / topology / AbstractNetconfTopology.java
index ad4bfa9029425205ccab65efdf3bc4600395a493..9bc6b59e7e8ccc7788df3144a801c11ee9bfa4a2 100644 (file)
@@ -12,18 +12,30 @@ import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+import io.netty.handler.ssl.SslHandler;
 import io.netty.util.concurrent.EventExecutor;
 import java.io.File;
+import java.io.IOException;
 import java.math.BigDecimal;
 import java.net.InetSocketAddress;
 import java.net.URL;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.TrustManagerFactory;
+import org.opendaylight.aaa.encrypt.AAAEncryptionService;
 import org.opendaylight.controller.config.threadpool.ScheduledThreadPool;
 import org.opendaylight.controller.config.threadpool.ThreadPool;
 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
@@ -31,11 +43,12 @@ import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
 import org.opendaylight.netconf.api.NetconfMessage;
 import org.opendaylight.netconf.client.NetconfClientDispatcher;
 import org.opendaylight.netconf.client.NetconfClientSessionListener;
+import org.opendaylight.netconf.client.SslHandlerFactory;
 import org.opendaylight.netconf.client.conf.NetconfClientConfiguration;
 import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfiguration;
 import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfigurationBuilder;
 import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.AuthenticationHandler;
-import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPassword;
+import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPasswordHandler;
 import org.opendaylight.netconf.sal.connect.api.RemoteDevice;
 import org.opendaylight.netconf.sal.connect.api.RemoteDeviceHandler;
 import org.opendaylight.netconf.sal.connect.netconf.LibraryModulesSchemas;
@@ -43,11 +56,13 @@ import org.opendaylight.netconf.sal.connect.netconf.NetconfDevice;
 import org.opendaylight.netconf.sal.connect.netconf.NetconfDeviceBuilder;
 import org.opendaylight.netconf.sal.connect.netconf.NetconfStateSchemasResolverImpl;
 import org.opendaylight.netconf.sal.connect.netconf.SchemalessNetconfDevice;
+import org.opendaylight.netconf.sal.connect.netconf.auth.DatastoreBackedPublicKeyAuth;
 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCapabilities;
 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCommunicator;
 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
 import org.opendaylight.netconf.sal.connect.netconf.listener.UserPreferences;
 import org.opendaylight.netconf.sal.connect.netconf.sal.KeepaliveSalFacade;
+import org.opendaylight.netconf.sal.connect.netconf.sal.NetconfKeystoreAdapter;
 import org.opendaylight.netconf.sal.connect.netconf.schema.YangLibrarySchemaYangSourceProvider;
 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
 import org.opendaylight.netconf.topology.api.NetconfTopology;
@@ -58,8 +73,17 @@ import org.opendaylight.protocol.framework.TimedReconnectStrategy;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Host;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.Protocol.Name;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.protocol.Specification;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.protocol.specification.TlsCase;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapability.CapabilityOrigin;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.Credentials;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.KeyAuth;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPw;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPwUnencrypted;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.key.auth.KeyBased;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.LoginPassword;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.unencrypted.LoginPasswordUnencrypted;
 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
 import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactory;
@@ -72,7 +96,7 @@ import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistration;
 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
 import org.opendaylight.yangtools.yang.model.repo.util.FilesystemSchemaSourceCache;
 import org.opendaylight.yangtools.yang.parser.repo.SharedSchemaRepository;
-import org.opendaylight.yangtools.yang.parser.util.TextToASTTransformer;
+import org.opendaylight.yangtools.yang.parser.rfc7950.repo.TextToASTTransformer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -160,18 +184,21 @@ public abstract class AbstractNetconfTopology implements NetconfTopology {
     protected final SharedSchemaRepository sharedSchemaRepository;
     protected final DataBroker dataBroker;
     protected final DOMMountPointService mountPointService;
-
+    private final NetconfKeystoreAdapter keystoreAdapter;
     protected SchemaSourceRegistry schemaRegistry = DEFAULT_SCHEMA_REPOSITORY;
     protected SchemaRepository schemaRepository = DEFAULT_SCHEMA_REPOSITORY;
     protected SchemaContextFactory schemaContextFactory = DEFAULT_SCHEMA_CONTEXT_FACTORY;
-
+    protected String privateKeyPath;
+    protected String privateKeyPassphrase;
+    protected final AAAEncryptionService encryptionService;
     protected final HashMap<NodeId, NetconfConnectorDTO> activeConnectors = new HashMap<>();
 
     protected AbstractNetconfTopology(final String topologyId, final NetconfClientDispatcher clientDispatcher,
                                       final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor,
                                       final ThreadPool processingExecutor,
                                       final SchemaRepositoryProvider schemaRepositoryProvider,
-                                      final DataBroker dataBroker, final DOMMountPointService mountPointService) {
+                                      final DataBroker dataBroker, final DOMMountPointService mountPointService,
+                                      final AAAEncryptionService encryptionService) {
         this.topologyId = topologyId;
         this.clientDispatcher = clientDispatcher;
         this.eventExecutor = eventExecutor;
@@ -180,6 +207,9 @@ public abstract class AbstractNetconfTopology implements NetconfTopology {
         this.sharedSchemaRepository = schemaRepositoryProvider.getSharedSchemaRepository();
         this.dataBroker = dataBroker;
         this.mountPointService = mountPointService;
+        this.encryptionService = encryptionService;
+
+        this.keystoreAdapter = new NetconfKeystoreAdapter(dataBroker);
     }
 
     public void setSchemaRegistry(final SchemaSourceRegistry schemaRegistry) {
@@ -212,7 +242,7 @@ public abstract class AbstractNetconfTopology implements NetconfTopology {
     }
 
     protected ListenableFuture<NetconfDeviceCapabilities> setupConnection(final NodeId nodeId,
-                                                                        final Node configNode) {
+                                                                          final Node configNode) {
         final NetconfNode netconfNode = configNode.getAugmentation(NetconfNode.class);
 
         Preconditions.checkNotNull(netconfNode.getHost());
@@ -240,13 +270,13 @@ public abstract class AbstractNetconfTopology implements NetconfTopology {
                 LOG.error("Connector for : " + nodeId.getValue() + " failed");
                 // remove this node from active connectors?
             }
-        });
+        }, MoreExecutors.directExecutor());
 
         return future;
     }
 
     protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId,
-                                                         final NetconfNode node) {
+                                                           final NetconfNode node) {
         //setup default values since default value is not supported in mdsal
         final Long defaultRequestTimeoutMillis = node.getDefaultRequestTimeoutMillis() == null
                 ? DEFAULT_REQUEST_TIMEOUT_MILLIS : node.getDefaultRequestTimeoutMillis();
@@ -373,14 +403,14 @@ public abstract class AbstractNetconfTopology implements NetconfTopology {
      */
     private NetconfDevice.SchemaResourcesDTO createSchemaResourcesDTO(final String moduleSchemaCacheDirectory) {
         final SharedSchemaRepository repository = new SharedSchemaRepository(moduleSchemaCacheDirectory);
-        final SchemaContextFactory schemaContextFactory
+        final SchemaContextFactory contextFactory
                 = repository.createSchemaContextFactory(SchemaSourceFilter.ALWAYS_ACCEPT);
         setSchemaRegistry(repository);
-        setSchemaContextFactory(schemaContextFactory);
+        setSchemaContextFactory(contextFactory);
         final FilesystemSchemaSourceCache<YangTextSchemaSource> deviceCache =
                 createDeviceFilesystemCache(moduleSchemaCacheDirectory);
         repository.registerSchemaSourceListener(deviceCache);
-        return new NetconfDevice.SchemaResourcesDTO(repository, repository, schemaContextFactory,
+        return new NetconfDevice.SchemaResourcesDTO(repository, repository, contextFactory,
                 new NetconfStateSchemasResolverImpl());
     }
 
@@ -397,6 +427,20 @@ public abstract class AbstractNetconfTopology implements NetconfTopology {
                 new File(relativeSchemaCacheDirectory));
     }
 
+    /**
+     * Sets the private key path from location specified in configuration file using blueprint.
+     */
+    public void setPrivateKeyPath(final String privateKeyPath) {
+        this.privateKeyPath = privateKeyPath;
+    }
+
+    /**
+     * Sets the private key passphrase from location specified in configuration file using blueprint.
+     */
+    public void setPrivateKeyPassphrase(final String privateKeyPassphrase) {
+        this.privateKeyPassphrase = privateKeyPassphrase;
+    }
+
     public NetconfReconnectingClientConfiguration getClientConfig(final NetconfClientSessionListener listener,
                                                                   final NetconfNode node) {
 
@@ -415,31 +459,61 @@ public abstract class AbstractNetconfTopology implements NetconfTopology {
                 maxConnectionAttempts, betweenAttemptsTimeoutMillis, sleepFactor);
         final ReconnectStrategy strategy = sf.createReconnectStrategy();
 
-        final AuthenticationHandler authHandler;
-        final Credentials credentials = node.getCredentials();
-        if (credentials instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114
-                .netconf.node.credentials.credentials.LoginPassword) {
-            authHandler = new LoginPassword(
-                    ((org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114
-                            .netconf.node.credentials.credentials.LoginPassword) credentials).getUsername(),
-                    ((org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114
-                            .netconf.node.credentials.credentials.LoginPassword) credentials).getPassword());
+        final NetconfReconnectingClientConfigurationBuilder reconnectingClientConfigurationBuilder =
+                NetconfReconnectingClientConfigurationBuilder.create();
+
+        if (node.isTcpOnly() || node.getProtocol() == null || node.getProtocol().getName() == Name.SSH) {
+            final AuthenticationHandler authHandler = getHandlerFromCredentials(node.getCredentials());
+            reconnectingClientConfigurationBuilder
+                .withAuthHandler(authHandler)
+                .withProtocol(node.isTcpOnly() ? NetconfClientConfiguration.NetconfClientProtocol.TCP :
+                    NetconfClientConfiguration.NetconfClientProtocol.SSH);
+        } else if (node.getProtocol().getName() == Name.TLS) {
+            final SslHandlerFactory sslHandlerFactory = new SslHandlerFactoryImpl(keystoreAdapter,
+                    node.getProtocol().getSpecification());
+            reconnectingClientConfigurationBuilder
+                .withSslHandlerFactory(sslHandlerFactory)
+                .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TLS);
         } else {
-            throw new IllegalStateException("Only login/password authentification is supported");
+            throw new IllegalStateException("Unsupported protocol type: " + node.getProtocol().getName().getClass());
         }
 
-        return NetconfReconnectingClientConfigurationBuilder.create()
+        return reconnectingClientConfigurationBuilder
                 .withAddress(socketAddress)
                 .withConnectionTimeoutMillis(clientConnectionTimeoutMillis)
                 .withReconnectStrategy(strategy)
-                .withAuthHandler(authHandler)
-                .withProtocol(node.isTcpOnly() ? NetconfClientConfiguration.NetconfClientProtocol.TCP :
-                        NetconfClientConfiguration.NetconfClientProtocol.SSH)
                 .withConnectStrategyFactory(sf)
                 .withSessionListener(listener)
                 .build();
     }
 
+    private AuthenticationHandler getHandlerFromCredentials(final Credentials credentials) {
+        if (credentials instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology
+                .rev150114.netconf.node.credentials.credentials.LoginPassword) {
+            final org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology
+                    .rev150114.netconf.node.credentials.credentials.LoginPassword loginPassword
+                    = (org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology
+                    .rev150114.netconf.node.credentials.credentials.LoginPassword) credentials;
+            return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
+        }
+        if (credentials instanceof LoginPwUnencrypted) {
+            final LoginPasswordUnencrypted loginPassword =
+                    ((LoginPwUnencrypted) credentials).getLoginPasswordUnencrypted();
+            return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
+        }
+        if (credentials instanceof LoginPw) {
+            final LoginPassword loginPassword = ((LoginPw) credentials).getLoginPassword();
+            return new LoginPasswordHandler(loginPassword.getUsername(),
+                    encryptionService.decrypt(loginPassword.getPassword()));
+        }
+        if (credentials instanceof KeyAuth) {
+            final KeyBased keyPair = ((KeyAuth) credentials).getKeyBased();
+            return new DatastoreBackedPublicKeyAuth(keyPair.getUsername(), keyPair.getKeyId(),
+                    keystoreAdapter, encryptionService);
+        }
+        throw new IllegalStateException("Unsupported credential type: " + credentials.getClass());
+    }
+
     protected abstract RemoteDeviceHandler<NetconfSessionPreferences> createSalFacade(RemoteDeviceId id);
 
     private InetSocketAddress getSocketAddress(final Host host, final int port) {
@@ -542,4 +616,50 @@ public abstract class AbstractNetconfTopology implements NetconfTopology {
             facade.close();
         }
     }
+
+    private static final class SslHandlerFactoryImpl implements SslHandlerFactory {
+        private final NetconfKeystoreAdapter keystoreAdapter;
+        private final Optional<Specification> specOptional;
+
+        SslHandlerFactoryImpl(final NetconfKeystoreAdapter keystoreAdapter, final Specification specification) {
+            this.keystoreAdapter = keystoreAdapter;
+            this.specOptional = Optional.fromNullable(specification);
+        }
+
+        @Override
+        public SslHandler createSslHandler() {
+            try {
+                final KeyStore keyStore = keystoreAdapter.getJavaKeyStore();
+
+                final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+                kmf.init(keyStore, "".toCharArray());
+
+                final TrustManagerFactory tmf =
+                        TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+                tmf.init(keyStore);
+
+                final SSLContext sslCtx = SSLContext.getInstance("TLS");
+                sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+                final SSLEngine engine = sslCtx.createSSLEngine();
+                engine.setUseClientMode(true);
+
+                final Set<String> protocols = Sets.newHashSet(engine.getSupportedProtocols());
+                if (specOptional.isPresent()) {
+                    final Specification specification = specOptional.get();
+                    if (!(specification instanceof TlsCase)) {
+                        throw new IllegalArgumentException("Cannot get TLS specification from: " + specification);
+                    }
+                    protocols.removeAll(((TlsCase)specification).getTls().getExcludedVersions());
+                }
+
+                engine.setEnabledProtocols(protocols.toArray(new String[0]));
+                engine.setEnabledCipherSuites(engine.getSupportedCipherSuites());
+                engine.setEnableSessionCreation(true);
+
+                return new SslHandler(engine);
+            } catch (GeneralSecurityException | IOException exc) {
+                throw new IllegalStateException(exc);
+            }
+        }
+    }
 }