Merge "BUG-1422 Introduce Call-Home functionality for the NETCONF Topology."
[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.net.InetSocketAddress;
13 import java.net.SocketAddress;
14 import java.security.PublicKey;
15 import java.util.Collection;
16 import java.util.concurrent.ConcurrentHashMap;
17 import java.util.concurrent.ConcurrentMap;
18 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
19 import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
20 import org.opendaylight.controller.md.sal.binding.api.DataTreeChangeListener;
21 import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
22 import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
23 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
24 import org.opendaylight.netconf.callhome.protocol.AuthorizedKeysDecoder;
25 import org.opendaylight.netconf.callhome.protocol.CallHomeAuthorization;
26 import org.opendaylight.netconf.callhome.protocol.CallHomeAuthorization.Builder;
27 import org.opendaylight.netconf.callhome.protocol.CallHomeAuthorizationProvider;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.NetconfCallhomeServer;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.credentials.Credentials;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.AllowedDevices;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.Global;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev161109.netconf.callhome.server.allowed.devices.Device;
33 import org.opendaylight.yangtools.concepts.ListenerRegistration;
34 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 public class CallHomeAuthProviderImpl implements CallHomeAuthorizationProvider, AutoCloseable {
39
40     private static final Logger LOG = LoggerFactory.getLogger(CallHomeAuthProviderImpl.class);
41     private static final InstanceIdentifier<Global> GLOBAL_PATH =
42             InstanceIdentifier.create(NetconfCallhomeServer.class).child(Global.class);
43     private static final DataTreeIdentifier<Global> GLOBAL =
44             new DataTreeIdentifier<>(LogicalDatastoreType.CONFIGURATION, GLOBAL_PATH);
45
46     private static final InstanceIdentifier<Device> ALLOWED_DEVICES_PATH =
47             InstanceIdentifier.create(NetconfCallhomeServer.class).child(AllowedDevices.class).child(Device.class);
48     private static final DataTreeIdentifier<Device> ALLOWED_DEVICES =
49             new DataTreeIdentifier<>(LogicalDatastoreType.CONFIGURATION, ALLOWED_DEVICES_PATH);
50
51     private final GlobalConfig globalConfig = new GlobalConfig();
52     private final DeviceConfig deviceConfig = new DeviceConfig();
53     private final ListenerRegistration<GlobalConfig> configReg;
54     private final ListenerRegistration<DeviceConfig> deviceReg;
55
56     public CallHomeAuthProviderImpl(DataBroker broker) {
57         configReg = broker.registerDataTreeChangeListener(GLOBAL, globalConfig);
58         deviceReg = broker.registerDataTreeChangeListener(ALLOWED_DEVICES, deviceConfig);
59     }
60
61     @Override
62     public CallHomeAuthorization provideAuth(SocketAddress remoteAddress, PublicKey serverKey) {
63         Device deviceSpecific = deviceConfig.get(serverKey);
64         String sessionName;
65         Credentials deviceCred;
66         if (deviceSpecific != null) {
67             sessionName = deviceSpecific.getUniqueId();
68             deviceCred = deviceSpecific.getCredentials();
69         } else if (globalConfig.allowedUnknownKeys()) {
70             sessionName = fromRemoteAddress(remoteAddress);
71             deviceCred = null;
72         } else {
73             return CallHomeAuthorization.rejected();
74         }
75         final Credentials credentials = deviceCred != null ? deviceCred : globalConfig.getCredentials();
76
77         if (credentials == null) {
78             LOG.info("No credentials found for {}, rejecting.", remoteAddress);
79             return CallHomeAuthorization.rejected();
80         }
81         Builder authBuilder = CallHomeAuthorization.serverAccepted(sessionName, credentials.getUsername());
82         for (String password : credentials.getPasswords()) {
83             authBuilder.addPassword(password);
84         }
85         return authBuilder.build();
86     }
87
88     @Override
89     public void close() throws Exception {
90         configReg.close();
91         deviceReg.close();
92     }
93
94     private String fromRemoteAddress(SocketAddress remoteAddress) {
95         if (remoteAddress instanceof InetSocketAddress) {
96             InetSocketAddress socketAddress = (InetSocketAddress) remoteAddress;
97             return InetAddresses.toAddrString(socketAddress.getAddress()) + ":" + socketAddress.getPort();
98         }
99         return remoteAddress.toString();
100     }
101
102
103     private class DeviceConfig implements DataTreeChangeListener<Device> {
104
105         private ConcurrentMap<PublicKey, Device> byPublicKey = new ConcurrentHashMap<PublicKey, Device>();
106
107         @Override
108         public void onDataTreeChanged(Collection<DataTreeModification<Device>> arg0) {
109             for (DataTreeModification<Device> dataTreeModification : arg0) {
110                 DataObjectModification<Device> rootNode = dataTreeModification.getRootNode();
111                 process(rootNode);
112             }
113         }
114
115         private void process(DataObjectModification<Device> deviceMod) {
116             Device before = deviceMod.getDataBefore();
117             Device after = deviceMod.getDataAfter();
118
119             if (before == null) {
120                 putDevice(after);
121             } else if (after == null) {
122                 // Delete
123                 removeDevice(before);
124             } else {
125                 if (!Objects.equal(before.getSshHostKey(), after.getSshHostKey())) {
126                     // key changed // we should remove previous key.
127                     removeDevice(before);
128                 }
129                 putDevice(after);
130             }
131         }
132
133         private void putDevice(Device device) {
134             PublicKey key = publicKey(device);
135             if (key == null) {
136                 return;
137             }
138             byPublicKey.put(key, device);
139         }
140
141         private void removeDevice(Device device) {
142             PublicKey key = publicKey(device);
143             if (key == null) {
144                 return;
145             }
146             byPublicKey.remove(key);
147         }
148
149         private PublicKey publicKey(Device device) {
150             String hostKey = device.getSshHostKey();
151             try {
152                 return new AuthorizedKeysDecoder().decodePublicKey(hostKey);
153             } catch (Exception e) {
154                 LOG.error("Unable to decode SSH key for {}. Ignoring update for this device",device.getUniqueId(),e);
155                 return null;
156             }
157         }
158
159         private Device get(PublicKey key) {
160             return byPublicKey.get(key);
161         }
162     }
163
164     private class GlobalConfig implements DataTreeChangeListener<Global> {
165
166
167         private volatile Global current = null;
168
169         @Override
170         public void onDataTreeChanged(Collection<DataTreeModification<Global>> arg0) {
171             for (DataTreeModification<Global> dataTreeModification : arg0) {
172                 current = dataTreeModification.getRootNode().getDataAfter();
173             }
174         }
175
176         boolean allowedUnknownKeys() {
177             if (current == null) {
178                 return false;
179             }
180             // Deal with null values.
181             return Boolean.TRUE.equals(current.isAcceptAllSshKeys());
182         }
183
184         Credentials getCredentials() {
185             return current != null ? current.getCredentials() : null;
186         }
187
188     }
189
190 }