diff --git a/build.xml b/build.xml index ab6de065d6c7e668d76add3907dbd8aa2b13886a..2b4989e597f2b98868c8c16d533547f72b194a92 100644 --- a/build.xml +++ b/build.xml @@ -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"/> diff --git a/lib/org.jsslutils.jar b/lib/org.jsslutils.jar new file mode 100644 index 0000000000000000000000000000000000000000..2c87c5e3e9bc6126de5626f0d270faee7925e421 Binary files /dev/null and b/lib/org.jsslutils.jar differ diff --git a/lib/org.restlet-2.2M3.jar b/lib/org.restlet-2.2M3.jar deleted file mode 100644 index a23f783d8312bd2a9a8728f96c3379454fb7a1ac..0000000000000000000000000000000000000000 Binary files a/lib/org.restlet-2.2M3.jar and /dev/null differ diff --git a/lib/org.restlet.ext.jackson-2.2M3.jar b/lib/org.restlet.ext.jackson-2.2M3.jar deleted file mode 100644 index f4f95781d05a8af3e1e96fb0647e148f7a0a7f6a..0000000000000000000000000000000000000000 Binary files a/lib/org.restlet.ext.jackson-2.2M3.jar and /dev/null differ diff --git a/lib/org.restlet.ext.jackson.jar b/lib/org.restlet.ext.jackson.jar new file mode 100644 index 0000000000000000000000000000000000000000..01d9340b15defb56865407bf77bbfbfa32f0db6f Binary files /dev/null and b/lib/org.restlet.ext.jackson.jar differ diff --git a/lib/org.restlet.ext.jsslutils.jar b/lib/org.restlet.ext.jsslutils.jar new file mode 100644 index 0000000000000000000000000000000000000000..491817334fc8f960053c2c5ff506af7e778c68ab Binary files /dev/null and b/lib/org.restlet.ext.jsslutils.jar differ diff --git a/lib/org.restlet.ext.simple-2.2M3.jar b/lib/org.restlet.ext.simple-2.2M3.jar deleted file mode 100644 index c870c7cf50c075b1daf0be3db1a6fb8fe231d948..0000000000000000000000000000000000000000 Binary files a/lib/org.restlet.ext.simple-2.2M3.jar and /dev/null differ diff --git a/lib/org.restlet.ext.simple.jar b/lib/org.restlet.ext.simple.jar new file mode 100644 index 0000000000000000000000000000000000000000..6f23e97c2d980291c38a5ed57d189d9e94523cc2 Binary files /dev/null and b/lib/org.restlet.ext.simple.jar differ diff --git a/lib/org.restlet.ext.slf4j-2.2M3.jar b/lib/org.restlet.ext.slf4j-2.2M3.jar deleted file mode 100644 index 83eed674e2fde6deed5e67593ff909ba0001fc29..0000000000000000000000000000000000000000 Binary files a/lib/org.restlet.ext.slf4j-2.2M3.jar and /dev/null differ diff --git a/lib/org.restlet.ext.slf4j.jar b/lib/org.restlet.ext.slf4j.jar new file mode 100644 index 0000000000000000000000000000000000000000..5b3770b841d67ed30d1bd1456017e86250f807bb Binary files /dev/null and b/lib/org.restlet.ext.slf4j.jar differ diff --git a/lib/org.restlet.jar b/lib/org.restlet.jar new file mode 100644 index 0000000000000000000000000000000000000000..9a31169fb326c498f462068c01c4d837d49dc773 Binary files /dev/null and b/lib/org.restlet.jar differ diff --git a/lib/org.simpleframework.jar b/lib/org.simpleframework.jar new file mode 100644 index 0000000000000000000000000000000000000000..04d5c454f557f28b9ce2073c0fb73f51232be82c Binary files /dev/null and b/lib/org.simpleframework.jar differ diff --git a/lib/simple-5.1.1.jar b/lib/simple-5.1.1.jar deleted file mode 100644 index b536f4098086a35ccecd50035c728b02b5e11cfb..0000000000000000000000000000000000000000 Binary files a/lib/simple-5.1.1.jar and /dev/null differ diff --git a/src/main/java/net/floodlightcontroller/restserver/RestApiServer.java b/src/main/java/net/floodlightcontroller/restserver/RestApiServer.java index 307577161daed30e6eca1f52eff040a758485911..f257df544ec9fd2e532457e6b58262c283344b78 100644 --- a/src/main/java/net/floodlightcontroller/restserver/RestApiServer.java +++ b/src/main/java/net/floodlightcontroller/restserver/RestApiServer.java @@ -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 diff --git a/src/main/resources/floodlightdefault.properties b/src/main/resources/floodlightdefault.properties index cf8a95612319a3c83eb29fca61163e568b39bf38..6e6daf828e686c224f766f352498c07cfbca150e 100644 --- a/src/main/resources/floodlightdefault.properties +++ b/src/main/resources/floodlightdefault.properties @@ -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