X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=netconf%2Fnetconf-topology%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fnetconf%2Ftopology%2FAbstractNetconfTopology.java;h=9bc6b59e7e8ccc7788df3144a801c11ee9bfa4a2;hb=32c6b3e6afb7d118d5615ec001a9f53705b8eecb;hp=ad4bfa9029425205ccab65efdf3bc4600395a493;hpb=57d2a366160965279304fbc24df0f946b09ea9ac;p=netconf.git diff --git a/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/AbstractNetconfTopology.java b/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/AbstractNetconfTopology.java index ad4bfa9029..9bc6b59e7e 100644 --- a/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/AbstractNetconfTopology.java +++ b/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/AbstractNetconfTopology.java @@ -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 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 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 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 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 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 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); + } + } + } }