ONOS-3810 augmenting Rest southbound protocol and provider for https and password based auth
Change-Id: I3e5f07ba6a751bc8a7637373c037a1910181f9ab
diff --git a/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/PortDiscoveryCienaWaveserverImpl.java b/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/PortDiscoveryCienaWaveserverImpl.java
index fe466ad..5e3a442 100644
--- a/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/PortDiscoveryCienaWaveserverImpl.java
+++ b/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/PortDiscoveryCienaWaveserverImpl.java
@@ -54,14 +54,20 @@
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 @@
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 @@
private long toGbps(long speed) {
return speed * 1000;
}
+
}
diff --git a/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/DefaultRestSBDevice.java b/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/DefaultRestSBDevice.java
index baf26cb..aa1b959 100644
--- a/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/DefaultRestSBDevice.java
+++ b/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/DefaultRestSBDevice.java
@@ -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 @@
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 @@
}
@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;
diff --git a/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/RestSBDevice.java b/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/RestSBDevice.java
index 6b76989..abef64e 100644
--- a/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/RestSBDevice.java
+++ b/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/RestSBDevice.java
@@ -79,4 +79,10 @@
*/
String protocol();
+ /**
+ * Returns the url for the REST requests, to be used instead of IP and PORT.
+ *
+ * @return url
+ */
+ String url();
}
diff --git a/protocols/rest/ctl/src/main/java/org/onosproject/protocol/rest/ctl/RestSBControllerImpl.java b/protocols/rest/ctl/src/main/java/org/onosproject/protocol/rest/ctl/RestSBControllerImpl.java
index 0bc3545..0542dec 100644
--- a/protocols/rest/ctl/src/main/java/org/onosproject/protocol/rest/ctl/RestSBControllerImpl.java
+++ b/protocols/rest/ctl/src/main/java/org/onosproject/protocol/rest/ctl/RestSBControllerImpl.java
@@ -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.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 @@
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 @@
@Override
public Map<DeviceId, RestSBDevice> getDevices() {
- return deviceMap;
+ return ImmutableMap.copyOf(deviceMap);
}
@Override
@@ -88,13 +99,8 @@
@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 @@
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 @@
}
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 @@
statusCode == STATUS_ACCEPTED) {
return true;
} else {
- log.error("Failed request: HTTP error code : "
+ log.error("Failed request, HTTP error code : "
+ statusCode);
return false;
}
diff --git a/protocols/rest/ctl/src/test/java/org/onosproject/protocol/rest/ctl/RestSBControllerImplTest.java b/protocols/rest/ctl/src/test/java/org/onosproject/protocol/rest/ctl/RestSBControllerImplTest.java
index ab01cac..c7f809d 100644
--- a/protocols/rest/ctl/src/test/java/org/onosproject/protocol/rest/ctl/RestSBControllerImplTest.java
+++ b/protocols/rest/ctl/src/test/java/org/onosproject/protocol/rest/ctl/RestSBControllerImplTest.java
@@ -39,8 +39,8 @@
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);
}
diff --git a/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceProvider.java b/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceProvider.java
index 76f5abd..18c376b 100644
--- a/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceProvider.java
+++ b/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceProvider.java
@@ -49,9 +49,14 @@
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 @@
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 @@
} 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 @@
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;
}
diff --git a/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceProviderUtilities.java b/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceProviderUtilities.java
new file mode 100644
index 0000000..53c97e1
--- /dev/null
+++ b/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceProviderUtilities.java
@@ -0,0 +1,75 @@
+/*
+ * 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;
+ }
+ }
+}
diff --git a/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestProviderConfig.java b/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestProviderConfig.java
index 2056dce..3671922 100644
--- a/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestProviderConfig.java
+++ b/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestProviderConfig.java
@@ -42,6 +42,7 @@
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 @@
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) {
diff --git a/tools/test/configs/restSB-cfg.json b/tools/test/configs/restSB-cfg.json
index b420f7c..944d40a 100644
--- a/tools/test/configs/restSB-cfg.json
+++ b/tools/test/configs/restSB-cfg.json
@@ -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"