Add TLS data to call-home yang-models 84/91284/11
authorOleksii Mozghovyi <oleksii.mozghovyi@pantheon.tech>
Tue, 30 Jun 2020 09:16:01 +0000 (12:16 +0300)
committerRobert Varga <robert.varga@pantheon.tech>
Fri, 16 Oct 2020 14:39:12 +0000 (16:39 +0200)
The configuration model for callhome-server does not allow TLS to
be configured -- i.e. we are blindly assuming the devices will be
connecting via SSH.

Rectify this by deprecating the old configuration leaf and adding
the option to configure authentication on a per-transport basis.

JIRA: NETCONF-5
Change-Id: I56f14ad9472b5e87836be326a02c6663c229fb32
Signed-off-by: Oleksii Mozghovyi <oleksii.mozghovyi@pantheon.tech>
Signed-off-by: Vladyslav Marchenko <vladyslav.marchenko@pantheon.tech>
docs/user-guide.rst
netconf/callhome-model/src/main/yang/odl-netconf-callhome-server.yang
netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeAuthProviderImpl.java
netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallhomeStatusReporter.java
netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/IetfZeroTouchCallHomeServerProvider.java

index 0c34d8a06386e077a2115e1b65662cea3176af6d..9502f3bcf083c3adf6f5ce697fc4264a72fe3d5d 100644 (file)
@@ -801,12 +801,6 @@ particular mount point.
 NETCONF Call Home
 -----------------
 
-.. important::
-
-    The call home feature is experimental and will change in a future
-    release. In particular, the Yang models will change to those specified
-    in the `RFC 8071 <https://tools.ietf.org/html/rfc8071>`__
-
 Call Home Installation
 ~~~~~~~~~~~~~~~~~~~~~~
 
@@ -821,8 +815,10 @@ configuring Call Home & testing its functionality.
 
 .. note::
 
-    In order to test Call Home functionality we recommend Netopeer.
-    See `Netopeer Call Home <https://github.com/CESNET/netopeer/wiki/CallHome>`__ to learn how to enable call-home on Netopeer.
+    In order to test Call Home functionality we recommend Netopeer or
+    Netopeer2. See `Netopeer Call Home <https://github.com/CESNET/netopeer/wiki/CallHome>`__
+    or `Netopeer2 <https://github.com/CESNET/netopeer2>`__ to learn how to
+    enable call-home on Netopeer.
 
 Northbound Call-Home API
 ~~~~~~~~~~~~~~~~~~~~~~~~
@@ -833,12 +829,16 @@ following describes this configuration.
 Global Configuration
 ^^^^^^^^^^^^^^^^^^^^
 
+.. important::
+  The global configuration is not a part of the `RFC 8071
+  <https://tools.ietf.org/html/rfc8071>`__ and, therefore, subject to change.
+
 Configuring global credentials
 ''''''''''''''''''''''''''''''
 
-ODL Call-Home server allows user to configure global credentials, which
-will be used for devices which does not have device-specific credentials
-configured.
+ODL Call-Home server allows user to configure global credentials, which will be
+used for connected over SSH transport protocol devices which does not have
+device-specific credentials configured.
 
 This is done by creating
 ``/odl-netconf-callhome-server:netconf-callhome-server/global/credentials``
@@ -846,7 +846,7 @@ with username and passwords specified.
 
 *Configuring global username & passwords to try*
 
-.. code-block:: none
+.. code-block::
 
     PUT
     /restconf/config/odl-netconf-callhome-server:netconf-callhome-server/global/credentials HTTP/1.1
@@ -883,7 +883,7 @@ This is a debug feature and should not be used in production. Besides being an o
 security issue, this also causes the Call-Home Server to drastically increase its output
 to the log.
 
-.. code-block:: none
+.. code-block::
 
     POST
     /restconf/config/odl-netconf-callhome-server:netconf-callhome-server/global HTTP/1.1
