Andrea Campanella
Committed by Gerrit Code Review

ONOS-3810 augmenting Rest southbound protocol and provider for https and password based auth

Change-Id: I3e5f07ba6a751bc8a7637373c037a1910181f9ab
......@@ -54,14 +54,20 @@ public class PortDiscoveryCienaWaveserverImpl extends AbstractHandlerBehaviour
private static final String NAME = "name";
private static final String ADMIN_STATE = "admin-state";
private static final ArrayList<String> LINESIDE = Lists.newArrayList(
"1.1", "1.2", "12.1", "12.2");
private static final ArrayList<String> LINESIDE_PORT_ID = Lists.newArrayList(
"4", "48");
private static final String GENERAL_PORT_REQUEST =
"yang-api/datastore/ws-ports?config=true&format=xml&depth=unbounded";
private static final String SPECIFIC_PORT_PATH = "yang-api/datastore/ws-ptps/ptp/";
"ws-ports?config=true&format=xml&depth=unbounded";
private static final String SPECIFIC_PORT_PATH = "ws-ptps/ptp/";
private static final String SPECIFIC_PORT_CONFIG =
"/ptp-config?config=true&format=xml&depth=unbounded";
//HTTP strings
// private static final String GENERAL_PORT_REQUEST =
// "/yang-api/datastore/ws-ports?config=true&format=xml&depth=unbounded";
// private static final String SPECIFIC_PORT_PATH = "/yang-api/datastore/ws-ptps/ptp/";
// private static final String SPECIFIC_PORT_CONFIG =
// "/ptp-config?config=true&format=xml&depth=unbounded";
@Override
......@@ -76,24 +82,35 @@ public class PortDiscoveryCienaWaveserverImpl extends AbstractHandlerBehaviour
loadXml(controller.get(deviceId, GENERAL_PORT_REQUEST, XML));
List<HierarchicalConfiguration> portsConfig =
XmlConfigParser.parseWaveServerCienaPorts(config);
portsConfig.stream().forEach(sub -> {
String portId = sub.getString(PORT_ID);
String name = sub.getString(NAME);
SparseAnnotations annotations = DefaultAnnotations.builder()
.set(AnnotationKeys.NAME, String.valueOf(name)).build();
if (LINESIDE.contains(name)) {
String wsportInfoRequest = SPECIFIC_PORT_PATH + sub.getLong(PORT_ID) +
.set(AnnotationKeys.NAME, name).build();
if (LINESIDE_PORT_ID.contains(portId)) {
String wsportInfoRequest = SPECIFIC_PORT_PATH + portId +
SPECIFIC_PORT_CONFIG;
ports.add(XmlConfigParser.parseWaveServerCienaOchPorts(
sub.getLong(PORT_ID),
toGbps(Long.parseLong(sub.getString(SPEED).replace(GBPS, EMPTY_STRING))),
toGbps(Long.parseLong(sub.getString(SPEED).replace(GBPS, EMPTY_STRING)
.replace(" ", EMPTY_STRING))),
XmlConfigParser.loadXml(controller.get(deviceId, wsportInfoRequest, XML)),
annotations));
} else {
//adding corresponding opposite side port
ports.add(XmlConfigParser.parseWaveServerCienaOchPorts(
sub.getLong(PORT_ID) + 1,
toGbps(Long.parseLong(sub.getString(SPEED).replace(GBPS, EMPTY_STRING)
.replace(" ", EMPTY_STRING))),
XmlConfigParser.loadXml(controller.get(deviceId, wsportInfoRequest, XML)),
DefaultAnnotations.builder()
.set(AnnotationKeys.NAME, name.replace(".1", ".2"))
.build()));
} else if (!portId.equals("5") && !portId.equals("49")) {
//FIXME change when all optical types have two way information methods, see jira tickets
final int speed100GbpsinMbps = 100000;
CltSignalType cltType = toGbps(Long.parseLong(
sub.getString(SPEED).replace(GBPS, EMPTY_STRING))) == speed100GbpsinMbps ?
sub.getString(SPEED).replace(GBPS, EMPTY_STRING)
.replace(" ", EMPTY_STRING))) == speed100GbpsinMbps ?
CltSignalType.CLT_100GBE : null;
ports.add(new OduCltPortDescription(PortNumber.portNumber(sub.getLong(PORT_ID)),
sub.getString(ADMIN_STATE).equals(ENABLED),
......@@ -107,5 +124,6 @@ public class PortDiscoveryCienaWaveserverImpl extends AbstractHandlerBehaviour
private long toGbps(long speed) {
return speed * 1000;
}
}
......
......@@ -16,7 +16,9 @@
package org.onosproject.protocol.rest;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import org.apache.commons.lang3.StringUtils;
import org.onlab.packet.IpAddress;
import org.onosproject.net.DeviceId;
......@@ -34,18 +36,20 @@ public class DefaultRestSBDevice implements RestSBDevice {
private final String password;
private boolean isActive;
private String protocol;
private String url;
public DefaultRestSBDevice(IpAddress ip, int port, String name, String password,
String protocol, boolean isActive) {
String protocol, String url, boolean isActive) {
Preconditions.checkNotNull(ip, "IP address cannot be null");
Preconditions.checkArgument(port > 0, "Port address cannot be negative");
Preconditions.checkNotNull(protocol, "protocol address cannot be null");
this.ip = ip;
this.port = port;
this.name = name;
this.password = password;
this.password = StringUtils.isEmpty(password) ? null : password;
this.isActive = isActive;
this.protocol = protocol;
this.url = StringUtils.isEmpty(url) ? null : url;
}
@Override
......@@ -89,6 +93,22 @@ public class DefaultRestSBDevice implements RestSBDevice {
}
@Override
public String url() {
return url;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("url", url)
.add("protocol", protocol)
.add("name", name)
.add("port", port)
.add("ip", ip)
.toString();
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
......
......@@ -79,4 +79,10 @@ public interface RestSBDevice {
*/
String protocol();
/**
* Returns the url for the REST requests, to be used instead of IP and PORT.
*
* @return url
*/
String url();
}
......
......@@ -16,17 +16,22 @@
package org.onosproject.protocol.rest.ctl;
import com.google.common.collect.ImmutableMap;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;
import org.apache.commons.io.IOUtils;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Service;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.onlab.packet.IpAddress;
import org.onosproject.net.DeviceId;
import org.onosproject.protocol.rest.RestSBController;
......@@ -40,6 +45,10 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
......@@ -59,7 +68,9 @@ public class RestSBControllerImpl implements RestSBController {
private static final int STATUS_OK = Response.Status.OK.getStatusCode();
private static final int STATUS_CREATED = Response.Status.CREATED.getStatusCode();
private static final int STATUS_ACCEPTED = Response.Status.ACCEPTED.getStatusCode();
private static final String SLASH = "/";
private static final String HTTPS = "https";
private static final String AUTHORIZATION_PROPERTY = "authorization";
private static final String BASIC_AUTH_PREFIX = "Basic ";
private final Map<DeviceId, RestSBDevice> deviceMap = new ConcurrentHashMap<>();
Client client;
......@@ -78,7 +89,7 @@ public class RestSBControllerImpl implements RestSBController {
@Override
public Map<DeviceId, RestSBDevice> getDevices() {
return deviceMap;
return ImmutableMap.copyOf(deviceMap);
}
@Override
......@@ -88,13 +99,8 @@ public class RestSBControllerImpl implements RestSBController {
@Override
public RestSBDevice getDevice(IpAddress ip, int port) {
for (RestSBDevice device : deviceMap.values()) {
if (device.ip().equals(ip) &&
device.port() == port) {
return device;
}
}
return null;
return deviceMap.values().stream().filter(v -> v.ip().equals(ip)
&& v.port() == port).findFirst().get();
}
@Override
......@@ -162,32 +168,44 @@ public class RestSBControllerImpl implements RestSBController {
throw new IllegalArgumentException("Unsupported media type " + mediaType);
}
return new ByteArrayInputStream(webResource.accept(type).get(ClientResponse.class)
.getEntity(String.class)
.getBytes(StandardCharsets.UTF_8));
ClientResponse s = webResource.accept(type).get(ClientResponse.class);
if (checkReply(s)) {
return new ByteArrayInputStream(s.getEntity(String.class)
.getBytes(StandardCharsets.UTF_8));
}
return null;
}
@Override
public boolean patch(DeviceId device, String request, InputStream payload, String mediaType) {
String url = deviceMap.get(device).protocol() + COLON +
DOUBLESLASH +
deviceMap.get(device).ip().toString() +
COLON + deviceMap.get(device).port() +
SLASH + request;
try {
HttpPatch httprequest = new HttpPatch(url);
log.debug("Url request {} ", getUrlString(device, request));
HttpPatch httprequest = new HttpPatch(getUrlString(device, request));
if (deviceMap.get(device).password() != null) {
String userPassword = deviceMap.get(device).name() + COLON + deviceMap.get(device).password();
String base64string = Base64.getEncoder().encodeToString(userPassword.getBytes(StandardCharsets.UTF_8));
httprequest.addHeader(AUTHORIZATION_PROPERTY, BASIC_AUTH_PREFIX + base64string);
}
if (payload != null) {
StringEntity input = new StringEntity(IOUtils.toString(payload, StandardCharsets.UTF_8));
input.setContentType(mediaType);
httprequest.setEntity(input);
}
int responseStatusCode = HttpClients.createDefault().execute(httprequest)
CloseableHttpClient httpClient;
if (deviceMap.containsKey(device) && deviceMap.get(device).protocol().equals(HTTPS)) {
httpClient = getApacheSslBypassClient();
} else {
httpClient = HttpClients.createDefault();
}
int responseStatusCode = httpClient
.execute(httprequest)
.getStatusLine()
.getStatusCode();
return checkStatusCode(responseStatusCode);
} catch (IOException e) {
log.error("Cannot do PATCH {} request on device {} because can't read payload",
request, device);
} catch (IOException | NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
log.error("Cannot do PATCH {} request on device {}",
request, device, e);
}
return false;
}
......@@ -212,11 +230,35 @@ public class RestSBControllerImpl implements RestSBController {
}
private WebResource getWebResource(DeviceId device, String request) {
return Client.create().resource(deviceMap.get(device).protocol() + COLON +
DOUBLESLASH +
deviceMap.get(device).ip().toString() +
COLON + deviceMap.get(device).port() +
SLASH + request);
log.debug("Sending request to URL {} ", getUrlString(device, request));
WebResource webResource = client.resource(getUrlString(device, request));
if (deviceMap.containsKey(device) && deviceMap.get(device).password() != null) {
client.addFilter(new HTTPBasicAuthFilter(deviceMap.get(device).name(),
deviceMap.get(device).password()));
}
return webResource;
}
//FIXME security issue: this trusts every SSL certificate, even if is self-signed. Also deprecated methods.
private CloseableHttpClient getApacheSslBypassClient() throws NoSuchAlgorithmException,
KeyManagementException, KeyStoreException {
return HttpClients.custom().
setHostnameVerifier(new AllowAllHostnameVerifier()).
setSslcontext(new SSLContextBuilder()
.loadTrustMaterial(null, (arg0, arg1) -> true)
.build()).build();
}
private String getUrlString(DeviceId device, String request) {
if (deviceMap.get(device).url() != null) {
return deviceMap.get(device).protocol() + COLON + DOUBLESLASH
+ deviceMap.get(device).url() + request;
} else {
return deviceMap.get(device).protocol() + COLON +
DOUBLESLASH +
deviceMap.get(device).ip().toString() +
COLON + deviceMap.get(device).port() + request;
}
}
private boolean checkReply(ClientResponse response) {
......@@ -233,7 +275,7 @@ public class RestSBControllerImpl implements RestSBController {
statusCode == STATUS_ACCEPTED) {
return true;
} else {
log.error("Failed request: HTTP error code : "
log.error("Failed request, HTTP error code : "
+ statusCode);
return false;
}
......
......@@ -39,8 +39,8 @@ public class RestSBControllerImplTest {
public void setUp() {
controller = new RestSBControllerImpl();
controller.activate();
device1 = new DefaultRestSBDevice(IpAddress.valueOf("127.0.0.1"), 8080, "foo", "bar", "http", true);
device2 = new DefaultRestSBDevice(IpAddress.valueOf("127.0.0.2"), 8080, "foo1", "bar2", "http", true);
device1 = new DefaultRestSBDevice(IpAddress.valueOf("127.0.0.1"), 8080, "foo", "bar", "http", null, true);
device2 = new DefaultRestSBDevice(IpAddress.valueOf("127.0.0.2"), 8080, "foo1", "bar2", "http", null, true);
controller.addDevice(device1);
}
......
......@@ -49,9 +49,14 @@ import org.onosproject.protocol.rest.RestSBController;
import org.onosproject.protocol.rest.RestSBDevice;
import org.slf4j.Logger;
import javax.net.ssl.HttpsURLConnection;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.HashSet;
import java.util.Set;
......@@ -71,6 +76,10 @@ public class RestDeviceProvider extends AbstractProvider
private static final String PROVIDER = "org.onosproject.provider.rest.device";
private static final String IPADDRESS = "ipaddress";
private static final int TEST_CONNECT_TIMEOUT = 1000;
private static final String HTTPS = "https";
private static final String AUTHORIZATION_PROPERTY = "authorization";
private static final String BASIC_AUTH_PREFIX = "Basic ";
private static final String URL_SEPARATOR = "://";
private final Logger log = getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
......@@ -202,7 +211,7 @@ public class RestDeviceProvider extends AbstractProvider
} catch (ConfigException e) {
log.error("Configuration error {}", e);
}
log.info("REST Devices {}", controller.getDevices());
log.debug("REST Devices {}", controller.getDevices());
controller.getDevices().keySet().forEach(deviceId -> {
DriverHandler h = driverService.createHandler(deviceId);
PortDiscovery portConfig = h.behaviour(PortDiscovery.class);
......@@ -216,14 +225,39 @@ public class RestDeviceProvider extends AbstractProvider
private boolean testDeviceConnection(RestSBDevice device) {
try {
URL url = new URL(device.protocol(), device.ip().toString(), device.port(), "/");
HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
URL url;
if (device.url() == null) {
url = new URL(device.protocol(), device.ip().toString(), device.port(), "");
} else {
url = new URL(device.protocol() + URL_SEPARATOR + device.url());
}
HttpURLConnection urlConn;
if (device.protocol().equals(HTTPS)) {
//FIXME this method provides no security accepting all SSL certs.
RestDeviceProviderUtilities.enableSslCert();
urlConn = (HttpsURLConnection) url.openConnection();
} else {
urlConn = (HttpURLConnection) url.openConnection();
}
if (device.password() != null) {
String userPassword = device.name() + ":" + device.password();
String basicAuth = Base64.getEncoder()
.encodeToString(userPassword.getBytes(StandardCharsets.UTF_8));
urlConn.setRequestProperty(AUTHORIZATION_PROPERTY, BASIC_AUTH_PREFIX + basicAuth);
}
urlConn.setConnectTimeout(TEST_CONNECT_TIMEOUT);
boolean open = urlConn.getResponseCode() == (HttpURLConnection.HTTP_OK);
boolean open = urlConn.getResponseCode() == (HttpsURLConnection.HTTP_OK);
if (!open) {
log.error("Device {} not accessibile, response code {} ", device,
urlConn.getResponseCode());
}
urlConn.disconnect();
return open;
} catch (IOException e) {
log.error("Device {} not reachable, error creating HTTP connection", device, e);
} catch (IOException | NoSuchAlgorithmException | KeyManagementException e) {
log.error("Device {} not reachable, error creating {} connection", device,
device.protocol(), e);
}
return false;
}
......
/*
* Copyright 2016 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.provider.rest.device.impl;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
/**
* Utilities class for RestDevice provider.
*/
final class RestDeviceProviderUtilities {
private static final String TLS = "TLS";
//disable construction.
private RestDeviceProviderUtilities(){}
/**
* Method that bypasses every SSL certificate verification and accepts every
* connection with any SSL protected device that ONOS has an interaction with.
* Needs addressing for secutirty purposes.
*
* @throws NoSuchAlgorithmException
* @throws KeyManagementException
*/
//FIXME redo for security purposes.
protected static void enableSslCert() throws NoSuchAlgorithmException, KeyManagementException {
SSLContext ctx = SSLContext.getInstance(TLS);
ctx.init(new KeyManager[0], new TrustManager[]{new DefaultTrustManager()}, new SecureRandom());
SSLContext.setDefault(ctx);
HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> {
//FIXME better way to do this.
return true;
});
}
//FIXME this accepts every connection
private static class DefaultTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
}
......@@ -42,6 +42,7 @@ public class RestProviderConfig extends Config<ApplicationId> {
private static final String NAME = "name";
private static final String PASSWORD = "password";
private static final String PROTOCOL = "protocol";
private static final String URL = "url";
public Set<RestSBDevice> getDevicesAddresses() throws ConfigException {
Set<RestSBDevice> devicesAddresses = Sets.newHashSet();
......@@ -54,8 +55,10 @@ public class RestProviderConfig extends Config<ApplicationId> {
String name = node.path(NAME).asText();
String password = node.path(PASSWORD).asText();
String protocol = node.path(PROTOCOL).asText();
String url = node.path(URL).asText();
devicesAddresses.add(new DefaultRestSBDevice(ipAddr, port, name,
password, protocol, false));
password, protocol,
url, false));
}
} catch (IllegalArgumentException e) {
......
......@@ -9,8 +9,8 @@
"apps": {
"org.onosproject.restsb": {
"restDevices": [{
"name": "local",
"password": "local",
"name": "dev",
"password": "",
"ip": "127.0.0.1",
"port": 8080,
"protocol": "http"
......