a115bec6c897fedefb04685ddf00cb89cf13c47d
[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.GeneralSecurityException;
16 import java.security.PublicKey;
17 import java.util.Collection;
18 import java.util.concurrent.ConcurrentHashMap;
19 import java.util.concurrent.ConcurrentMap;
20 import javax.annotation.Nonnull;
21 import org.opendaylight.mdsal.binding.api.DataBroker;
22 import org.opendaylight.mdsal.binding.api.DataObjectModification;
23 import org.opendaylight.mdsal.binding.api.DataTreeChangeListener;
24 import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
25 import org.opendaylight.mdsal.binding.api.DataTreeModification;
26 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
27 import org.opendaylight.netconf.callhome.protocol.AuthorizedKeysDecoder;
28 import org.opendaylight.netconf.callhome.protocol.CallHomeAuthorization;
29 import org.opendaylight.netconf.callhome.protocol.CallHomeAuthorization.Builder;
30 import org.opendaylight.netconf.callhome.protocol.CallHomeAuthorizationProvider;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.NetconfCallhomeServer;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.credentials.Credentials;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.AllowedDevices;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.Global;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.Device;
36 import org.opendaylight.yangtools.concepts.ListenerRegistration;
37 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 public class CallHomeAuthProviderImpl implements CallHomeAuthorizationProvider, AutoCloseable {
42
43     private static final Logger LOG = LoggerFactory.getLogger(CallHomeAuthProviderImpl.class);
44     private static final InstanceIdentifier<Global> GLOBAL_PATH =
45             InstanceIdentifier.create(NetconfCallhomeServer.class).child(Global.class);
46     private static final DataTreeIdentifier<Global> GLOBAL =
47             DataTreeIdentifier.create(LogicalDatastoreType.CONFIGURATION, GLOBAL_PATH);
48
49     private static final InstanceIdentifier<Device> ALLOWED_DEVICES_PATH =
50             InstanceIdentifier.create(NetconfCallhomeServer.class).child(AllowedDevices.class).child(Device.class);
51     private static final DataTreeIdentifier<Device> ALLOWED_DEVICES =
52             DataTreeIdentifier.create(LogicalDatastoreType.CONFIGURATION, ALLOWED_DEVICES_PATH);
53     private static final DataTreeIdentifier<Device> ALLOWED_OP_DEVICES =
54             DataTreeIdentifier.create(LogicalDatastoreType.OPERATIONAL, ALLOWED_DEVICES_PATH);
55
56     private final GlobalConfig globalConfig = new GlobalConfig();
57     private final DeviceConfig deviceConfig = new DeviceConfig();
58     private final DeviceOp deviceOp = new DeviceOp();
59     private final ListenerRegistration<GlobalConfig> configReg;
60     private final ListenerRegistration<DeviceConfig> deviceReg;
61     private final ListenerRegistration<DeviceOp> deviceOpReg;
62
63     private final CallhomeStatusReporter statusReporter;
64
65     CallHomeAuthProviderImpl(final DataBroker broker) {
66         configReg = broker.registerDataTreeChangeListener(GLOBAL, globalConfig);
67         deviceReg = broker.registerDataTreeChangeListener(ALLOWED_DEVICES, deviceConfig);
68         deviceOpReg = broker.registerDataTreeChangeListener(ALLOWED_OP_DEVICES, deviceOp);
69         statusReporter = new CallhomeStatusReporter(broker);
70     }
71
72     @Nonnull
73     @Override
74     public CallHomeAuthorization provideAuth(@Nonnull final SocketAddress remoteAddress,
75             @Nonnull final PublicKey serverKey) {
76         Device deviceSpecific = deviceConfig.get(serverKey);
77         String sessionName;
78         Credentials deviceCred;
79
80         if (deviceSpecific != null) {
81             sessionName = deviceSpecific.getUniqueId();
82             deviceCred = deviceSpecific.getCredentials();
83         } else {
84             String syntheticId = fromRemoteAddress(remoteAddress);
85             if (globalConfig.allowedUnknownKeys()) {
86                 sessionName = syntheticId;
87                 deviceCred = null;
88                 statusReporter.asForceListedDevice(syntheticId, serverKey);
89             } else {
90                 Device opDevice = deviceOp.get(serverKey);
91                 if (opDevice == null) {
92                     statusReporter.asUnlistedDevice(syntheticId, serverKey);
93                 } else {
94                     LOG.info("Repeating rejection of unlisted device with id of {}", opDevice.getUniqueId());
95                 }
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() {
116         configReg.close();
117         deviceReg.close();
118         deviceOpReg.close();
119     }
120
121     private static String fromRemoteAddress(final 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 static class DeviceConfig implements DataTreeChangeListener<Device> {
130
131         private final AuthorizedKeysDecoder keyDecoder = new AuthorizedKeysDecoder();
132
133         private final ConcurrentMap<PublicKey, Device> byPublicKey = new ConcurrentHashMap<>();
134
135         @Override
136         public void onDataTreeChanged(@Nonnull final 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(final 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(final 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(final Device device) {
170             PublicKey key = publicKey(device);
171             if (key == null) {
172                 return;
173             }
174             byPublicKey.remove(key);
175         }
176
177         private PublicKey publicKey(final Device device) {
178             String hostKey = device.getSshHostKey();
179             try {
180                 return keyDecoder.decodePublicKey(hostKey);
181             } catch (GeneralSecurityException 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(final PublicKey key) {
188             return byPublicKey.get(key);
189         }
190     }
191
192     private static class DeviceOp implements DataTreeChangeListener<Device> {
193
194         private final ConcurrentMap<String, Device> byPublicKey = new ConcurrentHashMap<>();
195
196         @Override
197         public void onDataTreeChanged(@Nonnull final 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(final 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(final Device device) {
223             String key = device.getSshHostKey();
224             byPublicKey.put(key, device);
225         }
226
227         private void removeDevice(final Device device) {
228             String key = device.getSshHostKey();
229             byPublicKey.remove(key);
230         }
231
232         Device get(final 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 static class GlobalConfig implements DataTreeChangeListener<Global> {
246
247         private volatile Global current = null;
248
249         @Override
250         public void onDataTreeChanged(@Nonnull final 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 }