@@ -901,8 +901,12 @@ to the log.
 Device-Specific Configuration
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-Allowing Device & Configuring Name
-''''''''''''''''''''''''''''''''''
+Netconf Call Home server supports both of the secure transports used
+by the Network Configuration Protocol (NETCONF) - Secure Shell (SSH),
+and Transport Layer Security (TLS).
+
+Configure device to connect over SSH protocol
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Netconf Call Home Server uses device provided SSH server key (host key)
 to identify device. The pairing of name and server key is configured in
@@ -915,9 +919,76 @@ not found, the connection between the Call Home server and the device is dropped
 immediately. In either case, the device that connects to the Call home server
 leaves a record of its presence in the operational store.
 
+Configuring Device with Device-specific Credentials
+'''''''''''''''''''''''''''''''''''''''''''''''''''
+
+Adding specific device to the allowed list is done by creating
+``/odl-netconf-callhome-server:netconf-callhome-server/allowed-devices/device/{device}``
+with device-id and connection parameters inside the ssh-client-params container.
+
+*Configuring Device with Credentials*
+
+.. code-block::
+
+    PUT
+    /restconf/config/odl-netconf-callhome-server:netconf-callhome-server/allowed-devices/device/example HTTP/1.1
+    Content-Type: application/json
+    Accept: application/json
+
+.. code-block:: json
+
+    {
+      "device": {
+        "unique-id": "example",
+        "ssh-client-params": {
+          "credentials": {
+            "username": "example",
+            "passwords": [ "password" ]
+          },
+          "ssh-host-key": "AAAAB3NzaC1yc2EAAAADAQABAAABAQDHoH1jMjltOJnCt999uaSfc48ySutaD3ISJ9fSECe1Spdq9o9mxj0kBTTTq+2V8hPspuW75DNgN+V/rgJeoUewWwCAasRx9X4eTcRrJrwOQKzb5Fk+UKgQmenZ5uhLAefi2qXX/agFCtZi99vw+jHXZStfHm9TZCAf2zi+HIBzoVksSNJD0VvPo66EAvLn5qKWQD4AdpQQbKqXRf5/W8diPySbYdvOP2/7HFhDukW8yV/7ZtcywFUIu3gdXsrzwMnTqnATSLPPuckoi0V2jd8dQvEcu1DY+rRqmqu0tEkFBurlRZDf1yhNzq5xWY3OXcjgDGN+RxwuWQK3cRimcosH"
+        }
+      }
+    }
+
+Configuring Device with Global Credentials
+'''''''''''''''''''''''''''''''''''''''''''''''''''
+
+It is possible to omit 'username' and 'password' for ssh-client-params,
+in such case values from global credentials will be used.
+
 *Example of configuring device*
 
-.. code-block:: none
+.. code-block::
+
+    PUT
+    /restconf/config/odl-netconf-callhome-server:netconf-callhome-server/allowed-devices/device/example HTTP/1.1
+    Content-Type: application/json
+    Accept: application/json
+
+.. code-block:: json
+
+    {
+      "device": {
+        "unique-id": "example",
+        "ssh-client-params": {
+          "host-key": "AAAAB3NzaC1yc2EAAAADAQABAAABAQDHoH1jMjltOJnCt999uaSfc48ySutaD3ISJ9fSECe1Spdq9o9mxj0kBTTTq+2V8hPspuW75DNgN+V/rgJeoUewWwCAasRx9X4eTcRrJrwOQKzb5Fk+UKgQmenZ5uhLAefi2qXX/agFCtZi99vw+jHXZStfHm9TZCAf2zi+HIBzoVksSNJD0VvPo66EAvLn5qKWQD4AdpQQbKqXRf5/W8diPySbYdvOP2/7HFhDukW8yV/7ZtcywFUIu3gdXsrzwMnTqnATSLPPuckoi0V2jd8dQvEcu1DY+rRqmqu0tEkFBurlRZDf1yhNzq5xWY3OXcjgDGN+RxwuWQK3cRimcosH"
+        }
+      }
+    }
+
+Deprecated configuration models for devices accessed with SSH protocol
+''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
+
+With `RFC 8071 <https://tools.ietf.org/html/rfc8071>`__ alignment and adding
+support for TLS transport following configuration models has been marked
+deprecated.
+
+Configuring Device with Global Credentials
+'''''''''''''''''''''''''''''''''''''''''''''''''''
+
+*Example of configuring device*
+
+.. code-block::
 
     PUT
     /restconf/config/odl-netconf-callhome-server:netconf-callhome-server/allowed-devices/device/example HTTP/1.1
@@ -942,7 +1013,7 @@ device-specific configuration. Format is same as in global credentials.
 
 *Configuring Device with Credentials*
 
-.. code-block:: none
+.. code-block::
 
     PUT
     /restconf/config/odl-netconf-callhome-server:netconf-callhome-server/allowed-devices/device/example HTTP/1.1
@@ -962,6 +1033,117 @@ device-specific configuration. Format is same as in global credentials.
       }
     }
 
+Configure device to connect over TLS protocol
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Netconf Call Home Server allows devices to use TLS transport protocol to
+establish a connection towards the NETCONF device. This communication
+requires proper setup to make two-way TLS authentication possible for client
+and server.
+
+The initial step is to configure certificates and keys for two-way TLS by
+storing them within the netconf-keystore.
+
+*Adding a client private key credential to the netconf-keystore*
+
+.. code-block::
+
+    POST
+    /rests/operations/netconf-keystore:add-keystore-entry HTTP/1.1
+    Content-Type: application/json
+    Accept: application/json
+
+.. code-block:: json
+
+  {
+    "input": {
+      "key-credential": [
+        {
+          "key-id": "example-client-key-id",
+          "private-key": "base64encoded-private-key",
+          "passphrase": "passphrase"
+        }
+      ]
+    }
+  }
+
+*Associate a private key with a client and CA certificates chain*
+
+.. code-block::
+
+    POST
+    /rests/operations/netconf-keystore:add-private-key HTTP/1.1
+    Content-Type: application/json
+    Accept: application/json
+
+.. code-block:: json
+
+  {
+    "input": {
+      "private-key": [
+        {
+          "name": "example-client-key-id",
+          "data": "key-data",
+          "certificate-chain": [
+            "certificate-data"
+          ]
+        }
+      ]
+    }
+  }
+
+*Add a list of trusted CA and server certificates*
+
+.. code-block::
+
+    POST
+    /rests/operations/netconf-keystore:add-trusted-certificate HTTP/1.1
+    Content-Type: application/json
+    Accept: application/json
+
+.. code-block:: json
+
+  {
+    "input": {
+      "trusted-certificate": [
+        {
+          "name": "example-ca-certificate",
+          "certificate": "ca-certificate-data"
+        },
+        {
+          "name": "example-server-certificate",
+          "certificate": "server-certificate-data"
+        }
+      ]
+    }
+  }
+
+In a second step, it is required to create an allowed device associated with
+a server certificate and client key. The server certificate will be used to
+identify and pin the NETCONF device during SSL handshake and should be unique
+among the allowed devices.
+
+*Add device configuration for TLS protocol to allowed devices list*
+
+.. code-block::
+
+    PUT
+    /restconf/config/odl-netconf-callhome-server:netconf-callhome-server/allowed-devices/device/example-device HTTP/1.1
+    Content-Type: application/json
+    Accept: application/json
+
+.. code-block:: json
+
+  {
+    "device": {
+      "unique-id": "example-device",
+      "tls-client-params": {
+        "key-id": "example-client-key-id",
+        "certificate-id": "example-server-certificate"
+      }
+    }
+  }
+
 Operational Status
 ^^^^^^^^^^^^^^^^^^
 
index 4a4d168316538e3c40633fa046df7335acb52792..2ca3e96a462faa7ed3a45e5b8d770d620b38a5a3 100644 (file)
@@ -63,11 +63,43 @@ module odl-netconf-callhome-server {
           type string;
         }
         leaf ssh-host-key {
-          description "BASE-64 encoded public key which device will use during connection.";
+          description "BASE-64 encoded public key which device will use during connection.
+                       Deprecated, a 'host-key' from the  'ssh-client-params' containers should be used instead.";
+          status deprecated;
           type string;
         }
         unique ssh-host-key;
         uses credentials;
+
+        choice transport {
+          description "Provides connectivity details for one of the supported transport protocols";
+          case ssh {
+            container ssh-client-params {
+              leaf host-key {
+                mandatory true;
+                description "BASE-64 encoded public key which device will use during connection.";
+                type string;
+              }
+              uses credentials;
+            }
+          }
+          case tls {
+            container tls-client-params {
+              leaf certificate-id {
+                mandatory true;
+                description "Certificate identifier which will be used during two-way TLS authentication.";
+                type string;
+              }
+              leaf key-id {
+                mandatory true;
+                description "Key identifier inside the NetConf keystore which will be used during two-way TLS authentication.";
+                type string;
+              }
+            }
+          }
+        }
+        unique certificate-id;
+        unique host-key;
       }
     }
   }
index 17b5c6be57a6d13a757e3625dc828019873600c5..da5804af430a3560408df665a18ad14d22993774 100644 (file)
@@ -33,6 +33,8 @@ 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.Global;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.Global.MountPointNamingStrategy;
 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.Ssh;
