Fix formatting in callhome-provider
[netconf.git] / netconf / callhome-provider / src / main / java / org / opendaylight / netconf / callhome / mount / CallHomeAuthProviderImpl.java
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 package org.opendaylight.netconf.callhome.mount;
9
10 import com.google.common.base.Objects;
11 import com.google.common.net.InetAddresses;
12 import java.io.IOException;
13 import java.net.InetSocketAddress;
14 import java.net.SocketAddress;
15 import java.security.NoSuchAlgorithmException;
16 import java.security.NoSuchProviderException;
17 import java.security.PublicKey;
18 import java.security.spec.InvalidKeySpecException;
19 import java.util.Collection;
20 import java.util.concurrent.ConcurrentHashMap;
21 import java.util.concurrent.ConcurrentMap;
22 import javax.annotation.Nonnull;
23 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
24 import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
25 import org.opendaylight.controller.md.sal.binding.api.DataTreeChangeListener;
26 import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
27 import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
28 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
29 import org.opendaylight.netconf.callhome.protocol.AuthorizedKeysDecoder;
30 import org.opendaylight.netconf.callhome.protocol.CallHomeAuthorization;
31 import org.opendaylight.netconf.callhome.protocol.CallHomeAuthorization.Builder;
32 import org.opendaylight.netconf.callhome.protocol.CallHomeAuthorizationProvider;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.NetconfCallhomeServer;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.credentials.Credentials;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.AllowedDevices;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.Global;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.Device;
38 import org.opendaylight.yangtools.concepts.ListenerRegistration;
39 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 public class CallHomeAuthProviderImpl implements CallHomeAuthorizationProvider, AutoCloseable {
44
45     private static final Logger LOG = LoggerFactory.getLogger(CallHomeAuthProviderImpl.class);
46     private static final InstanceIdentifier<Global> GLOBAL_PATH =
47             InstanceIdentifier.create(NetconfCallhomeServer.class).child(Global.class);
48     private static final DataTreeIdentifier<Global> GLOBAL =
49             new DataTreeIdentifier<>(LogicalDatastoreType.CONFIGURATION, GLOBAL_PATH);
50
51     private static final InstanceIdentifier<Device> ALLOWED_DEVICES_PATH =
52             InstanceIdentifier.create(NetconfCallhomeServer.class).child(AllowedDevices.class).child(Device.class);
53     private static final DataTreeIdentifier<Device> ALLOWED_DEVICES =
54             new DataTreeIdentifier<>(LogicalDatastoreType.CONFIGURATION, ALLOWED_DEVICES_PATH);
55     private static final DataTreeIdentifier<Device> ALLOWED_OP_DEVICES =
56             new DataTreeIdentifier<>(LogicalDatastoreType.OPERATIONAL, ALLOWED_DEVICES_PATH);
57
58     private final GlobalConfig globalConfig = new GlobalConfig();
59     private final DeviceConfig deviceConfig = new DeviceConfig();
60     private final DeviceOp deviceOp = new DeviceOp();
61     private final ListenerRegistration<GlobalConfig> configReg;
62     private final ListenerRegistration<DeviceConfig> deviceReg;
63     private final ListenerRegistration<DeviceOp> deviceOpReg;
64
65     private final CallhomeStatusReporter statusReporter;
66
67     CallHomeAuthProviderImpl(DataBroker broker) {
68         configReg = broker.registerDataTreeChangeListener(GLOBAL, globalConfig);
69         deviceReg = broker.registerDataTreeChangeListener(ALLOWED_DEVICES, deviceConfig);
70         deviceOpReg = broker.registerDataTreeChangeListener(ALLOWED_OP_DEVICES, deviceOp);
71         statusReporter = new CallhomeStatusReporter(broker);
72     }
73
74     @Nonnull
75     @Override
76     public CallHomeAuthorization provideAuth(SocketAddress remoteAddress, PublicKey serverKey) {
77         Device deviceSpecific = deviceConfig.get(serverKey);
78         String sessionName;
79         Credentials deviceCred;
80
81         if (deviceSpecific != null) {
82             sessionName = deviceSpecific.getUniqueId();
83             deviceCred = deviceSpecific.getCredentials();
84         } else {
85             String syntheticId = fromRemoteAddress(remoteAddress);
86             if (globalConfig.allowedUnknownKeys()) {
87                 sessionName = syntheticId;
88                 deviceCred = null;
89                 statusReporter.asForceListedDevice(syntheticId, serverKey);
90             } else {
91                 Device opDevice = deviceOp.get(serverKey);
92                 if (opDevice == null)
93                     statusReporter.asUnlistedDevice(syntheticId, serverKey);
94                 else
95                     LOG.info("Repeating rejection of unlisted device with id of {}", opDevice.getUniqueId());
96                 return CallHomeAuthorization.rejected();
97             }
98         }
99
100         final Credentials credentials = deviceCred != null ? deviceCred : globalConfig.getCredentials();
101
102         if (credentials == null) {
103             LOG.info("No credentials found for {}, rejecting.", remoteAddress);
104             return CallHomeAuthorization.rejected();
105         }
106
107         Builder authBuilder = CallHomeAuthorization.serverAccepted(sessionName, credentials.getUsername());
108         for (String password : credentials.getPasswords()) {
109             authBuilder.addPassword(password);
110         }
111         return authBuilder.build();
112     }
113
114     @Override
115     public void close() throws Exception {
116         configReg.close();
117         deviceReg.close();
118         deviceOpReg.close();
119     }
120
121     private String fromRemoteAddress(SocketAddress remoteAddress) {
122         if (remoteAddress instanceof InetSocketAddress) {
123             InetSocketAddress socketAddress = (InetSocketAddress) remoteAddress;
124             return InetAddresses.toAddrString(socketAddress.getAddress()) + ":" + socketAddress.getPort();
125         }
126         return remoteAddress.toString();
127     }
128
129     private class DeviceConfig implements DataTreeChangeListener<Device> {
130
131         private final AuthorizedKeysDecoder keyDecoder = new AuthorizedKeysDecoder();
132
133         private ConcurrentMap<PublicKey, Device> byPublicKey = new ConcurrentHashMap<PublicKey, Device>();
134
135         @Override
136         public void onDataTreeChanged(Collection<DataTreeModification<Device>> mods) {
137             for (DataTreeModification<Device> dataTreeModification : mods) {
138                 DataObjectModification<Device> rootNode = dataTreeModification.getRootNode();
139                 process(rootNode);
140             }
141         }
142
143         private void process(DataObjectModification<Device> deviceMod) {
144             Device before = deviceMod.getDataBefore();
145             Device after = deviceMod.getDataAfter();
146
147             if (before == null) {
148                 putDevice(after);
149             } else if (after == null) {
150                 // Delete
151                 removeDevice(before);
152             } else {
153                 if (!Objects.equal(before.getSshHostKey(), after.getSshHostKey())) {
154                     // key changed // we should remove previous key.
155                     removeDevice(before);
156                 }
157                 putDevice(after);
158             }
159         }
160
161         private void putDevice(Device device) {
162             PublicKey key = publicKey(device);
163             if (key == null) {
164                 return;
165             }
166             byPublicKey.put(key, device);
167         }
168
169         private void removeDevice(Device device) {
170             PublicKey key = publicKey(device);
171             if (key == null) {
172                 return;
173             }
174             byPublicKey.remove(key);
175         }
176
177         private PublicKey publicKey(Device device) {
178             String hostKey = device.getSshHostKey();
179             try {
180                 return keyDecoder.decodePublicKey(hostKey);
181             } catch (InvalidKeySpecException | NoSuchAlgorithmException | NoSuchProviderException e) {
182                 LOG.error("Unable to decode SSH key for {}. Ignoring update for this device", device.getUniqueId(), e);
183                 return null;
184             }
185         }
186
187         private Device get(PublicKey key) {
188             return byPublicKey.get(key);
189         }
190     }
191
192     private class DeviceOp implements DataTreeChangeListener<Device> {
193
194         private ConcurrentMap<String, Device> byPublicKey = new ConcurrentHashMap<>();
195
196         @Override
197         public void onDataTreeChanged(Collection<DataTreeModification<Device>> mods) {
198             for (DataTreeModification<Device> dataTreeModification : mods) {
199                 DataObjectModification<Device> rootNode = dataTreeModification.getRootNode();
200                 process(rootNode);
201             }
202         }
203
204         private void process(DataObjectModification<Device> deviceMod) {
205             Device before = deviceMod.getDataBefore();
206             Device after = deviceMod.getDataAfter();
207
208             if (before == null) {
209                 putDevice(after);
210             } else if (after == null) {
211                 // Delete
212                 removeDevice(before);
213             } else {
214                 if (!Objects.equal(before.getSshHostKey(), after.getSshHostKey())) {
215                     // key changed // we should remove previous key.
216                     removeDevice(before);
217                 }
218                 putDevice(after);
219             }
220         }
221
222         private void putDevice(Device device) {
223             String key = device.getSshHostKey();
224             byPublicKey.put(key, device);
225         }
226
227         private void removeDevice(Device device) {
228             String key = device.getSshHostKey();
229             byPublicKey.remove(key);
230         }
231
232         Device get(PublicKey serverKey) {
233             String skey = "";
234
235             try {
236                 skey = AuthorizedKeysDecoder.encodePublicKey(serverKey);
237                 return byPublicKey.get(skey);
238             } catch (IOException | IllegalArgumentException e) {
239                 LOG.error("Unable to encode server key: {}", skey, e);
240                 return null;
241             }
242         }
243     }
244
245     private class GlobalConfig implements DataTreeChangeListener<Global> {
246
247         private volatile Global current = null;
248
249         @Override
250         public void onDataTreeChanged(Collection<DataTreeModification<Global>> mods) {
251             for (DataTreeModification<Global> dataTreeModification : mods) {
252                 current = dataTreeModification.getRootNode().getDataAfter();
253             }
254         }
255
256         boolean allowedUnknownKeys() {
257             if (current == null) {
258                 return false;
259             }
260             // Deal with null values.
261             return Boolean.TRUE.equals(current.isAcceptAllSshKeys());
262         }
263
264         Credentials getCredentials() {
265             return current != null ? current.getCredentials() : null;
266         }
267     }
268 }