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 @@ ...@@ -61,11 +61,13 @@
<include name="jackson-dataformat-yaml-2.1.4.jar"/> <include name="jackson-dataformat-yaml-2.1.4.jar"/>
<include name="jackson-dataformat-csv-2.1.4.jar"/> <include name="jackson-dataformat-csv-2.1.4.jar"/>
<include name="slf4j-api-1.6.4.jar"/> <include name="slf4j-api-1.6.4.jar"/>
<include name="org.restlet-2.2M3.jar"/> <include name="org.restlet.jar"/>
<include name="org.restlet.ext.jackson-2.2M3.jar"/> <include name="org.restlet.ext.jackson.jar"/>
<include name="org.restlet.ext.simple-2.2M3.jar"/> <include name="org.restlet.ext.simple.jar"/>
<include name="org.restlet.ext.slf4j-2.2M3.jar"/> <include name="org.restlet.ext.slf4j.jar"/>
<include name="simple-5.1.1.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="netty-3.10.0.Final.jar"/>
<include name="args4j-2.0.16.jar"/> <include name="args4j-2.0.16.jar"/>
<include name="concurrentlinkedhashmap-lru-1.2.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; ...@@ -28,6 +28,8 @@ import org.restlet.Context;
import org.restlet.Request; import org.restlet.Request;
import org.restlet.Response; import org.restlet.Response;
import org.restlet.Restlet; import org.restlet.Restlet;
import org.restlet.Server;
import org.restlet.data.Parameter;
import org.restlet.data.Protocol; import org.restlet.data.Protocol;
import org.restlet.data.Reference; import org.restlet.data.Reference;
import org.restlet.data.Status; import org.restlet.data.Status;
...@@ -37,6 +39,7 @@ import org.restlet.routing.Filter; ...@@ -37,6 +39,7 @@ import org.restlet.routing.Filter;
import org.restlet.routing.Router; import org.restlet.routing.Router;
import org.restlet.routing.Template; import org.restlet.routing.Template;
import org.restlet.service.StatusService; import org.restlet.service.StatusService;
import org.restlet.util.Series;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -46,179 +49,288 @@ import net.floodlightcontroller.core.module.FloodlightModuleException; ...@@ -46,179 +49,288 @@ import net.floodlightcontroller.core.module.FloodlightModuleException;
import net.floodlightcontroller.core.module.IFloodlightModule; import net.floodlightcontroller.core.module.IFloodlightModule;
import net.floodlightcontroller.core.module.IFloodlightService; import net.floodlightcontroller.core.module.IFloodlightService;
public class RestApiServer public class RestApiServer implements IFloodlightModule, IRestApiService {
implements IFloodlightModule, IRestApiService { protected static Logger logger = LoggerFactory.getLogger(RestApiServer.class);
protected static Logger logger = LoggerFactory.getLogger(RestApiServer.class); protected List<RestletRoutable> restlets;
protected List<RestletRoutable> restlets; protected FloodlightModuleContext fmlContext;
protected FloodlightModuleContext fmlContext; protected String restHost = null;
protected String restHost = null;
protected int restPort = 8080; private static String keyStorePassword;
private static String keyStore;
// ***********
// Application private static String httpsNeedClientAuth = "true";
// ***********
private static boolean useHttps = false;
protected class RestApplication extends Application { private static boolean useHttp = false;
protected Context context;
private static String httpsPort;
public RestApplication() { private static String httpPort;
super(new Context());
this.context = getContext();
} // ***********
// Application
@Override // ***********
public Restlet createInboundRoot() {
Router baseRouter = new Router(context); protected class RestApplication extends Application {
baseRouter.setDefaultMatchingMode(Template.MODE_STARTS_WITH); protected Context context;
for (RestletRoutable rr : restlets) {
baseRouter.attach(rr.basePath(), rr.getRestlet(context)); public RestApplication() {
} super(new Context());
this.context = getContext();
Filter slashFilter = new Filter() { }
@Override
protected int beforeHandle(Request request, Response response) { @Override
Reference ref = request.getResourceRef(); public Restlet createInboundRoot() {
String originalPath = ref.getPath(); Router baseRouter = new Router(context);
if (originalPath.contains("//")) baseRouter.setDefaultMatchingMode(Template.MODE_STARTS_WITH);
{ for (RestletRoutable rr : restlets) {
String newPath = originalPath.replaceAll("/+", "/"); baseRouter.attach(rr.basePath(), rr.getRestlet(context));
ref.setPath(newPath); }
}
return Filter.CONTINUE; Filter slashFilter = new Filter() {
} @Override
protected int beforeHandle(Request request, Response response) {
}; Reference ref = request.getResourceRef();
slashFilter.setNext(baseRouter); String originalPath = ref.getPath();
if (originalPath.contains("//"))
return slashFilter; {
} String newPath = originalPath.replaceAll("/+", "/");
ref.setPath(newPath);
public void run(FloodlightModuleContext fmlContext, String restHost, int restPort) { }
setStatusService(new StatusService() { return Filter.CONTINUE;
@Override }
public Representation getRepresentation(Status status,
Request request, };
Response response) { slashFilter.setNext(baseRouter);
return new JacksonRepresentation<Status>(status);
} return slashFilter;
}); }
// Add everything in the module context to the rest public void run(FloodlightModuleContext fmlContext, String restHost) {
for (Class<? extends IFloodlightService> s : fmlContext.getAllServices()) { setStatusService(new StatusService() {
if (logger.isTraceEnabled()) { @Override
logger.trace("Adding {} for service {} into context", public Representation getRepresentation(Status status,
s.getCanonicalName(), fmlContext.getServiceImpl(s)); Request request,
} Response response) {
context.getAttributes().put(s.getCanonicalName(), return new JacksonRepresentation<Status>(status);
fmlContext.getServiceImpl(s)); }
} });
/* // Add everything in the module context to the rest
* Specifically add the FML for use by the REST API's /wm/core/modules/... for (Class<? extends IFloodlightService> s : fmlContext.getAllServices()) {
*/ if (logger.isTraceEnabled()) {
context.getAttributes().put(fmlContext.getModuleLoader().getClass().getCanonicalName(), fmlContext.getModuleLoader()); logger.trace("Adding {} for service {} into context",
s.getCanonicalName(), fmlContext.getServiceImpl(s));
// Start listening for REST requests }
try { context.getAttributes().put(s.getCanonicalName(),
final Component component = new Component(); fmlContext.getServiceImpl(s));
if (restHost == null) { }
component.getServers().add(Protocol.HTTP, restPort);
} else { /*
component.getServers().add(Protocol.HTTP, restHost, restPort); * Specifically add the FML for use by the REST API's /wm/core/modules/...
} */
component.getClients().add(Protocol.CLAP); context.getAttributes().put(fmlContext.getModuleLoader().getClass().getCanonicalName(), fmlContext.getModuleLoader());
component.getDefaultHost().attach(this);
component.start(); /* Start listening for REST requests */
} catch (Exception e) { try {
throw new RuntimeException(e); final Component component = new Component();
}
} if (RestApiServer.useHttps) {
} Server server;
// *************** if (restHost == null) {
// IRestApiService server = component.getServers().add(Protocol.HTTPS, Integer.valueOf(RestApiServer.httpsPort));
// *************** } else {
server = component.getServers().add(Protocol.HTTPS, restHost, Integer.valueOf(RestApiServer.httpsPort));
@Override }
public void addRestletRoutable(RestletRoutable routable) {
restlets.add(routable); Series<Parameter> parameters = server.getContext().getParameters();
} //parameters.add("sslContextFactory", "org.restlet.ext.jsslutils.PkixSslContextFactory");
parameters.add("sslContextFactory", "org.restlet.engine.ssl.DefaultSslContextFactory");
@Override
public void run() { parameters.add("keystorePath", RestApiServer.keyStore);
if (logger.isDebugEnabled()) { parameters.add("keystorePassword", RestApiServer.keyStorePassword);
StringBuffer sb = new StringBuffer(); parameters.add("keyPassword", RestApiServer.keyStorePassword);
sb.append("REST API routables: "); parameters.add("keystoreType", "JKS");
for (RestletRoutable routable : restlets) {
sb.append(routable.getClass().getSimpleName()); parameters.add("truststorePath", RestApiServer.keyStore);
sb.append(" ("); parameters.add("truststorePassword", RestApiServer.keyStorePassword);
sb.append(routable.basePath()); parameters.add("trustPassword", RestApiServer.keyStorePassword);
sb.append("), "); parameters.add("truststoreType", "JKS");
}
logger.debug(sb.toString()); parameters.add("needClientAuthentication", RestApiServer.httpsNeedClientAuth);
} }
RestApplication restApp = new RestApplication(); if (RestApiServer.useHttp) {
restApp.run(fmlContext, restHost, restPort); if (restHost == null) {
} component.getServers().add(Protocol.HTTP, Integer.valueOf(RestApiServer.httpPort));
} else {
// ***************** component.getServers().add(Protocol.HTTP, restHost, Integer.valueOf(RestApiServer.httpPort));
// IFloodlightModule }
// ***************** }
@Override component.getClients().add(Protocol.CLAP);
public Collection<Class<? extends IFloodlightService>> getModuleServices() { component.getDefaultHost().attach(this);
Collection<Class<? extends IFloodlightService>> services = component.start();
new ArrayList<Class<? extends IFloodlightService>>(1); } catch (Exception e) {
services.add(IRestApiService.class); throw new RuntimeException(e);
return services; }
} }
}
@Override
public Map<Class<? extends IFloodlightService>, IFloodlightService> // ***************
getServiceImpls() { // IRestApiService
Map<Class<? extends IFloodlightService>, // ***************
IFloodlightService> m =
new HashMap<Class<? extends IFloodlightService>, @Override
IFloodlightService>(); public void addRestletRoutable(RestletRoutable routable) {
m.put(IRestApiService.class, this); restlets.add(routable);
return m; }
}
@Override
@Override public void run() {
public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { if (logger.isDebugEnabled()) {
// We don't have any StringBuffer sb = new StringBuffer();
return null; sb.append("REST API routables: ");
} for (RestletRoutable routable : restlets) {
sb.append(routable.getClass().getSimpleName());
@Override sb.append(" (");
public void init(FloodlightModuleContext context) sb.append(routable.basePath());
throws FloodlightModuleException { sb.append("), ");
// This has to be done here since we don't know what order the }
// startUp methods will be called logger.debug(sb.toString());
this.restlets = new ArrayList<RestletRoutable>(); }
this.fmlContext = context;
RestApplication restApp = new RestApplication();
// read our config options restApp.run(fmlContext, restHost);
Map<String, String> configOptions = context.getConfigParams(this); }
restHost = configOptions.get("host");
if (restHost == null) { // *****************
Map<String, String> providerConfigOptions = context.getConfigParams( // IFloodlightModule
FloodlightProvider.class); // *****************
restHost = providerConfigOptions.get("openflowhost");
} @Override
if (restHost != null) { public Collection<Class<? extends IFloodlightService>> getModuleServices() {
logger.debug("REST host set to {}", restHost); Collection<Class<? extends IFloodlightService>> services =
} new ArrayList<Class<? extends IFloodlightService>>(1);
String port = configOptions.get("port"); services.add(IRestApiService.class);
if (port != null) { return services;
restPort = Integer.parseInt(port); }
}
logger.debug("REST port set to {}", restPort); @Override
} public Map<Class<? extends IFloodlightService>, IFloodlightService>
getServiceImpls() {
@Override Map<Class<? extends IFloodlightService>,
public void startUp(FloodlightModuleContext Context) { IFloodlightService> m =
// no-op 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 ...@@ -23,4 +23,11 @@ net.floodlightcontroller.core.internal.FloodlightProvider.openflowPort=6653
net.floodlightcontroller.core.internal.FloodlightProvider.role=ACTIVE net.floodlightcontroller.core.internal.FloodlightProvider.role=ACTIVE
net.floodlightcontroller.core.internal.OFSwitchManager.keyStorePath=/path/to/your/keystore-file.jks 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.keyStorePassword=your-keystore-password
net.floodlightcontroller.core.internal.OFSwitchManager.useSsl=NO net.floodlightcontroller.core.internal.OFSwitchManager.useSsl=NO
\ No newline at end of file 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