f6af5e41f8145b661ceb6c5335630effbff3658c
[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.net.InetAddresses;
11 import java.io.IOException;
12 import java.net.InetSocketAddress;
13 import java.net.SocketAddress;
14 import java.security.GeneralSecurityException;
15 import java.security.PublicKey;
16 import java.util.Collection;
17 import java.util.concurrent.ConcurrentHashMap;
18 import java.util.concurrent.ConcurrentMap;
19 import javax.annotation.Nonnull;
20 import org.opendaylight.mdsal.binding.api.DataBroker;
21 import org.opendaylight.mdsal.binding.api.DataObjectModification;
22 import org.opendaylight.mdsal.binding.api.DataObjectModification.ModificationType;
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 abstract static class AbstractDeviceListener implements DataTreeChangeListener<Device> {
130
131         @Override
132         public final void onDataTreeChanged(final Collection<DataTreeModification<Device>> mods) {
133             for (DataTreeModification<Device> dataTreeModification : mods) {
134                 final DataObjectModification<Device> deviceMod = dataTreeModification.getRootNode();
135                 final ModificationType modType = deviceMod.getModificationType();
136                 switch (modType) {
137                     case DELETE:
138                         deleteDevice(deviceMod.getDataBefore());
139                         break;
140                     case SUBTREE_MODIFIED:
141                     case WRITE:
142                         deleteDevice(deviceMod.getDataBefore());
143                         writeDevice(deviceMod.getDataAfter());
144                         break;
145                     default:
146                         throw new IllegalStateException("Unhandled modification type " + modType);
147                 }
148             }
149         }
150
151         private void deleteDevice(final Device dataBefore) {
152             if (dataBefore != null) {
153                 final String publicKey = dataBefore.getSshHostKey();
154                 if (publicKey != null) {
155                     LOG.debug("Removing device {}", dataBefore.getUniqueId());
156                     removeDevice(publicKey, dataBefore);
157                 } else {
158                     LOG.debug("Ignoring removal of device {}, no host key present", dataBefore.getUniqueId());
159                 }
160             }
161         }
162
163         private void writeDevice(final Device dataAfter) {
164             final String publicKey = dataAfter.getSshHostKey();
165             if (publicKey != null) {
166                 LOG.debug("Adding device {}", dataAfter.getUniqueId());
167                 addDevice(publicKey, dataAfter);
168             } else {
169                 LOG.debug("Ignoring addition of device {}, no host key present", dataAfter.getUniqueId());
170             }
171         }
172
173         abstract void addDevice(String publicKey, Device device);
174
175         abstract void removeDevice(String publicKey, Device device);
176     }
177
178     private static class DeviceConfig extends AbstractDeviceListener {
179         private final ConcurrentMap<PublicKey, Device> byPublicKey = new ConcurrentHashMap<>();
180         private final AuthorizedKeysDecoder keyDecoder = new AuthorizedKeysDecoder();
181
182         Device get(final PublicKey key) {
183             return byPublicKey.get(key);
184         }
185
186         @Override
187         void addDevice(final String publicKey, final Device device) {
188             final PublicKey key = publicKey(publicKey, device);
189             if (key != null) {
190                 byPublicKey.put(key, device);
191             }
192         }
193
194         @Override
195         void removeDevice(final String publicKey, final Device device) {
196             final PublicKey key = publicKey(publicKey, device);
197             if (key != null) {
198                 byPublicKey.remove(key);
199             }
200         }
201
202         private PublicKey publicKey(final String hostKey, final Device device) {
203             try {
204                 return keyDecoder.decodePublicKey(hostKey);
205             } catch (GeneralSecurityException e) {
206                 LOG.error("Unable to decode SSH key for {}. Ignoring update for this device", device.getUniqueId(), e);
207                 return null;
208             }
209         }
210     }
211
212     private static class DeviceOp extends AbstractDeviceListener {
213         private final ConcurrentMap<String, Device> byPublicKey = new ConcurrentHashMap<>();
214
215         Device get(final PublicKey serverKey) {
216             final String skey;
217             try {
218                 skey = AuthorizedKeysDecoder.encodePublicKey(serverKey);
219             } catch (IOException | IllegalArgumentException e) {
220                 LOG.error("Unable to encode server key: {}", serverKey, e);
221                 return null;
222             }
223
224             return byPublicKey.get(skey);
225         }
226
227         @Override
228         void removeDevice(final String publicKey, final Device device) {
229             byPublicKey.remove(publicKey);
230         }
231
232         @Override
233         void addDevice(final String publicKey, final Device device) {
234             byPublicKey.put(publicKey, device);
235         }
236     }
237
238     private static class GlobalConfig implements DataTreeChangeListener<Global> {
239
240         private volatile Global current = null;
241
242         @Override
243         public void onDataTreeChanged(@Nonnull final Collection<DataTreeModification<Global>> mods) {
244             for (DataTreeModification<Global> dataTreeModification : mods) {
245                 current = dataTreeModification.getRootNode().getDataAfter();
246             }
247         }
248
249         boolean allowedUnknownKeys() {
250             if (current == null) {
251                 return false;
252             }
253             // Deal with null values.
254             return Boolean.TRUE.equals(current.isAcceptAllSshKeys());
255         }
256
257         Credentials getCredentials() {
258             return current != null ? current.getCredentials() : null;
259         }
260     }
261 }