Bug 8153: enforce check-style rules for netconf
[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                 }
97                 return CallHomeAuthorization.rejected();
98             }
99         }
100
101         final Credentials credentials = deviceCred != null ? deviceCred : globalConfig.getCredentials();
102
103         if (credentials == null) {
104             LOG.info("No credentials found for {}, rejecting.", remoteAddress);
105             return CallHomeAuthorization.rejected();
106         }
107
108         Builder authBuilder = CallHomeAuthorization.serverAccepted(sessionName, credentials.getUsername());
109         for (String password : credentials.getPasswords()) {
110             authBuilder.addPassword(password);
111         }
112         return authBuilder.build();
113     }
114
115     @Override
116     public void close() throws Exception {
117         configReg.close();
118         deviceReg.close();
119         deviceOpReg.close();
120     }
121
122     private String fromRemoteAddress(SocketAddress remoteAddress) {
123         if (remoteAddress instanceof InetSocketAddress) {
124             InetSocketAddress socketAddress = (InetSocketAddress) remoteAddress;
125             return InetAddresses.toAddrString(socketAddress.getAddress()) + ":" + socketAddress.getPort();
126         }
127         return remoteAddress.toString();
128     }
129
130     private class DeviceConfig implements DataTreeChangeListener<Device> {
131
132         private final AuthorizedKeysDecoder keyDecoder = new AuthorizedKeysDecoder();
133
134         private ConcurrentMap<PublicKey, Device> byPublicKey = new ConcurrentHashMap<PublicKey, Device>();
135
136         @Override
137         public void onDataTreeChanged(Collection<DataTreeModification<Device>> mods) {
138             for (DataTreeModification<Device> dataTreeModification : mods) {
139                 DataObjectModification<Device> rootNode = dataTreeModification.getRootNode();
140                 process(rootNode);
141             }
142         }
143
144         private void process(DataObjectModification<Device> deviceMod) {
145             Device before = deviceMod.getDataBefore();
146             Device after = deviceMod.getDataAfter();
147
148             if (before == null) {
149                 putDevice(after);
150             } else if (after == null) {
151                 // Delete
152                 removeDevice(before);
153             } else {
154                 if (!Objects.equal(before.getSshHostKey(), after.getSshHostKey())) {
155                     // key changed // we should remove previous key.
156                     removeDevice(before);
157                 }
158                 putDevice(after);
159             }
160         }
161
162         private void putDevice(Device device) {
163             PublicKey key = publicKey(device);
164             if (key == null) {
165                 return;
166             }
167             byPublicKey.put(key, device);
168         }
169
170         private void removeDevice(Device device) {
171             PublicKey key = publicKey(device);
172             if (key == null) {
173                 return;
174             }
175             byPublicKey.remove(key);
176         }
177
178         private PublicKey publicKey(Device device) {
179             String hostKey = device.getSshHostKey();
180             try {
181                 return keyDecoder.decodePublicKey(hostKey);
182             } catch (InvalidKeySpecException | NoSuchAlgorithmException | NoSuchProviderException e) {
183                 LOG.error("Unable to decode SSH key for {}. Ignoring update for this device", device.getUniqueId(), e);
184                 return null;
185             }
186         }
187
188         private Device get(PublicKey key) {
189             return byPublicKey.get(key);
190         }
191     }
192
193     private class DeviceOp implements DataTreeChangeListener<Device> {
194
195         private ConcurrentMap<String, Device> byPublicKey = new ConcurrentHashMap<>();
196
197         @Override
198         public void onDataTreeChanged(Collection<DataTreeModification<Device>> mods) {
199             for (DataTreeModification<Device> dataTreeModification : mods) {
200                 DataObjectModification<Device> rootNode = dataTreeModification.getRootNode();
201                 process(rootNode);
202             }
203         }
204
205         private void process(DataObjectModification<Device> deviceMod) {
206             Device before = deviceMod.getDataBefore();
207             Device after = deviceMod.getDataAfter();
208
209             if (before == null) {
210                 putDevice(after);
211             } else if (after == null) {
212                 // Delete
213                 removeDevice(before);
214             } else {
215                 if (!Objects.equal(before.getSshHostKey(), after.getSshHostKey())) {
216                     // key changed // we should remove previous key.
217                     removeDevice(before);
218                 }
219                 putDevice(after);
220             }
221         }
222
223         private void putDevice(Device device) {
224             String key = device.getSshHostKey();
225             byPublicKey.put(key, device);
226         }
227
228         private void removeDevice(Device device) {
229             String key = device.getSshHostKey();
230             byPublicKey.remove(key);
231         }
232
233         Device get(PublicKey serverKey) {
234             String skey = "";
235
236             try {
237                 skey = AuthorizedKeysDecoder.encodePublicKey(serverKey);
238                 return byPublicKey.get(skey);
239             } catch (IOException | IllegalArgumentException e) {
240                 LOG.error("Unable to encode server key: {}", skey, e);
241                 return null;
242             }
243         }
244     }
245
246     private class GlobalConfig implements DataTreeChangeListener<Global> {
247
248         private volatile Global current = null;
249
250         @Override
251         public void onDataTreeChanged(Collection<DataTreeModification<Global>> mods) {
252             for (DataTreeModification<Global> dataTreeModification : mods) {
253                 current = dataTreeModification.getRootNode().getDataAfter();
254             }
255         }
256
257         boolean allowedUnknownKeys() {
258             if (current == null) {
259                 return false;
260             }
261             // Deal with null values.
262             return Boolean.TRUE.equals(current.isAcceptAllSshKeys());
263         }
264
265         Credentials getCredentials() {
266             return current != null ? current.getCredentials() : null;
267         }
268     }
269 }