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.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;
41 public class CallHomeAuthProviderImpl implements CallHomeAuthorizationProvider, AutoCloseable {
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);
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);
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;
63 private final CallhomeStatusReporter statusReporter;
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);
74 public CallHomeAuthorization provideAuth(@Nonnull final SocketAddress remoteAddress,
75 @Nonnull final PublicKey serverKey) {
76 Device deviceSpecific = deviceConfig.get(serverKey);
78 Credentials deviceCred;
80 if (deviceSpecific != null) {
81 sessionName = deviceSpecific.getUniqueId();
82 deviceCred = deviceSpecific.getCredentials();
84 String syntheticId = fromRemoteAddress(remoteAddress);
85 if (globalConfig.allowedUnknownKeys()) {
86 sessionName = syntheticId;
88 statusReporter.asForceListedDevice(syntheticId, serverKey);
90 Device opDevice = deviceOp.get(serverKey);
91 if (opDevice == null) {
92 statusReporter.asUnlistedDevice(syntheticId, serverKey);
94 LOG.info("Repeating rejection of unlisted device with id of {}", opDevice.getUniqueId());
96 return CallHomeAuthorization.rejected();
100 final Credentials credentials = deviceCred != null ? deviceCred : globalConfig.getCredentials();
102 if (credentials == null) {
103 LOG.info("No credentials found for {}, rejecting.", remoteAddress);
104 return CallHomeAuthorization.rejected();
107 Builder authBuilder = CallHomeAuthorization.serverAccepted(sessionName, credentials.getUsername());
108 for (String password : credentials.getPasswords()) {
109 authBuilder.addPassword(password);
111 return authBuilder.build();
115 public void close() {
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();
126 return remoteAddress.toString();
129 private static class DeviceConfig implements DataTreeChangeListener<Device> {
131 private final AuthorizedKeysDecoder keyDecoder = new AuthorizedKeysDecoder();
133 private final ConcurrentMap<PublicKey, Device> byPublicKey = new ConcurrentHashMap<>();
136 public void onDataTreeChanged(@Nonnull final Collection<DataTreeModification<Device>> mods) {
137 for (DataTreeModification<Device> dataTreeModification : mods) {
138 DataObjectModification<Device> rootNode = dataTreeModification.getRootNode();
143 private void process(final DataObjectModification<Device> deviceMod) {
144 Device before = deviceMod.getDataBefore();
145 Device after = deviceMod.getDataAfter();
147 if (before == null) {
149 } else if (after == null) {
151 removeDevice(before);
153 if (!Objects.equal(before.getSshHostKey(), after.getSshHostKey())) {
154 // key changed // we should remove previous key.
155 removeDevice(before);
161 private void putDevice(final Device device) {
162 PublicKey key = publicKey(device);
166 byPublicKey.put(key, device);
169 private void removeDevice(final Device device) {
170 PublicKey key = publicKey(device);
174 byPublicKey.remove(key);
177 private PublicKey publicKey(final Device device) {
178 String hostKey = device.getSshHostKey();
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);
187 private Device get(final PublicKey key) {
188 return byPublicKey.get(key);
192 private static class DeviceOp implements DataTreeChangeListener<Device> {
194 private final ConcurrentMap<String, Device> byPublicKey = new ConcurrentHashMap<>();
197 public void onDataTreeChanged(@Nonnull final Collection<DataTreeModification<Device>> mods) {
198 for (DataTreeModification<Device> dataTreeModification : mods) {
199 DataObjectModification<Device> rootNode = dataTreeModification.getRootNode();
204 private void process(final DataObjectModification<Device> deviceMod) {
205 Device before = deviceMod.getDataBefore();
206 Device after = deviceMod.getDataAfter();
208 if (before == null) {
210 } else if (after == null) {
212 removeDevice(before);
214 if (!Objects.equal(before.getSshHostKey(), after.getSshHostKey())) {
215 // key changed // we should remove previous key.
216 removeDevice(before);
222 private void putDevice(final Device device) {
223 String key = device.getSshHostKey();
224 byPublicKey.put(key, device);
227 private void removeDevice(final Device device) {
228 String key = device.getSshHostKey();
229 byPublicKey.remove(key);
232 Device get(final PublicKey serverKey) {
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);
245 private static class GlobalConfig implements DataTreeChangeListener<Global> {
247 private volatile Global current = null;
250 public void onDataTreeChanged(@Nonnull final Collection<DataTreeModification<Global>> mods) {
251 for (DataTreeModification<Global> dataTreeModification : mods) {
252 current = dataTreeModification.getRootNode().getDataAfter();
256 boolean allowedUnknownKeys() {
257 if (current == null) {
260 // Deal with null values.
261 return Boolean.TRUE.equals(current.isAcceptAllSshKeys());
264 Credentials getCredentials() {
265 return current != null ? current.getCredentials() : null;