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

Merge pull request #504 from rizard/v1.0

HTTPS Support for the REST API
parents b09f60c5 2dc31089
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