Add Call-Home implementation for the TLS secure-transport 39/90539/41
authorOleksii Mozghovyi <oleksii.mozghovyi@bluefield.tech>
Mon, 15 Jun 2020 20:07:04 +0000 (23:07 +0300)
committerRobert Varga <robert.varga@pantheon.tech>
Thu, 29 Oct 2020 11:36:35 +0000 (12:36 +0100)
This is a quick implementation of TLS transport. There are a few
wiring issues, which will need a follow up.

JIRA: NETCONF-5
Change-Id: I666444b7c187d2afbc8869a32331cba4a1393add
Signed-off-by: Oleksii Mozghovyi <oleksii.mozghovyi@pantheon.tech>
Signed-off-by: Vladyslav Marchenko <vladyslav.marchenko@pantheon.tech>
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
netconf/callhome-protocol/src/main/java/org/opendaylight/netconf/callhome/protocol/tls/CallHomeTlsSessionContext.java [new file with mode: 0644]
netconf/callhome-protocol/src/main/java/org/opendaylight/netconf/callhome/protocol/tls/NetconfCallHomeTlsServer.java [new file with mode: 0644]
netconf/callhome-protocol/src/main/java/org/opendaylight/netconf/callhome/protocol/tls/NetconfCallHomeTlsServerBuilder.java [new file with mode: 0644]
netconf/callhome-protocol/src/main/java/org/opendaylight/netconf/callhome/protocol/tls/TlsAuthChannelInitializer.java [new file with mode: 0644]
netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/IetfZeroTouchCallHomeServerProvider.java
netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/tls/Configuration.java [new file with mode: 0644]
netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/tls/NetconfCallHomeTlsService.java [new file with mode: 0644]
netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/tls/SslHandlerFactoryAdapter.java [new file with mode: 0644]
netconf/callhome-provider/src/main/resources/OSGI-INF/blueprint/callhome-topology.xml
netconf/netconf-client/src/main/java/org/opendaylight/netconf/client/TlsClientChannelInitializer.java

