Skip to content
Snippets Groups Projects
Commit 2dc31089 authored by Ryan Izard's avatar Ryan Izard
Browse files

Updated Restlet from 2.2M3 to 2.3.1. Included with the update is full SSL...

Updated Restlet from 2.2M3 to 2.3.1. Included with the update is full SSL support for the REST API, either all-access+encryption or restricted-access+encryption (truststore verified clients only). Additional options have been added to floodlightdefault.properties in order to turn HTTPS on/off. HTTPS ccan be used in conjunction with vanilla HTTP as well -- each must reside on a different TCP port though. Also, this time, I remembered to update and test the build.xml file to remove the old and include the new Restlet jars.
parent 2df40ecb
No related branches found
No related tags found
No related merge requests found
Showing with 302 additions and 181 deletions
......@@ -61,11 +61,13 @@
<include name="jackson-dataformat-yaml-2.1.4.jar"/>
<include name="jackson-dataformat-csv-2.1.4.jar"/>
<include name="slf4j-api-1.6.4.jar"/>
<include name="org.restlet-2.2M3.jar"/>
<include name="org.restlet.ext.jackson-2.2M3.jar"/>
<include name="org.restlet.ext.simple-2.2M3.jar"/>
<include name="org.restlet.ext.slf4j-2.2M3.jar"/>
<include name="simple-5.1.1.jar"/>
<include name="org.restlet.jar"/>
<include name="org.restlet.ext.jackson.jar"/>
<include name="org.restlet.ext.simple.jar"/>
<include name="org.restlet.ext.slf4j.jar"/>
<include name="org.restlet.ext.jsslutils.jar"/>
<include name="org.simpleframework.jar"/>
<include name="org.jsslutils.jar"/>
<include name="netty-3.10.0.Final.jar"/>
<include name="args4j-2.0.16.jar"/>
<include name="concurrentlinkedhashmap-lru-1.2.jar"/>
......
File added
File deleted
File deleted
File added
File added
File deleted
File added
File deleted
File added
File added
File added
File deleted
......@@ -28,6 +28,8 @@ import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.Restlet;
import org.restlet.Server;
import org.restlet.data.Parameter;
import org.restlet.data.Protocol;
import org.restlet.data.Reference;
import org.restlet.data.Status;
......@@ -37,6 +39,7 @@ import org.restlet.routing.Filter;
import org.restlet.routing.Router;
import org.restlet.routing.Template;
import org.restlet.service.StatusService;
import org.restlet.util.Series;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -46,179 +49,288 @@ import net.floodlightcontroller.core.module.FloodlightModuleException;
import net.floodlightcontroller.core.module.IFloodlightModule;
import net.floodlightcontroller.core.module.IFloodlightService;
public class RestApiServer
implements IFloodlightModule, IRestApiService {
protected static Logger logger = LoggerFactory.getLogger(RestApiServer.class);
protected List<RestletRoutable> restlets;
protected FloodlightModuleContext fmlContext;
protected String restHost = null;
protected int restPort = 8080;
// ***********
// Application
// ***********
protected class RestApplication extends Application {
protected Context context;
public RestApplication() {
super(new Context());
this.context = getContext();
}
@Override
public Restlet createInboundRoot() {
Router baseRouter = new Router(context);
baseRouter.setDefaultMatchingMode(Template.MODE_STARTS_WITH);
for (RestletRoutable rr : restlets) {
baseRouter.attach(rr.basePath(), rr.getRestlet(context));
}
Filter slashFilter = new Filter() {
@Override
protected int beforeHandle(Request request, Response response) {
Reference ref = request.getResourceRef();
String originalPath = ref.getPath();
if (originalPath.contains("//"))
{
String newPath = originalPath.replaceAll("/+", "/");
ref.setPath(newPath);
}
return Filter.CONTINUE;
}
};
slashFilter.setNext(baseRouter);
return slashFilter;
}
public void run(FloodlightModuleContext fmlContext, String restHost, int restPort) {
setStatusService(new StatusService() {
@Override
public Representation getRepresentation(Status status,
Request request,
Response response) {
return new JacksonRepresentation<Status>(status);
}
});
// Add everything in the module context to the rest
for (Class<? extends IFloodlightService> s : fmlContext.getAllServices()) {
if (logger.isTraceEnabled()) {
logger.trace("Adding {} for service {} into context",
s.getCanonicalName(), fmlContext.getServiceImpl(s));
}
context.getAttributes().put(s.getCanonicalName(),
fmlContext.getServiceImpl(s));
}
/*
* Specifically add the FML for use by the REST API's /wm/core/modules/...
*/
context.getAttributes().put(fmlContext.getModuleLoader().getClass().getCanonicalName(), fmlContext.getModuleLoader());
// Start listening for REST requests
try {
final Component component = new Component();
if (restHost == null) {
component.getServers().add(Protocol.HTTP, restPort);
} else {
component.getServers().add(Protocol.HTTP, restHost, restPort);
}
component.getClients().add(Protocol.CLAP);
component.getDefaultHost().attach(this);
component.start();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
// ***************
// IRestApiService
// ***************
@Override
public void addRestletRoutable(RestletRoutable routable) {
restlets.add(routable);
}
@Override
public void run() {
if (logger.isDebugEnabled()) {
StringBuffer sb = new StringBuffer();
sb.append("REST API routables: ");
for (RestletRoutable routable : restlets) {
sb.append(routable.getClass().getSimpleName());
sb.append(" (");
sb.append(routable.basePath());
sb.append("), ");
}
logger.debug(sb.toString());
}
RestApplication restApp = new RestApplication();
restApp.run(fmlContext, restHost, restPort);
}
// *****************
// IFloodlightModule
// *****************
@Override
public Collection<Class<? extends IFloodlightService>> getModuleServices() {
Collection<Class<? extends IFloodlightService>> services =
new ArrayList<Class<? extends IFloodlightService>>(1);
services.add(IRestApiService.class);
return services;
}
@Override
public Map<Class<? extends IFloodlightService>, IFloodlightService>
getServiceImpls() {
Map<Class<? extends IFloodlightService>,
IFloodlightService> m =
new HashMap<Class<? extends IFloodlightService>,
IFloodlightService>();
m.put(IRestApiService.class, this);
return m;
}
@Override
public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
// We don't have any
return null;
}
@Override
public void init(FloodlightModuleContext context)
throws FloodlightModuleException {
// This has to be done here since we don't know what order the
// startUp methods will be called
this.restlets = new ArrayList<RestletRoutable>();
this.fmlContext = context;
// read our config options
Map<String, String> configOptions = context.getConfigParams(this);
restHost = configOptions.get("host");
if (restHost == null) {
Map<String, String> providerConfigOptions = context.getConfigParams(
FloodlightProvider.class);
restHost = providerConfigOptions.get("openflowhost");
}
if (restHost != null) {
logger.debug("REST host set to {}", restHost);
}
String port = configOptions.get("port");
if (port != null) {
restPort = Integer.parseInt(port);
}
logger.debug("REST port set to {}", restPort);
}
@Override
public void startUp(FloodlightModuleContext Context) {
// no-op
}
public class RestApiServer implements IFloodlightModule, IRestApiService {
protected static Logger logger = LoggerFactory.getLogger(RestApiServer.class);
protected List<RestletRoutable> restlets;
protected FloodlightModuleContext fmlContext;
protected String restHost = null;
private static String keyStorePassword;
private static String keyStore;
private static String httpsNeedClientAuth = "true";
private static boolean useHttps = false;
private static boolean useHttp = false;
private static String httpsPort;
private static String httpPort;
// ***********
// Application
// ***********
protected class RestApplication extends Application {
protected Context context;
public RestApplication() {
super(new Context());
this.context = getContext();
}
@Override
public Restlet createInboundRoot() {
Router baseRouter = new Router(context);
baseRouter.setDefaultMatchingMode(Template.MODE_STARTS_WITH);
for (RestletRoutable rr : restlets) {
baseRouter.attach(rr.basePath(), rr.getRestlet(context));
}
Filter slashFilter = new Filter() {
@Override
protected int beforeHandle(Request request, Response response) {
Reference ref = request.getResourceRef();
String originalPath = ref.getPath();
if (originalPath.contains("//"))
{
String newPath = originalPath.replaceAll("/+", "/");
ref.setPath(newPath);
}
return Filter.CONTINUE;
}
};
slashFilter.setNext(baseRouter);
return slashFilter;
}
public void run(FloodlightModuleContext fmlContext, String restHost) {
setStatusService(new StatusService() {
@Override
public Representation getRepresentation(Status status,
Request request,
Response response) {
return new JacksonRepresentation<Status>(status);
}
});
// Add everything in the module context to the rest
for (Class<? extends IFloodlightService> s : fmlContext.getAllServices()) {
if (logger.isTraceEnabled()) {
logger.trace("Adding {} for service {} into context",
s.getCanonicalName(), fmlContext.getServiceImpl(s));
}
context.getAttributes().put(s.getCanonicalName(),
fmlContext.getServiceImpl(s));
}
/*
* Specifically add the FML for use by the REST API's /wm/core/modules/...
*/
context.getAttributes().put(fmlContext.getModuleLoader().getClass().getCanonicalName(), fmlContext.getModuleLoader());
/* Start listening for REST requests */
try {
final Component component = new Component();
if (RestApiServer.useHttps) {
Server server;
if (restHost == null) {
server = component.getServers().add(Protocol.HTTPS, Integer.valueOf(RestApiServer.httpsPort));
} else {
server = component.getServers().add(Protocol.HTTPS, restHost, Integer.valueOf(RestApiServer.httpsPort));
}
Series<Parameter> parameters = server.getContext().getParameters();
//parameters.add("sslContextFactory", "org.restlet.ext.jsslutils.PkixSslContextFactory");
parameters.add("sslContextFactory", "org.restlet.engine.ssl.DefaultSslContextFactory");
parameters.add("keystorePath", RestApiServer.keyStore);
parameters.add("keystorePassword", RestApiServer.keyStorePassword);
parameters.add("keyPassword", RestApiServer.keyStorePassword);
parameters.add("keystoreType", "JKS");
parameters.add("truststorePath", RestApiServer.keyStore);
parameters.add("truststorePassword", RestApiServer.keyStorePassword);
parameters.add("trustPassword", RestApiServer.keyStorePassword);
parameters.add("truststoreType", "JKS");
parameters.add("needClientAuthentication", RestApiServer.httpsNeedClientAuth);
}
if (RestApiServer.useHttp) {
if (restHost == null) {
component.getServers().add(Protocol.HTTP, Integer.valueOf(RestApiServer.httpPort));
} else {
component.getServers().add(Protocol.HTTP, restHost, Integer.valueOf(RestApiServer.httpPort));
}
}
component.getClients().add(Protocol.CLAP);
component.getDefaultHost().attach(this);
component.start();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
// ***************
// IRestApiService
// ***************
@Override
public void addRestletRoutable(RestletRoutable routable) {
restlets.add(routable);
}
@Override
public void run() {
if (logger.isDebugEnabled()) {
StringBuffer sb = new StringBuffer();
sb.append("REST API routables: ");
for (RestletRoutable routable : restlets) {
sb.append(routable.getClass().getSimpleName());
sb.append(" (");
sb.append(routable.basePath());
sb.append("), ");
}
logger.debug(sb.toString());
}
RestApplication restApp = new RestApplication();
restApp.run(fmlContext, restHost);
}
// *****************
// IFloodlightModule
// *****************
@Override
public Collection<Class<? extends IFloodlightService>> getModuleServices() {
Collection<Class<? extends IFloodlightService>> services =
new ArrayList<Class<? extends IFloodlightService>>(1);
services.add(IRestApiService.class);
return services;
}
@Override
public Map<Class<? extends IFloodlightService>, IFloodlightService>
getServiceImpls() {
Map<Class<? extends IFloodlightService>,
IFloodlightService> m =
new HashMap<Class<? extends IFloodlightService>,
IFloodlightService>();
m.put(IRestApiService.class, this);
return m;
}
@Override
public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
// We don't have any
return null;
}
@Override
public void init(FloodlightModuleContext context)
throws FloodlightModuleException {
// This has to be done here since we don't know what order the
// startUp methods will be called
this.restlets = new ArrayList<RestletRoutable>();
this.fmlContext = context;
// read our config options
Map<String, String> configOptions = context.getConfigParams(this);
restHost = configOptions.get("host");
if (restHost == null) {
Map<String, String> providerConfigOptions = context.getConfigParams(
FloodlightProvider.class);
restHost = providerConfigOptions.get("openflowhost");
}
if (restHost != null) {
logger.debug("REST host set to {}", restHost);
}
String path = configOptions.get("keyStorePath");
String pass = configOptions.get("keyStorePassword");
String useHttps = configOptions.get("useHttps");
String useHttp = configOptions.get("useHttp");
String httpsNeedClientAuth = configOptions.get("httpsNeedClientAuthentication");
/* HTTPS Access (ciphertext) */
if (useHttps == null || path == null || path.isEmpty() ||
(!useHttps.trim().equalsIgnoreCase("yes") && !useHttps.trim().equalsIgnoreCase("true") &&
!useHttps.trim().equalsIgnoreCase("yep") && !useHttps.trim().equalsIgnoreCase("ja") &&
!useHttps.trim().equalsIgnoreCase("stimmt")
)
) {
RestApiServer.useHttps = false;
RestApiServer.keyStore = null;
RestApiServer.keyStorePassword = null;
} else {
RestApiServer.useHttps = true;
RestApiServer.keyStore = path;
RestApiServer.keyStorePassword = (pass == null ? "" : pass);
String port = configOptions.get("httpsPort");
if (port != null && !port.isEmpty()) {
RestApiServer.httpsPort = port.trim();
}
if (httpsNeedClientAuth == null || (!httpsNeedClientAuth.trim().equalsIgnoreCase("yes") &&
!httpsNeedClientAuth.trim().equalsIgnoreCase("true") &&
!httpsNeedClientAuth.trim().equalsIgnoreCase("yep") &&
!httpsNeedClientAuth.trim().equalsIgnoreCase("ja") &&
!httpsNeedClientAuth.trim().equalsIgnoreCase("stimmt"))
) {
RestApiServer.httpsNeedClientAuth = "false";
} else {
RestApiServer.httpsNeedClientAuth = "true";
}
}
/* HTTP Access (plaintext) */
if (useHttp == null || path == null || path.isEmpty() ||
(!useHttp.trim().equalsIgnoreCase("yes") && !useHttp.trim().equalsIgnoreCase("true") &&
!useHttp.trim().equalsIgnoreCase("yep") && !useHttp.trim().equalsIgnoreCase("ja") &&
!useHttp.trim().equalsIgnoreCase("stimmt")
)
) {
RestApiServer.useHttp = false;
} else {
RestApiServer.useHttp = true;
String port = configOptions.get("httpPort");
if (port != null && !port.isEmpty()) {
RestApiServer.httpPort = port.trim();
}
}
if (RestApiServer.useHttp && RestApiServer.useHttps && RestApiServer.httpPort.equals(RestApiServer.httpsPort)) {
logger.error("REST API's HTTP and HTTPS ports cannot be the same. Got " + RestApiServer.httpPort + " for both.");
throw new IllegalArgumentException("REST API's HTTP and HTTPS ports cannot be the same. Got " + RestApiServer.httpPort + " for both.");
}
if (!RestApiServer.useHttps) {
logger.warn("HTTPS disabled; HTTPS will not be used to connect to the REST API.");
} else {
if (RestApiServer.httpsNeedClientAuth.equals("true")) {
logger.warn("HTTPS enabled; Only trusted clients permitted. Allowing secure access to REST API on port {}.", RestApiServer.httpsPort);
} else {
logger.warn("HTTPS enabled; All clients permitted. Allowing secure access to REST API on port {}.", RestApiServer.httpsPort);
}
logger.info("HTTPS' SSL keystore/truststore path: {}, password: {}", RestApiServer.keyStore, RestApiServer.keyStorePassword);
}
if (!RestApiServer.useHttp) {
logger.warn("HTTP disabled; HTTP will not be used to connect to the REST API.");
} else {
logger.warn("HTTP enabled; Allowing unsecure access to REST API on port {}.", RestApiServer.httpPort);
}
}
@Override
public void startUp(FloodlightModuleContext Context) {
// no-op
}
}
\ No newline at end of file
......@@ -23,4 +23,11 @@ net.floodlightcontroller.core.internal.FloodlightProvider.openflowPort=6653
net.floodlightcontroller.core.internal.FloodlightProvider.role=ACTIVE
net.floodlightcontroller.core.internal.OFSwitchManager.keyStorePath=/path/to/your/keystore-file.jks
net.floodlightcontroller.core.internal.OFSwitchManager.keyStorePassword=your-keystore-password
net.floodlightcontroller.core.internal.OFSwitchManager.useSsl=NO
\ No newline at end of file
net.floodlightcontroller.core.internal.OFSwitchManager.useSsl=NO
net.floodlightcontroller.restserver.RestApiServer.keyStorePath=/path/to/your/keystore-file.jks
net.floodlightcontroller.restserver.RestApiServer.keyStorePassword=your-keystore-password
net.floodlightcontroller.restserver.RestApiServer.httpsNeedClientAuthentication=NO
net.floodlightcontroller.restserver.RestApiServer.useHttps=NO
net.floodlightcontroller.restserver.RestApiServer.useHttp=YES
net.floodlightcontroller.restserver.RestApiServer.httpsPort=8081
net.floodlightcontroller.restserver.RestApiServer.httpPort=8080
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment