a9da3f3d7cc5025b7449fd3fa33476d97f36e5c3
[netconf.git] /
1 /*
2  * Copyright (c) 2016 Brocade Communication Systems and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.netconf.callhome.mount;
10
11 import com.google.common.base.Optional;
12 import com.google.common.util.concurrent.CheckedFuture;
13 import java.io.IOException;
14 import java.security.NoSuchAlgorithmException;
15 import java.security.NoSuchProviderException;
16 import java.security.PublicKey;
17 import java.security.spec.InvalidKeySpecException;
18 import java.util.ArrayList;
19 import java.util.Collection;
20 import java.util.List;
21 import javax.annotation.Nonnull;
22 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
23 import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
24 import org.opendaylight.controller.md.sal.binding.api.DataTreeChangeListener;
25 import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
26 import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
27 import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
28 import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
29 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
30 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
31 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
32 import org.opendaylight.netconf.callhome.protocol.AuthorizedKeysDecoder;
33 import org.opendaylight.netconf.callhome.protocol.StatusRecorder;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.callhome.device.status.rev170112.Device1;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.callhome.device.status.rev170112.Device1Builder;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeConnectionStatus;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.network.topology.topology.topology.types.TopologyNetconf;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.NetconfCallhomeServer;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.AllowedDevices;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.Device;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.DeviceBuilder;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.DeviceKey;
44 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
45 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
46 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId;
47 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
48 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
49 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
50 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeKey;
51 import org.opendaylight.yangtools.concepts.ListenerRegistration;
52 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 class CallhomeStatusReporter implements DataTreeChangeListener<Node>, StatusRecorder, AutoCloseable {
57     private static final InstanceIdentifier<Topology> NETCONF_TOPO_IID =
58             InstanceIdentifier.create(NetworkTopology.class).child(Topology.class,
59                     new TopologyKey(new TopologyId(TopologyNetconf.QNAME.getLocalName())));
60
61     private static final Logger LOG = LoggerFactory.getLogger(CallhomeStatusReporter.class);
62
63     private final DataBroker dataBroker;
64     private final ListenerRegistration<CallhomeStatusReporter> reg;
65
66     CallhomeStatusReporter(DataBroker broker) {
67         this.dataBroker = broker;
68         this.reg = dataBroker.registerDataTreeChangeListener(new DataTreeIdentifier<>(LogicalDatastoreType.OPERATIONAL,
69             NETCONF_TOPO_IID.child(Node.class)), this);
70     }
71
72     @Override
73     public void onDataTreeChanged(Collection<DataTreeModification<Node>> changes) {
74         for (DataTreeModification<Node> change: changes) {
75             final DataObjectModification<Node> rootNode = change.getRootNode();
76             final InstanceIdentifier<Node> identifier = change.getRootPath().getRootIdentifier();
77             switch (rootNode.getModificationType()) {
78                 case WRITE:
79                 case SUBTREE_MODIFIED:
80                     if (isNetconfNode(rootNode.getDataAfter())) {
81                         NodeId nodeId = getNodeId(identifier);
82                         if (nodeId != null) {
83                             NetconfNode nnode = rootNode.getDataAfter().getAugmentation(NetconfNode.class);
84                             handledNetconfNode(nodeId, nnode);
85                         }
86                     }
87                     break;
88                 case DELETE:
89                     if (isNetconfNode(rootNode.getDataBefore())) {
90                         final NodeId nodeId = getNodeId(identifier);
91                         if (nodeId != null) {
92                             handleDisconnectedNetconfNode(nodeId);
93                         }
94                     }
95                     break;
96                 default:
97                     break;
98             }
99         }
100     }
101
102     private boolean isNetconfNode(Node node) {
103         return node.getAugmentation(NetconfNode.class) != null;
104     }
105
106     private NodeId getNodeId(final InstanceIdentifier<?> path) {
107         NodeKey key = path.firstKeyOf(Node.class);
108         return key != null ? key.getNodeId() : null;
109     }
110
111     private void handledNetconfNode(NodeId nodeId, NetconfNode nnode) {
112         NetconfNodeConnectionStatus.ConnectionStatus csts = nnode.getConnectionStatus();
113
114         switch (csts) {
115             case Connected: {
116                 handleConnectedNetconfNode(nodeId);
117                 break;
118             }
119             default:
120             case UnableToConnect: {
121                 handleUnableToConnectNetconfNode(nodeId);
122                 break;
123             }
124         }
125     }
126
127     private void handleConnectedNetconfNode(NodeId nodeId) {
128         // Fully connected, all services for remote device are
129         // available from the MountPointService.
130         LOG.debug("NETCONF Node: {} is fully connected", nodeId.getValue());
131
132         Device opDev = readAndGetDevice(nodeId);
133         if (opDev == null) {
134             LOG.warn("No corresponding callhome device found - exiting.");
135         } else {
136             Device modifiedDevice = withConnectedStatus(opDev);
137             if (modifiedDevice == null) {
138                 return;
139             }
140             LOG.info("Setting successful status for callhome device id:{}.", nodeId);
141             writeDevice(nodeId, modifiedDevice);
142         }
143     }
144
145     private void handleDisconnectedNetconfNode(NodeId nodeId) {
146         LOG.debug("NETCONF Node: {} disconnected", nodeId.getValue());
147
148         Device opDev = readAndGetDevice(nodeId);
149         if (opDev == null) {
150             LOG.warn("No corresponding callhome device found - exiting.");
151         } else {
152             Device modifiedDevice = withDisconnectedStatus(opDev);
153             if (modifiedDevice == null) {
154                 return;
155             }
156             LOG.info("Setting disconnected status for callhome device id:{}.", nodeId);
157             writeDevice(nodeId, modifiedDevice);
158         }
159     }
160
161     private void handleUnableToConnectNetconfNode(NodeId nodeId) {
162         // The maximum configured number of reconnect attempts
163         // have been reached. No more reconnects will be
164         // attempted by the Netconf Connector.
165         LOG.debug("NETCONF Node: {} connection failed", nodeId.getValue());
166
167         Device opDev = readAndGetDevice(nodeId);
168         if (opDev == null) {
169             LOG.warn("No corresponding callhome device found - exiting.");
170         } else {
171             Device modifiedDevice = withFailedStatus(opDev);
172             if (modifiedDevice == null) {
173                 return;
174             }
175             LOG.info("Setting failed status for callhome device id:{}.", nodeId);
176             writeDevice(nodeId, modifiedDevice);
177         }
178     }
179
180     void asForceListedDevice(String id, PublicKey serverKey) {
181         NodeId nid = new NodeId(id);
182         Device device = newDevice(id, serverKey, Device1.DeviceStatus.DISCONNECTED);
183         writeDevice(nid, device);
184     }
185
186     void asUnlistedDevice(String id, PublicKey serverKey) {
187         NodeId nid = new NodeId(id);
188         Device device = newDevice(id, serverKey, Device1.DeviceStatus.FAILEDNOTALLOWED);
189         writeDevice(nid, device);
190     }
191
192     private Device newDevice(String id, PublicKey serverKey, Device1.DeviceStatus status) {
193         String sshEncodedKey = serverKey.toString();
194         try {
195             sshEncodedKey = AuthorizedKeysDecoder.encodePublicKey(serverKey);
196         } catch (IOException e) {
197             LOG.warn("Unable to encode public key to ssh format.", e);
198         }
199         Device1 d1 = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.FAILEDNOTALLOWED).build();
200         DeviceBuilder builder = new DeviceBuilder()
201                 .setUniqueId(id)
202                 .setKey(new DeviceKey(id))
203                 .setSshHostKey(sshEncodedKey)
204                 .addAugmentation(Device1.class, d1);
205
206         return builder.build();
207     }
208
209     private Device readAndGetDevice(NodeId nodeId) {
210         return readDevice(nodeId).orNull();
211     }
212
213     @Nonnull
214     private Optional<Device> readDevice(NodeId nodeId) {
215         ReadOnlyTransaction opTx = dataBroker.newReadOnlyTransaction();
216
217         InstanceIdentifier<Device> deviceIID = buildDeviceInstanceIdentifier(nodeId);
218         CheckedFuture<Optional<Device>, ReadFailedException> devFuture =
219                 opTx.read(LogicalDatastoreType.OPERATIONAL, deviceIID);
220
221         try {
222             return devFuture.checkedGet();
223         } catch (ReadFailedException e) {
224             return Optional.absent();
225         }
226     }
227
228     private void writeDevice(NodeId nodeId, Device modifiedDevice) {
229         ReadWriteTransaction opTx = dataBroker.newReadWriteTransaction();
230         opTx.merge(LogicalDatastoreType.OPERATIONAL, buildDeviceInstanceIdentifier(nodeId), modifiedDevice);
231         opTx.submit();
232     }
233
234     private InstanceIdentifier<Device> buildDeviceInstanceIdentifier(NodeId nodeId) {
235         return InstanceIdentifier.create(NetconfCallhomeServer.class)
236                 .child(AllowedDevices.class)
237                 .child(Device.class, new DeviceKey(nodeId.getValue()));
238     }
239
240     private Device withConnectedStatus(Device opDev) {
241         Device1 status = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.CONNECTED).build();
242         return new DeviceBuilder().addAugmentation(Device1.class, status).setUniqueId(opDev.getUniqueId())
243                 .setSshHostKey(opDev.getSshHostKey()).build();
244     }
245
246     private Device withFailedStatus(Device opDev) {
247         Device1 status = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.FAILED).build();
248         return new DeviceBuilder().addAugmentation(Device1.class, status).setUniqueId(opDev.getUniqueId())
249                 .setSshHostKey(opDev.getSshHostKey()).build();
250     }
251
252     private Device withDisconnectedStatus(Device opDev) {
253         Device1 status = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.DISCONNECTED).build();
254         return new DeviceBuilder().addAugmentation(Device1.class, status).setUniqueId(opDev.getUniqueId())
255                 .setSshHostKey(opDev.getSshHostKey()).build();
256     }
257
258     private Device withFailedAuthStatus(Device opDev) {
259         Device1 status = new Device1Builder().setDeviceStatus(Device1.DeviceStatus.FAILEDAUTHFAILURE).build();
260         return new DeviceBuilder().addAugmentation(Device1.class, status).setUniqueId(opDev.getUniqueId())
261                 .setSshHostKey(opDev.getSshHostKey()).build();
262     }
263
264     private void setDeviceStatus(Device device) {
265         WriteTransaction tx = dataBroker.newWriteOnlyTransaction();
266         InstanceIdentifier<Device> deviceIId =
267                 InstanceIdentifier.create(NetconfCallhomeServer.class)
268                         .child(AllowedDevices.class)
269                         .child(Device.class, device.getKey());
270
271         tx.merge(LogicalDatastoreType.OPERATIONAL, deviceIId, device);
272         tx.submit();
273     }
274
275     private AllowedDevices getDevices() {
276         ReadOnlyTransaction rxTransaction = dataBroker.newReadOnlyTransaction();
277         CheckedFuture<Optional<AllowedDevices>, ReadFailedException> devicesFuture =
278                 rxTransaction.read(LogicalDatastoreType.OPERATIONAL, IetfZeroTouchCallHomeServerProvider.ALL_DEVICES);
279
280         try {
281             Optional<AllowedDevices> opt = devicesFuture.checkedGet();
282             if (opt.isPresent()) {
283                 AllowedDevices devices = opt.get();
284                 return devices;
285             }
286         } catch (ReadFailedException e) {
287             LOG.error("Error trying to read the whitelist devices: {}", e);
288         }
289
290         return null;
291     }
292
293     private List<Device> getDevicesAsList() {
294         AllowedDevices devices = getDevices();
295         return devices == null ? new ArrayList<>() : devices.getDevice();
296     }
297
298     @Override
299     public void reportFailedAuth(PublicKey sshKey) {
300         AuthorizedKeysDecoder decoder = new AuthorizedKeysDecoder();
301
302         for (Device device : getDevicesAsList()) {
303             String keyString = device.getSshHostKey();
304
305             try {
306                 PublicKey pubKey = decoder.decodePublicKey(keyString);
307                 if (sshKey.getAlgorithm().equals(pubKey.getAlgorithm()) && sshKey.equals(pubKey)) {
308                     Device failedDevice = withFailedAuthStatus(device);
309                     if (failedDevice == null) {
310                         return;
311                     }
312                     LOG.info("Setting auth failed status for callhome device id:{}.", failedDevice.getUniqueId());
313                     setDeviceStatus(failedDevice);
314                     return;
315                 }
316             } catch (InvalidKeySpecException | NoSuchAlgorithmException | NoSuchProviderException e) {
317                 LOG.error("Failed decoding a device key with host key: {} {}", keyString, e);
318                 return;
319             }
320         }
321
322         LOG.error("No match found for the failed auth device (should have been filtered by whitelist). Key: {}",
323                 sshKey);
324     }
325
326     @Override
327     public void close() throws Exception {
328         reg.close();
329     }
330 }