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.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;
43 public class CallHomeAuthProviderImpl implements CallHomeAuthorizationProvider, AutoCloseable {
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);
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);
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;
65 private final CallhomeStatusReporter statusReporter;
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);
76 public CallHomeAuthorization provideAuth(@Nonnull SocketAddress remoteAddress, @Nonnull 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() throws Exception {
122 private String fromRemoteAddress(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 class DeviceConfig implements DataTreeChangeListener<Device> {
132 private final AuthorizedKeysDecoder keyDecoder = new AuthorizedKeysDecoder();
134 private final ConcurrentMap<PublicKey, Device> byPublicKey = new ConcurrentHashMap<>();
137 public void onDataTreeChanged(@Nonnull Collection<DataTreeModification<Device>> mods) {
138 for (DataTreeModification<Device> dataTreeModification : mods) {
139 DataObjectModification<Device> rootNode = dataTreeModification.getRootNode();
144 private void process(DataObjectModification<Device> deviceMod) {
145 Device before = deviceMod.getDataBefore();
146 Device after = deviceMod.getDataAfter();
148 if (before == null) {
150 } else if (after == null) {
152 removeDevice(before);
154 if (!Objects.equal(before.getSshHostKey(), after.getSshHostKey())) {
155 // key changed // we should remove previous key.
156 removeDevice(before);
162 private void putDevice(Device device) {
163 PublicKey key = publicKey(device);
167 byPublicKey.put(key, device);
170 private void removeDevice(Device device) {
171 PublicKey key = publicKey(device);
175 byPublicKey.remove(key);
178 private PublicKey publicKey(Device device) {
179 String hostKey = device.getSshHostKey();
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);
188 private Device get(PublicKey key) {
189 return byPublicKey.get(key);
193 private class DeviceOp implements DataTreeChangeListener<Device> {
195 private final ConcurrentMap<String, Device> byPublicKey = new ConcurrentHashMap<>();
198 public void onDataTreeChanged(@Nonnull Collection<DataTreeModification<Device>> mods) {
199 for (DataTreeModification<Device> dataTreeModification : mods) {
200 DataObjectModification<Device> rootNode = dataTreeModification.getRootNode();
205 private void process(DataObjectModification<Device> deviceMod) {
206 Device before = deviceMod.getDataBefore();
207 Device after = deviceMod.getDataAfter();
209 if (before == null) {
211 } else if (after == null) {
213 removeDevice(before);
215 if (!Objects.equal(before.getSshHostKey(), after.getSshHostKey())) {
216 // key changed // we should remove previous key.
217 removeDevice(before);
223 private void putDevice(Device device) {
224 String key = device.getSshHostKey();
225 byPublicKey.put(key, device);
228 private void removeDevice(Device device) {
229 String key = device.getSshHostKey();
230 byPublicKey.remove(key);
233 Device get(PublicKey serverKey) {
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);
246 private class GlobalConfig implements DataTreeChangeListener<Global> {
248 private volatile Global current = null;
251 public void onDataTreeChanged(@Nonnull Collection<DataTreeModification<Global>> mods) {
252 for (DataTreeModification<Global> dataTreeModification : mods) {
253 current = dataTreeModification.getRootNode().getDataAfter();
257 boolean allowedUnknownKeys() {
258 if (current == null) {
261 // Deal with null values.
262 return Boolean.TRUE.equals(current.isAcceptAllSshKeys());
265 Credentials getCredentials() {
266 return current != null ? current.getCredentials() : null;