2 * Copyright (c) 2016 Brocade Communication Systems and others. All rights reserved.
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
8 package org.opendaylight.netconf.callhome.mount;
10 import com.google.common.collect.Iterables;
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.DataObjectModification.ModificationType;
24 import org.opendaylight.mdsal.binding.api.DataTreeChangeListener;
25 import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
26 import org.opendaylight.mdsal.binding.api.DataTreeModification;
27 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
28 import org.opendaylight.netconf.callhome.protocol.AuthorizedKeysDecoder;
29 import org.opendaylight.netconf.callhome.protocol.CallHomeAuthorization;
30 import org.opendaylight.netconf.callhome.protocol.CallHomeAuthorization.Builder;
31 import org.opendaylight.netconf.callhome.protocol.CallHomeAuthorizationProvider;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.NetconfCallhomeServer;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.credentials.Credentials;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.AllowedDevices;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.Global;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.Device;
37 import org.opendaylight.yangtools.concepts.ListenerRegistration;
38 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
42 public class CallHomeAuthProviderImpl implements CallHomeAuthorizationProvider, AutoCloseable {
44 private static final Logger LOG = LoggerFactory.getLogger(CallHomeAuthProviderImpl.class);
45 private static final InstanceIdentifier<Global> GLOBAL_PATH =
46 InstanceIdentifier.create(NetconfCallhomeServer.class).child(Global.class);
47 private static final DataTreeIdentifier<Global> GLOBAL =
48 DataTreeIdentifier.create(LogicalDatastoreType.CONFIGURATION, GLOBAL_PATH);
50 private static final InstanceIdentifier<Device> ALLOWED_DEVICES_PATH =
51 InstanceIdentifier.create(NetconfCallhomeServer.class).child(AllowedDevices.class).child(Device.class);
52 private static final DataTreeIdentifier<Device> ALLOWED_DEVICES =
53 DataTreeIdentifier.create(LogicalDatastoreType.CONFIGURATION, ALLOWED_DEVICES_PATH);
54 private static final DataTreeIdentifier<Device> ALLOWED_OP_DEVICES =
55 DataTreeIdentifier.create(LogicalDatastoreType.OPERATIONAL, ALLOWED_DEVICES_PATH);
57 private final GlobalConfig globalConfig = new GlobalConfig();
58 private final DeviceConfig deviceConfig = new DeviceConfig();
59 private final DeviceOp deviceOp = new DeviceOp();
60 private final ListenerRegistration<GlobalConfig> configReg;
61 private final ListenerRegistration<DeviceConfig> deviceReg;
62 private final ListenerRegistration<DeviceOp> deviceOpReg;
64 private final CallhomeStatusReporter statusReporter;
66 CallHomeAuthProviderImpl(final DataBroker broker) {
67 configReg = broker.registerDataTreeChangeListener(GLOBAL, globalConfig);
68 deviceReg = broker.registerDataTreeChangeListener(ALLOWED_DEVICES, deviceConfig);
69 deviceOpReg = broker.registerDataTreeChangeListener(ALLOWED_OP_DEVICES, deviceOp);
70 statusReporter = new CallhomeStatusReporter(broker);
75 public CallHomeAuthorization provideAuth(@Nonnull final SocketAddress remoteAddress,
76 @Nonnull final PublicKey serverKey) {
77 Device deviceSpecific = deviceConfig.get(serverKey);
79 Credentials deviceCred;
81 if (deviceSpecific != null) {
82 sessionName = deviceSpecific.getUniqueId();
83 deviceCred = deviceSpecific.getCredentials();
85 String syntheticId = fromRemoteAddress(remoteAddress);
86 if (globalConfig.allowedUnknownKeys()) {
87 sessionName = syntheticId;
89 statusReporter.asForceListedDevice(syntheticId, serverKey);
91 Device opDevice = deviceOp.get(serverKey);
92 if (opDevice == null) {
93 statusReporter.asUnlistedDevice(syntheticId, serverKey);
95 LOG.info("Repeating rejection of unlisted device with id of {}", opDevice.getUniqueId());
97 return CallHomeAuthorization.rejected();
101 final Credentials credentials = deviceCred != null ? deviceCred : globalConfig.getCredentials();
103 if (credentials == null) {
104 LOG.info("No credentials found for {}, rejecting.", remoteAddress);
105 return CallHomeAuthorization.rejected();
108 Builder authBuilder = CallHomeAuthorization.serverAccepted(sessionName, credentials.getUsername());
109 for (String password : credentials.getPasswords()) {
110 authBuilder.addPassword(password);
112 return authBuilder.build();
116 public void close() {
122 private static String fromRemoteAddress(final SocketAddress remoteAddress) {
123 if (remoteAddress instanceof InetSocketAddress) {
124 InetSocketAddress socketAddress = (InetSocketAddress) remoteAddress;
125 return InetAddresses.toAddrString(socketAddress.getAddress()) + ":" + socketAddress.getPort();
127 return remoteAddress.toString();
130 private abstract static class AbstractDeviceListener implements DataTreeChangeListener<Device> {
133 public final void onDataTreeChanged(final Collection<DataTreeModification<Device>> mods) {
134 for (DataTreeModification<Device> dataTreeModification : mods) {
135 final DataObjectModification<Device> deviceMod = dataTreeModification.getRootNode();
136 final ModificationType modType = deviceMod.getModificationType();
139 deleteDevice(deviceMod.getDataBefore());
141 case SUBTREE_MODIFIED:
143 deleteDevice(deviceMod.getDataBefore());
144 writeDevice(deviceMod.getDataAfter());
147 throw new IllegalStateException("Unhandled modification type " + modType);
152 private void deleteDevice(final Device dataBefore) {
153 if (dataBefore != null) {
154 final String publicKey = dataBefore.getSshHostKey();
155 if (publicKey != null) {
156 LOG.debug("Removing device {}", dataBefore.getUniqueId());
157 removeDevice(publicKey, dataBefore);
159 LOG.debug("Ignoring removal of device {}, no host key present", dataBefore.getUniqueId());
164 private void writeDevice(final Device dataAfter) {
165 final String publicKey = dataAfter.getSshHostKey();
166 if (publicKey != null) {
167 LOG.debug("Adding device {}", dataAfter.getUniqueId());
168 addDevice(publicKey, dataAfter);
170 LOG.debug("Ignoring addition of device {}, no host key present", dataAfter.getUniqueId());
174 abstract void addDevice(String publicKey, Device device);
176 abstract void removeDevice(String publicKey, Device device);
179 private static class DeviceConfig extends AbstractDeviceListener {
180 private final ConcurrentMap<PublicKey, Device> byPublicKey = new ConcurrentHashMap<>();
181 private final AuthorizedKeysDecoder keyDecoder = new AuthorizedKeysDecoder();
183 Device get(final PublicKey key) {
184 return byPublicKey.get(key);
188 void addDevice(final String publicKey, final Device device) {
189 final PublicKey key = publicKey(publicKey, device);
191 byPublicKey.put(key, device);
196 void removeDevice(final String publicKey, final Device device) {
197 final PublicKey key = publicKey(publicKey, device);
199 byPublicKey.remove(key);
203 private PublicKey publicKey(final String hostKey, final Device device) {
205 return keyDecoder.decodePublicKey(hostKey);
206 } catch (GeneralSecurityException e) {
207 LOG.error("Unable to decode SSH key for {}. Ignoring update for this device", device.getUniqueId(), e);
213 private static class DeviceOp extends AbstractDeviceListener {
214 private final ConcurrentMap<String, Device> byPublicKey = new ConcurrentHashMap<>();
216 Device get(final PublicKey serverKey) {
219 skey = AuthorizedKeysDecoder.encodePublicKey(serverKey);
220 } catch (IOException | IllegalArgumentException e) {
221 LOG.error("Unable to encode server key: {}", serverKey, e);
225 return byPublicKey.get(skey);
229 void removeDevice(final String publicKey, final Device device) {
230 byPublicKey.remove(publicKey);
234 void addDevice(final String publicKey, final Device device) {
235 byPublicKey.put(publicKey, device);
239 private static class GlobalConfig implements DataTreeChangeListener<Global> {
240 private volatile Global current = null;
243 public void onDataTreeChanged(final Collection<DataTreeModification<Global>> mods) {
244 if (!mods.isEmpty()) {
245 current = Iterables.getLast(mods).getRootNode().getDataAfter();
249 boolean allowedUnknownKeys() {
250 final Global local = current;
251 // Deal with null values.
252 return local != null && Boolean.TRUE.equals(local.isAcceptAllSshKeys());
255 Credentials getCredentials() {
256 final Global local = current;
257 return local != null ? local.getCredentials() : null;