+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.yangtools.concepts.ListenerRegistration;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.slf4j.Logger;
@@ -77,7 +79,12 @@ public class CallHomeAuthProviderImpl implements CallHomeAuthorizationProvider,
 
         if (deviceSpecific != null) {
             sessionName = deviceSpecific.getUniqueId();
-            deviceCred = deviceSpecific.getCredentials();
+            if (deviceSpecific.getTransport() instanceof Ssh) {
+                final SshClientParams clientParams = ((Ssh) deviceSpecific.getTransport()).getSshClientParams();
+                deviceCred = clientParams.getCredentials();
+            } else {
+                deviceCred = deviceSpecific.getCredentials();
+            }
         } else {
             String syntheticId = fromRemoteAddress(remoteAddress);
             if (globalConfig.allowedUnknownKeys()) {
@@ -158,7 +165,7 @@ public class CallHomeAuthProviderImpl implements CallHomeAuthorizationProvider,
 
         private void deleteDevice(final Device dataBefore) {
             if (dataBefore != null) {
-                final String publicKey = dataBefore.getSshHostKey();
+                final String publicKey = getHostPublicKey(dataBefore);
                 if (publicKey != null) {
                     LOG.debug("Removing device {}", dataBefore.getUniqueId());
                     removeDevice(publicKey, dataBefore);
@@ -169,7 +176,7 @@ public class CallHomeAuthProviderImpl implements CallHomeAuthorizationProvider,
         }
 
         private void writeDevice(final Device dataAfter) {
-            final String publicKey = dataAfter.getSshHostKey();
+            final String publicKey = getHostPublicKey(dataAfter);
             if (publicKey != null) {
                 LOG.debug("Adding device {}", dataAfter.getUniqueId());
                 addDevice(publicKey, dataAfter);
@@ -178,6 +185,14 @@ public class CallHomeAuthProviderImpl implements CallHomeAuthorizationProvider,
             }
         }
 
+        private String getHostPublicKey(final Device device) {
+            if (device.getTransport() instanceof Ssh) {
+                return ((Ssh) device.getTransport()).getSshClientParams().getHostKey();
+            } else {
+                return device.getSshHostKey();
+            }
+        }
+
         abstract void addDevice(String publicKey, Device device);
 
         abstract void removeDevice(String publicKey, Device device);
index 48467cde04a39e71937f37f078d6c4b5ddf95f9a..5fb653c6c890e317a81ce6cba0845f4ce9ecb8b9 100644 (file)
@@ -38,6 +38,11 @@ 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;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.DeviceBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.DeviceKey;
+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.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.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
 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.TopologyId;
@@ -68,7 +73,7 @@ class CallhomeStatusReporter implements DataTreeChangeListener<Node>, StatusReco
 
     @Override
     public void onDataTreeChanged(final Collection<DataTreeModification<Node>> changes) {
-        for (DataTreeModification<Node> change: changes) {
+        for (DataTreeModification<Node> change : changes) {
             final DataObjectModification<Node> rootNode = change.getRootNode();
             final InstanceIdentifier<Node> identifier = change.getRootPath().getRootIdentifier();
             switch (rootNode.getModificationType()) {
@@ -187,17 +192,20 @@ class CallhomeStatusReporter implements DataTreeChangeListener<Node>, StatusReco
     }
 
     private static Device newDevice(final String id, final PublicKey serverKey, final Device1.DeviceStatus status) {
+        // used only for netconf devices that are connected via SSH transport and global credentials
         String sshEncodedKey = serverKey.toString();
         try {
             sshEncodedKey = AuthorizedKeysDecoder.encodePublicKey(serverKey);
         } catch (IOException e) {
             LOG.warn("Unable to encode public key to ssh format.", e);
         }
+        final SshClientParams sshParams = new SshClientParamsBuilder().setHostKey(sshEncodedKey).build();
+        final Transport transport = new SshBuilder().setSshClientParams(sshParams).build();
         return new DeviceBuilder()
                 .setUniqueId(id)
                 .withKey(new DeviceKey(id))
-                .setSshHostKey(sshEncodedKey)
-                .addAugmentation(new Device1Builder().setDeviceStatus(Device1.DeviceStatus.FAILEDNOTALLOWED).build())
+                .setTransport(transport)
+                .addAugmentation(new Device1Builder().setDeviceStatus(status).build())
                 .build();
     }
 
@@ -222,8 +230,8 @@ class CallhomeStatusReporter implements DataTreeChangeListener<Node>, StatusReco
 
     private static InstanceIdentifier<Device> buildDeviceInstanceIdentifier(final NodeId nodeId) {
         return InstanceIdentifier.create(NetconfCallhomeServer.class)
-                .child(AllowedDevices.class)
-                .child(Device.class, new DeviceKey(nodeId.getValue()));
+            .child(AllowedDevices.class)
+            .child(Device.class, new DeviceKey(nodeId.getValue()));
     }
 
     private static Device withConnectedStatus(final Device opDev) {
@@ -243,11 +251,15 @@ class CallhomeStatusReporter implements DataTreeChangeListener<Node>, StatusReco
     }
 
     private static Device deviceWithStatus(final Device opDev, final DeviceStatus status) {
-        return new DeviceBuilder()
-                .setUniqueId(opDev.getUniqueId())
-                .setSshHostKey(opDev.getSshHostKey())
-                .addAugmentation(new Device1Builder().setDeviceStatus(status).build())
-                .build();
+        final DeviceBuilder deviceBuilder = new DeviceBuilder()
+            .setUniqueId(opDev.getUniqueId())
+            .addAugmentation(new Device1Builder().setDeviceStatus(status).build());
+        if (opDev.getTransport() != null) {
+            deviceBuilder.setTransport(opDev.getTransport());
+        } else {
+            deviceBuilder.setSshHostKey(opDev.getSshHostKey());
+        }
+        return deviceBuilder.build();
     }
 
     private void setDeviceStatus(final Device device) {
@@ -293,8 +305,13 @@ class CallhomeStatusReporter implements DataTreeChangeListener<Node>, StatusReco
     public void reportFailedAuth(final PublicKey sshKey) {
         AuthorizedKeysDecoder decoder = new AuthorizedKeysDecoder();
 
-        for (Device device : getDevicesAsList()) {
-            String keyString = device.getSshHostKey();
+        for (final Device device : getDevicesAsList()) {
+            final String keyString;
+            if (device.getTransport() instanceof Ssh) {
+                keyString = ((Ssh) device.getTransport()).getSshClientParams().getHostKey();
+            } else {
+                keyString = device.getSshHostKey();
+            }
             if (keyString == null) {
                 LOG.info("Whitelist device {} does not have a host key, skipping it", device.getUniqueId());
                 continue;
index eb20f0c483d99900067e5659c81015ded4274d3c..e2a99a34c34e683c7bcf607814ca67c34d6e79bf 100644 (file)
@@ -40,6 +40,11 @@ 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;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.DeviceBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.DeviceKey;
+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.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;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.slf4j.Logger;
@@ -220,9 +225,8 @@ public class IetfZeroTouchCallHomeServerProvider implements AutoCloseable, DataT
             devStatus = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.DISCONNECTED).build();
         }
 
-        tx.merge(LogicalDatastoreType.OPERATIONAL, deviceIID, new DeviceBuilder()
-            .addAugmentation(devStatus).setSshHostKey(cfgDevice.getSshHostKey())
-            .setUniqueId(cfgDevice.getUniqueId()).build());
+        final Device opDevice = createOperationalDevice(cfgDevice, devStatus);
+        tx.merge(LogicalDatastoreType.OPERATIONAL, deviceIID, opDevice);
         tx.commit().addCallback(new FutureCallback<CommitInfo>() {
             @Override
             public void onSuccess(final CommitInfo result) {
@@ -235,4 +239,19 @@ public class IetfZeroTouchCallHomeServerProvider implements AutoCloseable, DataT
             }
         }, MoreExecutors.directExecutor());
     }
+
+    private Device createOperationalDevice(final Device cfgDevice, final Device1 devStatus) {
+        final DeviceBuilder deviceBuilder = new DeviceBuilder()
+            .addAugmentation(devStatus)
+            .setUniqueId(cfgDevice.getUniqueId());
+        if (cfgDevice.getTransport() instanceof Ssh) {
+            final String hostKey = ((Ssh) cfgDevice.getTransport()).getSshClientParams().getHostKey();
+            final SshClientParams params = new SshClientParamsBuilder().setHostKey(hostKey).build();
+            final Transport sshTransport = new SshBuilder().setSshClientParams(params).build();
+            deviceBuilder.setTransport(sshTransport);
+        } else if (cfgDevice.getSshHostKey() != null) {
+            deviceBuilder.setSshHostKey(cfgDevice.getSshHostKey());
+        }
+        return deviceBuilder.build();
+    }
 }