diff --git a/netconf/callhome-protocol/src/main/java/org/opendaylight/netconf/callhome/protocol/tls/CallHomeTlsSessionContext.java b/netconf/callhome-protocol/src/main/java/org/opendaylight/netconf/callhome/protocol/tls/CallHomeTlsSessionContext.java
new file mode 100644 (file)
index 0000000..4e9f383
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2020 Pantheon Technologies, s.r.o. 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.netconf.callhome.protocol.tls;
+
+import static java.util.Objects.requireNonNull;
+
+import io.netty.channel.Channel;
+import io.netty.handler.ssl.SslHandler;
+import io.netty.util.HashedWheelTimer;
+import io.netty.util.concurrent.GlobalEventExecutor;
+import io.netty.util.concurrent.Promise;
+import java.net.SocketAddress;
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import org.opendaylight.netconf.callhome.protocol.CallHomeNetconfSubsystemListener;
+import org.opendaylight.netconf.callhome.protocol.CallHomeProtocolSessionContext;
+import org.opendaylight.netconf.callhome.protocol.TransportType;
+import org.opendaylight.netconf.client.NetconfClientSession;
+import org.opendaylight.netconf.client.NetconfClientSessionListener;
+import org.opendaylight.netconf.client.NetconfClientSessionNegotiatorFactory;
+import org.opendaylight.netconf.client.SslHandlerFactory;
+import org.opendaylight.netconf.client.TlsClientChannelInitializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+final class CallHomeTlsSessionContext implements CallHomeProtocolSessionContext {
+    private static final Logger LOG = LoggerFactory.getLogger(CallHomeTlsSessionContext.class);
+
+    private final AtomicBoolean activated = new AtomicBoolean();
+    private final SslHandlerFactory sslHandlerFactory;
+    private final CallHomeNetconfSubsystemListener subsystemListener;
+    private final String channelId;
+    private final Channel channel;
+    private final PublicKey publicKey;
+    private final SocketAddress socketAddress;
+
+    CallHomeTlsSessionContext(final Channel channel, final SslHandlerFactory sslHandlerFactory,
+                              final CallHomeNetconfSubsystemListener subsystemListener) {
+        this.channel = requireNonNull(channel, "channel");
+        this.channelId = channel.id().asLongText();
+        this.socketAddress = channel.remoteAddress();
+        this.publicKey = createPublicKey(channel);
+        this.sslHandlerFactory = requireNonNull(sslHandlerFactory, "sslHandlerFactory");
+        this.subsystemListener = subsystemListener;
+    }
+
+    private static Promise<NetconfClientSession> newSessionPromise() {
+        return GlobalEventExecutor.INSTANCE.newPromise();
+    }
+
+    void openNetconfChannel(final Channel ch) {
+        LOG.debug("Opening NETCONF Subsystem on TLS connection {}", channelId);
+        subsystemListener.onNetconfSubsystemOpened(this, listener -> doActivate(ch, listener));
+    }
+
+    @Override
+    public void terminate() {
+        channel.close();
+    }
+
+    private Promise<NetconfClientSession> doActivate(final Channel ch, final NetconfClientSessionListener listener) {
+        final Promise<NetconfClientSession> activationPromise = newSessionPromise();
+        if (!activated.compareAndExchange(false, true)) {
+            return activationPromise.setFailure(new IllegalStateException("Session (channel) already activated."));
+        }
+
+        LOG.info("Activating Netconf channel for {} with {}", getRemoteAddress(), listener);
+        final NetconfClientSessionNegotiatorFactory negotiatorFactory = new NetconfClientSessionNegotiatorFactory(
+            new HashedWheelTimer(), Optional.empty(), TimeUnit.SECONDS.toMillis(5));
+        final TlsClientChannelInitializer tlsClientChannelInitializer = new TlsClientChannelInitializer(
+            sslHandlerFactory, negotiatorFactory, listener);
+        tlsClientChannelInitializer.initialize(ch, activationPromise);
+        return activationPromise;
+    }
+
+    @Override
+    public PublicKey getRemoteServerKey() {
+        return publicKey;
+    }
+
+    @Override
+    public SocketAddress getRemoteAddress() {
+        return socketAddress;
+    }
+
+    @Override
+    public String getSessionId() {
+        return channelId;
+    }
+
+    @Override
+    public TransportType getTransportType() {
+        return TransportType.TLS;
+    }
+
+    private static PublicKey createPublicKey(final Channel ch) {
+        final SslHandler sslHandler = ch.pipeline().get(SslHandler.class);
+        final Certificate[] certificates;
+        try {
+            certificates = sslHandler.engine().getSession().getPeerCertificates();
+        } catch (SSLPeerUnverifiedException e) {
+            LOG.error("Peer certificate was not established during the handshake", e);
+            throw new IllegalStateException("No peer certificate present", e);
+        }
+        return certificates[0].getPublicKey();
+    }
+}
diff --git a/netconf/callhome-protocol/src/main/java/org/opendaylight/netconf/callhome/protocol/tls/NetconfCallHomeTlsServer.java b/netconf/callhome-protocol/src/main/java/org/opendaylight/netconf/callhome/protocol/tls/NetconfCallHomeTlsServer.java
new file mode 100644 (file)
index 0000000..ce3111c
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2020 Pantheon Technologies, s.r.o. 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.netconf.callhome.protocol.tls;
+
+import static java.util.Objects.requireNonNull;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.util.concurrent.Future;
+import io.netty.util.concurrent.GenericFutureListener;
+import java.net.InetSocketAddress;
+import org.opendaylight.netconf.callhome.protocol.CallHomeNetconfSubsystemListener;
+import org.opendaylight.netconf.client.SslHandlerFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class NetconfCallHomeTlsServer {
+    private static final Logger LOG = LoggerFactory.getLogger(NetconfCallHomeTlsServer.class);
+
+    private final String host;
+    private final Integer port;
+    private final Integer timeout;
+    private final Integer maxConnections;
+    private final SslHandlerFactory sslHandlerFactory;
+    private final CallHomeNetconfSubsystemListener subsystemListener;
+    private final EventLoopGroup bossGroup;
+    private final EventLoopGroup workerGroup;
+
+    private ChannelFuture cf;
+
+    NetconfCallHomeTlsServer(final String host, final Integer port, final Integer timeout, final Integer maxConnections,
+                             final SslHandlerFactory sslHandlerFactory,
+                             final CallHomeNetconfSubsystemListener subsystemListener,
+                             final EventLoopGroup bossGroup, final EventLoopGroup workerGroup) {
+        this.host = requireNonNull(host);
+        this.port = requireNonNull(port);
+        this.timeout = requireNonNull(timeout);
+        this.maxConnections = requireNonNull(maxConnections);
+        this.sslHandlerFactory = requireNonNull(sslHandlerFactory);
+        this.subsystemListener = requireNonNull(subsystemListener);
+        this.bossGroup = requireNonNull(bossGroup);
+        this.workerGroup = requireNonNull(workerGroup);
+    }
+
+    public void start() {
+        final ChannelFuture bindFuture = new ServerBootstrap()
+            .group(bossGroup, workerGroup)
+            // FIXME: a case against using globals: we really would like to use epoll() here
+            .channel(NioServerSocketChannel.class)
+            .localAddress(new InetSocketAddress(host, port))
+            .childOption(ChannelOption.SO_KEEPALIVE, true)
+            .childOption(ChannelOption.SO_BACKLOG, maxConnections)
+            .childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, timeout)
+            .childHandler(new TlsAuthChannelInitializer(sslHandlerFactory, handshakeListener))
+            .bind();
+        bindFuture.addListener(bindListener);
+    }
+
+    GenericFutureListener<Future<Channel>> handshakeListener = new GenericFutureListener<>() {
+        @Override
+        public void operationComplete(final Future<Channel> future) throws Exception {
+            if (future.isSuccess()) {
+                LOG.debug("SSL handshake completed successfully, accepting connection...");
+                final Channel channel = future.get();
+                final CallHomeTlsSessionContext tlsSessionContext = new CallHomeTlsSessionContext(channel,
+                    sslHandlerFactory, subsystemListener);
+                tlsSessionContext.openNetconfChannel(channel);
+            } else {
+                LOG.debug("SSL handshake failed, rejecting connection...");
+                future.get().close();
+            }
+        }
+    };
+
+    GenericFutureListener<ChannelFuture> bindListener = future -> {
+        if (future.isSuccess()) {
+            LOG.debug("Call-Home TLS server bind completed");
+        } else {
+            LOG.error("Call-Home TLS server bind failed", future.cause());
+        }
+        cf = future.channel().closeFuture().addListener(f -> stop());
+    };
+
+    public void stop() {
+        LOG.debug("Stopping the Call-Home TLS server...");
+        try {
+            if (cf != null && cf.channel().isOpen()) {
+                cf.channel().close().sync();
+            }
+        } catch (final InterruptedException e) {
+            LOG.error("Error during shutdown of the Call-Home TLS server", e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/netconf/callhome-protocol/src/main/java/org/opendaylight/netconf/callhome/protocol/tls/NetconfCallHomeTlsServerBuilder.java b/netconf/callhome-protocol/src/main/java/org/opendaylight/netconf/callhome/protocol/tls/NetconfCallHomeTlsServerBuilder.java
new file mode 100644 (file)
index 0000000..9740065
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2020 Pantheon Technologies, s.r.o. 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.netconf.callhome.protocol.tls;
+
+import io.netty.channel.EventLoopGroup;
+import org.opendaylight.netconf.callhome.protocol.CallHomeNetconfSubsystemListener;
+import org.opendaylight.netconf.client.SslHandlerFactory;
+
+public class NetconfCallHomeTlsServerBuilder {
+    private String host = "0.0.0.0";
+    private Integer port = 4335;
+    private Integer timeout = 10;
+    private Integer maxConnections = 64;
+    private SslHandlerFactory sslHandlerFactory;
+    private CallHomeNetconfSubsystemListener subsystemListener;
+    private EventLoopGroup bossGroup;
+    private EventLoopGroup workerGroup;
+
+    public NetconfCallHomeTlsServerBuilder setHost(final String host) {
+        this.host = host;
+        return this;
+    }
+
+    public NetconfCallHomeTlsServerBuilder setPort(final Integer port) {
+        this.port = port;
+        return this;
+    }
+
+    public NetconfCallHomeTlsServerBuilder setTimeout(final Integer timeout) {
+        this.timeout = timeout;
+        return this;
+    }
+
+    public NetconfCallHomeTlsServerBuilder setMaxConnections(final Integer maxConnections) {
+        this.maxConnections = maxConnections;
+        return this;
+    }
+
+    public NetconfCallHomeTlsServerBuilder setSslHandlerFactory(final SslHandlerFactory sslHandlerFactory) {
+        this.sslHandlerFactory = sslHandlerFactory;
+        return this;
+    }
+
+    public NetconfCallHomeTlsServerBuilder setSubsystemListener(final CallHomeNetconfSubsystemListener listener) {
+        this.subsystemListener = listener;
+        return this;
+    }
+
+    public NetconfCallHomeTlsServerBuilder setBossGroup(final EventLoopGroup bossGroup) {
+        this.bossGroup = bossGroup;
+        return this;
+    }
+
+    public NetconfCallHomeTlsServerBuilder setWorkerGroup(final EventLoopGroup workerGroup) {
+        this.workerGroup = workerGroup;
+        return this;
+    }
+
+    public NetconfCallHomeTlsServer build() {
+        return new NetconfCallHomeTlsServer(host, port, timeout, maxConnections, sslHandlerFactory, subsystemListener,
+            bossGroup, workerGroup);
+    }
+}
\ No newline at end of file
diff --git a/netconf/callhome-protocol/src/main/java/org/opendaylight/netconf/callhome/protocol/tls/TlsAuthChannelInitializer.java b/netconf/callhome-protocol/src/main/java/org/opendaylight/netconf/callhome/protocol/tls/TlsAuthChannelInitializer.java
new file mode 100644 (file)
index 0000000..a413174
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2020 Pantheon Technologies, s.r.o. 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.netconf.callhome.protocol.tls;
+
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelInitializer;
+import io.netty.handler.ssl.SslHandler;
+import io.netty.util.concurrent.GenericFutureListener;
+import org.opendaylight.netconf.client.SslHandlerFactory;
+
+@SuppressWarnings("rawtypes")
+final class TlsAuthChannelInitializer extends ChannelInitializer {
+
+    private static final String SSL_HANDLER_CHANNEL_NAME = "sslHandler";
+
+    private final SslHandlerFactory sslHandlerFactory;
+    private final GenericFutureListener listener;
+
+    TlsAuthChannelInitializer(final SslHandlerFactory sslHandlerFactory, final GenericFutureListener listener) {
+        this.sslHandlerFactory = sslHandlerFactory;
+        this.listener = listener;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public void initChannel(final Channel ch) {
+        final SslHandler sslHandler = sslHandlerFactory.createSslHandler();
+        sslHandler.handshakeFuture().addListener(listener);
+        ch.pipeline().addFirst(SSL_HANDLER_CHANNEL_NAME, sslHandler).fireChannelActive();
+    }
+}
\ No newline at end of file
index e2a99a34c34e683c7bcf607814ca67c34d6e79bf..529344d6b96894391e3c16f48cc3a7a3e5ce198a 100644 (file)
@@ -43,6 +43,7 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.device.Transport;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.device.transport.Ssh;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.device.transport.SshBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.device.transport.Tls;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.device.transport.ssh.SshClientParams;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.device.transport.ssh.SshClientParamsBuilder;
 import org.opendaylight.yangtools.concepts.ListenerRegistration;
@@ -249,6 +250,8 @@ public class IetfZeroTouchCallHomeServerProvider implements AutoCloseable, DataT
             final SshClientParams params = new SshClientParamsBuilder().setHostKey(hostKey).build();
             final Transport sshTransport = new SshBuilder().setSshClientParams(params).build();
             deviceBuilder.setTransport(sshTransport);
+        } else if (cfgDevice.getTransport() instanceof Tls) {
+            deviceBuilder.setTransport(cfgDevice.getTransport());
         } else if (cfgDevice.getSshHostKey() != null) {
             deviceBuilder.setSshHostKey(cfgDevice.getSshHostKey());
         }
diff --git a/netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/tls/Configuration.java b/netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/tls/Configuration.java
new file mode 100644 (file)
index 0000000..dfe65d6
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2020 Pantheon Technologies, s.r.o. 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.netconf.callhome.mount.tls;
+
+public class Configuration {
+    private String host;
+    private Integer port;
+    private Integer timeout;
+    private Integer maxConnections;
+
+    public Configuration() {
+        // empty constructor
+    }
+
+    public String getHost() {
+        return host;
+    }
+
+    public void setHost(final String host) {
+        this.host = host;
+    }
+
+    public Integer getPort() {
+        return port;
+    }
+
+    public void setPort(final Integer port) {
+        this.port = port;
+    }
+
+    public Integer getTimeout() {
+        return timeout;
+    }
+
+    public void setTimeout(final Integer timeout) {
+        this.timeout = timeout;
+    }
+
+    public Integer getMaxConnections() {
+        return maxConnections;
+    }
+
+    public void setMaxConnections(final Integer maxConnections) {
+        this.maxConnections = maxConnections;
+    }
+}
\ No newline at end of file
diff --git a/netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/tls/NetconfCallHomeTlsService.java b/netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/tls/NetconfCallHomeTlsService.java
new file mode 100644 (file)
index 0000000..8ea2465
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2020 Pantheon Technologies, s.r.o. 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.netconf.callhome.mount.tls;
+
+import static java.util.Objects.requireNonNull;
+
+import io.netty.channel.EventLoopGroup;
+import org.opendaylight.mdsal.binding.api.DataBroker;
+import org.opendaylight.netconf.callhome.protocol.CallHomeNetconfSubsystemListener;
+import org.opendaylight.netconf.callhome.protocol.tls.NetconfCallHomeTlsServer;
+import org.opendaylight.netconf.callhome.protocol.tls.NetconfCallHomeTlsServerBuilder;
+import org.opendaylight.netconf.client.SslHandlerFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class NetconfCallHomeTlsService implements AutoCloseable {
+    private static final Logger LOG = LoggerFactory.getLogger(NetconfCallHomeTlsService.class);
+
+    private final Configuration config;
+    private final SslHandlerFactory sslHandlerFactory;
+    private final CallHomeNetconfSubsystemListener subsystemListener;
+    private final EventLoopGroup bossGroup;
+    private final EventLoopGroup workerGroup;
+
+    private NetconfCallHomeTlsServer server;
+
+    public NetconfCallHomeTlsService(final Configuration config,
+                                     final DataBroker dataBroker,
+                                     final CallHomeNetconfSubsystemListener subsystemListener,
+                                     final EventLoopGroup bossGroup,
+                                     final EventLoopGroup workerGroup) {
+        this.config = requireNonNull(config);
+        this.subsystemListener = requireNonNull(subsystemListener);
+        this.bossGroup = requireNonNull(bossGroup);
+        this.workerGroup = requireNonNull(workerGroup);
+        this.sslHandlerFactory = new SslHandlerFactoryAdapter(dataBroker);
+    }
+
+    public void init() {
+        LOG.info("Initializing Call Home TLS server instance");
+
+        final NetconfCallHomeTlsServerBuilder builder = new NetconfCallHomeTlsServerBuilder();
+        server = builder.setHost(config.getHost())
+            .setPort(config.getPort())
+            .setTimeout(config.getTimeout())
+            .setMaxConnections(config.getMaxConnections())
+            .setSslHandlerFactory(sslHandlerFactory)
+            .setSubsystemListener(subsystemListener)
+            .setBossGroup(bossGroup)
+            .setWorkerGroup(workerGroup)
+            .build();
+        server.start();
+
+        LOG.info("Initializing Call Home TLS server instance completed successfuly");
+    }
+
+    @Override
+    public void close() {
+        server.stop();
+    }
+}
\ No newline at end of file
diff --git a/netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/tls/SslHandlerFactoryAdapter.java b/netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/tls/SslHandlerFactoryAdapter.java
new file mode 100644 (file)
index 0000000..3dfe21b
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2020 Pantheon Technologies, s.r.o. 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.netconf.callhome.mount.tls;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.collect.ImmutableSet;
+import io.netty.handler.ssl.SslHandler;
+import java.util.Collection;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import org.opendaylight.mdsal.binding.api.DataBroker;
+import org.opendaylight.mdsal.binding.api.DataObjectModification;
+import org.opendaylight.mdsal.binding.api.DataTreeChangeListener;
+import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
+import org.opendaylight.mdsal.binding.api.DataTreeModification;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.netconf.client.SslHandlerFactory;
+import org.opendaylight.netconf.sal.connect.netconf.sal.NetconfKeystoreAdapter;
+import org.opendaylight.netconf.sal.connect.util.SslHandlerFactoryImpl;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.NetconfCallhomeServer;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.AllowedDevices;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.Device;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.device.transport.Tls;
+import org.opendaylight.yangtools.concepts.AbstractRegistration;
+import org.opendaylight.yangtools.concepts.Registration;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SslHandlerFactoryAdapter extends AbstractRegistration implements SslHandlerFactory {
+    private static final DataTreeIdentifier<Device> ALLOWED_DEVICES =
+        DataTreeIdentifier.create(LogicalDatastoreType.CONFIGURATION,
+            InstanceIdentifier.builder(NetconfCallhomeServer.class).child(AllowedDevices.class).child(Device.class)
+                .build());
+
+    private static final Logger LOG = LoggerFactory.getLogger(SslHandlerFactoryAdapter.class);
+
+    private final DeviceListener deviceListener = new DeviceListener();
+    private final NetconfKeystoreAdapter keystoreAdapter;
+    private final SslHandlerFactory sslHandlerFactory;
+    private final Registration deviceListenerReg;
+
+    public SslHandlerFactoryAdapter(final DataBroker dataBroker) {
+        this.keystoreAdapter = new NetconfKeystoreAdapter(dataBroker);
+        this.sslHandlerFactory = new SslHandlerFactoryImpl(keystoreAdapter);
+        this.deviceListenerReg = dataBroker.registerDataTreeChangeListener(ALLOWED_DEVICES, deviceListener);
+    }
+
+    @Override
+    public SslHandler createSslHandler() {
+        return createSslHandlerFilteredByKeys();
+    }
+
+    @Override
+    public SslHandler createSslHandler(final Set<String> allowedKeys) {
+        return createSslHandlerFilteredByKeys();
+    }
+
+    @Override
+    protected void removeRegistration() {
+        deviceListenerReg.close();
+    }
+
+    private SslHandler createSslHandlerFilteredByKeys() {
+        return sslHandlerFactory.createSslHandler(deviceListener.getAllowedKeys());
+    }
+
+    private static final class DeviceListener implements DataTreeChangeListener<Device> {
+        private final ConcurrentMap<String, String> allowedKeys = new ConcurrentHashMap<>();
+
+        @Override
+        public void onDataTreeChanged(final Collection<DataTreeModification<Device>> mods) {
+            for (final DataTreeModification<Device> dataTreeModification : mods) {
+                final DataObjectModification<Device> deviceMod = dataTreeModification.getRootNode();
+                final DataObjectModification.ModificationType modType = deviceMod.getModificationType();
+                switch (modType) {
+                    case DELETE:
+                        deleteDevice(deviceMod.getDataBefore());
+                        break;
+                    case SUBTREE_MODIFIED:
+                    case WRITE:
+                        deleteDevice(deviceMod.getDataBefore());
+                        writeDevice(deviceMod.getDataAfter());
+                        break;
+                    default:
+                        throw new IllegalStateException("Unhandled modification type " + modType);
+                }
+            }
+        }
+
+        Set<String> getAllowedKeys() {
+            final Set<String> ret = ImmutableSet.copyOf(allowedKeys.values());
+            checkState(!ret.isEmpty(), "No associated keys for TLS authentication were found");
+            return ret;
+        }
+
+        private void deleteDevice(final Device dataBefore) {
+            if (dataBefore != null && dataBefore.getTransport() instanceof Tls) {
+                LOG.debug("Removing device {}", dataBefore.getUniqueId());
+                allowedKeys.remove(dataBefore.getUniqueId());
+            }
+        }
+
+        private void writeDevice(final Device dataAfter) {
+            if (dataAfter != null && dataAfter.getTransport() instanceof Tls) {
+                LOG.debug("Adding device {}", dataAfter.getUniqueId());
+                final String tlsKeyId = ((Tls) dataAfter.getTransport()).getTlsClientParams().getKeyId();
+                allowedKeys.putIfAbsent(dataAfter.getUniqueId(), tlsKeyId);
+            }
+        }
+    }
+}
\ No newline at end of file
index 990db2d227399bae40b089a6d3397c779c75f93b..f0d9f0357d6d9ca7fe4d66f051581df60a67661b 100644 (file)
                interface="org.opendaylight.netconf.sal.connect.api.SchemaResourceManager"/>
     <reference id="baseSchemas"
                interface="org.opendaylight.netconf.sal.connect.netconf.schema.mapping.BaseNetconfSchemas"/>
+    <reference id="globalBossGroup"
+               interface="io.netty.channel.EventLoopGroup"
+               odl:type="global-boss-group"/>
+    <reference id="globalWorkerGroup"
+               interface="io.netty.channel.EventLoopGroup"
+               odl:type="global-worker-group"/>
 
     <bean id="callhomeProvider" class="org.opendaylight.netconf.callhome.mount.IetfZeroTouchCallHomeServerProvider"
           init-method="init"
         <argument ref="encryptionService"/>
         <argument ref="deviceActionFactory"/>
     </bean>
+
+    <!-- Configuration for NetConf Call-Home TLS -->
+    <bean id="netconfCallHomeTlsConfiguration" class="org.opendaylight.netconf.callhome.mount.tls.Configuration">
+        <property name="host" value="0.0.0.0" />
+        <property name="port" value="4335" />
+        <property name="timeout" value="10000" />
+        <property name="maxConnections" value="64" />
+    </bean>
+
+    <bean id="netconfCallHomeService" class="org.opendaylight.netconf.callhome.mount.tls.NetconfCallHomeTlsService"
+          init-method="init"
+          destroy-method="close">
+        <argument ref="netconfCallHomeTlsConfiguration" />
+        <argument ref="dataBroker" />
+        <argument ref="callhomeDispatcher" />
+        <argument ref="globalBossGroup"/>
+        <argument ref="globalWorkerGroup"/>
+    </bean>
+
 </blueprint>
index 5441bdcd268a455ae38290df53b2d2d9ed2ee116..64d567f5aaac8893edee78673a98f5cfa7335180 100644 (file)
@@ -13,16 +13,16 @@ import io.netty.channel.ChannelInboundHandlerAdapter;
 import io.netty.util.concurrent.Promise;
 import org.opendaylight.netconf.nettyutil.AbstractChannelInitializer;
 
-final class TlsClientChannelInitializer extends AbstractChannelInitializer<NetconfClientSession> {
+public final class TlsClientChannelInitializer extends AbstractChannelInitializer<NetconfClientSession> {
     public static final String CHANNEL_ACTIVE_SENTRY = "channelActiveSentry";
 
     private final SslHandlerFactory sslHandlerFactory;
     private final NetconfClientSessionNegotiatorFactory negotiatorFactory;
     private final NetconfClientSessionListener sessionListener;
 
-    TlsClientChannelInitializer(final SslHandlerFactory sslHandlerFactory,
-                                final NetconfClientSessionNegotiatorFactory negotiatorFactory,
-                                final NetconfClientSessionListener sessionListener) {
+    public TlsClientChannelInitializer(final SslHandlerFactory sslHandlerFactory,
+                                       final NetconfClientSessionNegotiatorFactory negotiatorFactory,
+                                       final NetconfClientSessionListener sessionListener) {
         this.sslHandlerFactory = sslHandlerFactory;
         this.negotiatorFactory = negotiatorFactory;
         this.sessionListener = sessionListener;