From 030aee91fd3aec55a8940770181825f9f04a43aa Mon Sep 17 00:00:00 2001 From: Jorge Hernandez Date: Wed, 1 Aug 2018 16:18:25 -0500 Subject: [PATCH] generic jetty https server support jetty https support in constructor, or by using ".https" when creating an http server service. Change-Id: I94e8e3e4b93eb6b194657028c740b6781316c7da Issue-ID: POLICY-940 Signed-off-by: Jorge Hernandez --- .gitignore | 3 + .../http/server/HttpServletServerFactory.java | 50 ++++++++++--- .../http/server/internal/JettyJerseyServer.java | 11 +-- .../http/server/internal/JettyServletServer.java | 59 ++++++++++++++-- .../endpoints/http/server/test/HttpClientTest.java | 77 ++++++++++++++++++--- .../endpoints/http/server/test/HttpServerTest.java | 6 +- policy-endpoints/src/test/resources/keystore-test | Bin 0 -> 3895 bytes 7 files changed, 176 insertions(+), 30 deletions(-) create mode 100644 policy-endpoints/src/test/resources/keystore-test diff --git a/.gitignore b/.gitignore index ae515bd8..13d4a1eb 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ target bin .metadata/ +.idea/ +**/*.iml +*/logs/ integrity-audit/sql/generatedCreateIA.ddl integrity-audit/sql/generatedDropIA.ddl integrity-audit/sql/iaTest.mv.db diff --git a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/http/server/HttpServletServerFactory.java b/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/http/server/HttpServletServerFactory.java index f09893b2..c7d2b1bf 100644 --- a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/http/server/HttpServletServerFactory.java +++ b/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/http/server/HttpServletServerFactory.java @@ -37,9 +37,10 @@ import org.slf4j.LoggerFactory; public interface HttpServletServerFactory { /** - * builds an http server with support for servlets + * builds an http or https server with support for servlets * * @param name name + * @param https use secured http over tls connection * @param host binding host * @param port port * @param contextPath server base path @@ -48,9 +49,24 @@ public interface HttpServletServerFactory { * @return http server * @throws IllegalArgumentException when invalid parameters are provided */ - public HttpServletServer build(String name, String host, int port, String contextPath, boolean swagger, + HttpServletServer build(String name, boolean https, String host, int port, String contextPath, boolean swagger, boolean managed); + /** + * builds an http server with support for servlets + * + * @param name name + * @param host binding host + * @param port port + * @param contextPath server base path + * @param swagger enable swagger documentation + * @param managed is it managed by infrastructure + * @return http server + * @throws IllegalArgumentException when invalid parameters are provided + */ + HttpServletServer build(String name, String host, int port, String contextPath, boolean swagger, + boolean managed); + /** * list of http servers per properties * @@ -58,7 +74,7 @@ public interface HttpServletServerFactory { * @return list of http servers * @throws IllegalArgumentException when invalid parameters are provided */ - public List build(Properties properties); + List build(Properties properties); /** * gets a server based on the port @@ -66,26 +82,26 @@ public interface HttpServletServerFactory { * @param port port * @return http server */ - public HttpServletServer get(int port); + HttpServletServer get(int port); /** * provides an inventory of servers * * @return inventory of servers */ - public List inventory(); + List inventory(); /** * destroys server bound to a port * * @param port */ - public void destroy(int port); + void destroy(int port); /** * destroys the factory and therefore all servers */ - public void destroy(); + void destroy(); } @@ -107,14 +123,14 @@ class IndexedHttpServletServerFactory implements HttpServletServerFactory { protected HashMap servers = new HashMap<>(); @Override - public synchronized HttpServletServer build(String name, String host, int port, String contextPath, boolean swagger, + public synchronized HttpServletServer build(String name, boolean https, String host, int port, String contextPath, boolean swagger, boolean managed) { if (servers.containsKey(port)) { return servers.get(port); } - JettyJerseyServer server = new JettyJerseyServer(name, host, port, contextPath, swagger); + JettyJerseyServer server = new JettyJerseyServer(name, https, host, port, contextPath, swagger); if (managed) { servers.put(port, server); } @@ -122,6 +138,13 @@ class IndexedHttpServletServerFactory implements HttpServletServerFactory { return server; } + @Override + public synchronized HttpServletServer build(String name, String host, int port, String contextPath, + boolean swagger, boolean managed) { + return build(name, false, host, port, contextPath, swagger, managed); + } + + @Override public synchronized List build(Properties properties) { @@ -192,7 +215,14 @@ class IndexedHttpServletServerFactory implements HttpServletServerFactory { swagger = Boolean.parseBoolean(swaggerString); } - HttpServletServer service = build(serviceName, hostName, servicePort, contextUriPath, swagger, managed); + String httpsString = properties.getProperty(PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES + "." + + serviceName + PolicyEndPointProperties.PROPERTY_HTTP_HTTPS_SUFFIX); + boolean https = false; + if (httpsString != null && !httpsString.isEmpty()) { + https = Boolean.parseBoolean(httpsString); + } + + HttpServletServer service = build(serviceName, https, hostName, servicePort, contextUriPath, swagger, managed); if (userName != null && !userName.isEmpty() && password != null && !password.isEmpty()) { service.setBasicAuthentication(userName, password, authUriPath); } diff --git a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/http/server/internal/JettyJerseyServer.java b/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/http/server/internal/JettyJerseyServer.java index cd286927..9932d094 100644 --- a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/http/server/internal/JettyJerseyServer.java +++ b/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/http/server/internal/JettyJerseyServer.java @@ -104,6 +104,7 @@ public class JettyJerseyServer extends JettyServletServer { * Constructor * * @param name name + * @param https enable https? * @param host host server host * @param port port server port * @param swagger support swagger? @@ -111,19 +112,19 @@ public class JettyJerseyServer extends JettyServletServer { * * @throws IllegalArgumentException in invalid arguments are provided */ - public JettyJerseyServer(String name, String host, int port, String contextPath, boolean swagger) { + public JettyJerseyServer(String name, boolean https, String host, int port, String contextPath, boolean swagger) { - super(name, host, port, contextPath); + super(name, https, host, port, contextPath); if (swagger) { this.swaggerId = "swagger-" + this.port; - attachSwaggerServlet(); + attachSwaggerServlet(https); } } /** * attaches a swagger initialization servlet */ - protected void attachSwaggerServlet() { + protected void attachSwaggerServlet(boolean https) { ServletHolder swaggerServlet = context.addServlet(JerseyJaxrsConfig.class, "/"); @@ -133,7 +134,7 @@ public class JettyJerseyServer extends JettyServletServer { } swaggerServlet.setInitParameter(SWAGGER_API_BASEPATH, - "http://" + hostname + ":" + this.connector.getPort() + "/"); + ((https) ? "https://" : "http://") + hostname + ":" + this.connector.getPort() + "/"); swaggerServlet.setInitParameter(SWAGGER_CONTEXT_ID, swaggerId); swaggerServlet.setInitParameter(SWAGGER_SCANNER_ID, swaggerId); swaggerServlet.setInitParameter(SWAGGER_PRETTY_PRINT, "true"); diff --git a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/http/server/internal/JettyServletServer.java b/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/http/server/internal/JettyServletServer.java index 97166ec7..a4cc9b5f 100644 --- a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/http/server/internal/JettyServletServer.java +++ b/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/http/server/internal/JettyServletServer.java @@ -2,7 +2,7 @@ * ============LICENSE_START======================================================= * ONAP * ================================================================================ - * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * Copyright (C) 2017-2018 AT&T Intellectual Property. All rights reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,12 +26,16 @@ import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.ConstraintSecurityHandler; import org.eclipse.jetty.security.HashLoginService; import org.eclipse.jetty.security.authentication.BasicAuthenticator; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.SecureRequestCustomizer; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.Slf4jRequestLog; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.util.security.Constraint; import org.eclipse.jetty.util.security.Credential; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.onap.policy.common.endpoints.http.server.HttpServletServer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,6 +45,14 @@ import org.slf4j.LoggerFactory; */ public abstract class JettyServletServer implements HttpServletServer, Runnable { + /** + * Keystore/Truststore system property names + */ + public static final String SYSTEM_KEYSTORE_PROPERTY_NAME = "javax.net.ssl.keyStore"; + public static final String SYSTEM_KEYSTORE_PASSWORD_PROPERTY_NAME = "javax.net.ssl.keyStorePassword"; + public static final String SYSTEM_TRUSTSTORE_PROPERTY_NAME = "javax.net.ssl.trustStore"; + public static final String SYSTEM_TRUSTSTORE_PASSWORD_PROPERTY_NAME = "javax.net.ssl.trustStorePassword"; + /** * Logger */ @@ -111,7 +123,7 @@ public abstract class JettyServletServer implements HttpServletServer, Runnable * * @throws IllegalArgumentException if invalid parameters are passed in */ - public JettyServletServer(String name, String host, int port, String contextPath) { + public JettyServletServer(String name, boolean https, String host, int port, String contextPath) { String srvName = name; String srvHost = host; String ctxtPath = contextPath; @@ -120,7 +132,7 @@ public abstract class JettyServletServer implements HttpServletServer, Runnable srvName = "http-" + port; } - if (port <= 0 && port >= 65535) { + if (port <= 0 || port >= 65535) { throw new IllegalArgumentException("Invalid Port provided: " + port); } @@ -145,7 +157,11 @@ public abstract class JettyServletServer implements HttpServletServer, Runnable this.jettyServer = new Server(); this.jettyServer.setRequestLog(new Slf4jRequestLog()); - this.connector = new ServerConnector(this.jettyServer); + if (https) + this.connector = httpsConnector(); + else + this.connector = httpConnector(); + this.connector.setName(srvName); this.connector.setReuseAddress(true); this.connector.setPort(port); @@ -155,6 +171,41 @@ public abstract class JettyServletServer implements HttpServletServer, Runnable this.jettyServer.setHandler(context); } + public JettyServletServer(String name, String host, int port, String contextPath) { + this(name, false, host, port, contextPath); + } + + public ServerConnector httpsConnector() { + SslContextFactory sslContextFactory = new SslContextFactory(); + + String keyStore = System.getProperty(SYSTEM_KEYSTORE_PROPERTY_NAME); + if (keyStore != null) { + sslContextFactory.setKeyStorePath(keyStore); + + String ksPassword = System.getProperty(SYSTEM_KEYSTORE_PASSWORD_PROPERTY_NAME); + if (ksPassword != null) + sslContextFactory.setKeyStorePassword(ksPassword); + } + + String trustStore = System.getProperty(SYSTEM_TRUSTSTORE_PROPERTY_NAME); + if (trustStore != null) { + sslContextFactory.setTrustStorePath(trustStore); + + String tsPassword = System.getProperty(SYSTEM_TRUSTSTORE_PASSWORD_PROPERTY_NAME); + if (tsPassword != null) + sslContextFactory.setTrustStorePassword(tsPassword); + } + + HttpConfiguration https = new HttpConfiguration(); + https.addCustomizer(new SecureRequestCustomizer()); + + return new ServerConnector(jettyServer, sslContextFactory, new HttpConnectionFactory(https)); + } + + public ServerConnector httpConnector() { + return new ServerConnector(this.jettyServer); + } + @Override public void setBasicAuthentication(String user, String password, String servletPath) { String srvltPath = servletPath; diff --git a/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/HttpClientTest.java b/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/HttpClientTest.java index 08399e91..6ec9bc21 100644 --- a/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/HttpClientTest.java +++ b/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/HttpClientTest.java @@ -1,8 +1,8 @@ /*- * ============LICENSE_START======================================================= - * policy-endpoints + * ONAP * ================================================================================ - * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * Copyright (C) 2017-2018 AT&T Intellectual Property. All rights reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ package org.onap.policy.common.endpoints.http.server.test; import static org.junit.Assert.assertTrue; import java.io.IOException; +import java.util.HashMap; import java.util.List; import java.util.Properties; @@ -33,20 +34,22 @@ import org.junit.BeforeClass; import org.junit.Test; import org.onap.policy.common.endpoints.http.client.HttpClient; import org.onap.policy.common.endpoints.http.server.HttpServletServer; +import org.onap.policy.common.endpoints.http.server.internal.JettyJerseyServer; import org.onap.policy.common.endpoints.properties.PolicyEndPointProperties; import org.onap.policy.common.utils.network.NetworkUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class HttpClientTest { + private static final Logger logger = LoggerFactory.getLogger(HttpClientTest.class); - private static Logger logger = LoggerFactory.getLogger(HttpClientTest.class); + private static final HashMap savedValuesMap = new HashMap<>(); @BeforeClass public static void setUp() throws InterruptedException, IOException { logger.info("-- setup() --"); - /* echo server */ + /* echo server - http + no auth */ final HttpServletServer echoServerNoAuth = HttpServletServer.factory.build("echo", "localhost", 6666, "/", false, true); @@ -57,10 +60,38 @@ public class HttpClientTest { throw new IllegalStateException("cannot connect to port " + echoServerNoAuth.getPort()); } - /* no auth echo server */ + String keyStoreSystemProperty = System.getProperty(JettyJerseyServer.SYSTEM_KEYSTORE_PROPERTY_NAME); + if (keyStoreSystemProperty != null) { + savedValuesMap.put(JettyJerseyServer.SYSTEM_KEYSTORE_PROPERTY_NAME, keyStoreSystemProperty); + } + + String keyStorePasswordSystemProperty = System.getProperty(JettyJerseyServer.SYSTEM_KEYSTORE_PASSWORD_PROPERTY_NAME); + if (keyStorePasswordSystemProperty != null) { + savedValuesMap.put(JettyJerseyServer.SYSTEM_KEYSTORE_PASSWORD_PROPERTY_NAME, keyStorePasswordSystemProperty); + } + + String trustStoreSystemProperty = System.getProperty(JettyJerseyServer.SYSTEM_TRUSTSTORE_PROPERTY_NAME); + if (trustStoreSystemProperty != null) { + savedValuesMap + .put(JettyJerseyServer.SYSTEM_TRUSTSTORE_PROPERTY_NAME, trustStoreSystemProperty); + } + + String trustStorePasswordSystemProperty = System.getProperty(JettyJerseyServer.SYSTEM_TRUSTSTORE_PASSWORD_PROPERTY_NAME); + if (trustStorePasswordSystemProperty != null) { + savedValuesMap + .put(JettyJerseyServer.SYSTEM_TRUSTSTORE_PASSWORD_PROPERTY_NAME, trustStorePasswordSystemProperty); + } + + System.setProperty(JettyJerseyServer.SYSTEM_KEYSTORE_PROPERTY_NAME, "src/test/resources/keystore-test"); + System.setProperty(JettyJerseyServer.SYSTEM_KEYSTORE_PASSWORD_PROPERTY_NAME, "kstest"); + + System.setProperty(JettyJerseyServer.SYSTEM_TRUSTSTORE_PROPERTY_NAME, "src/test/resources/keystore-test"); + System.setProperty(JettyJerseyServer.SYSTEM_TRUSTSTORE_PASSWORD_PROPERTY_NAME, "kstest"); + + /* echo server - https + basic auth */ final HttpServletServer echoServerAuth = - HttpServletServer.factory.build("echo", "localhost", 6667, "/", false, true); + HttpServletServer.factory.build("echo", true, "localhost", 6667, "/", false, true); echoServerAuth.setBasicAuthentication("x", "y", null); echoServerAuth.addServletPackage("/*", HttpClientTest.class.getPackage().getName()); echoServerAuth.waitedStart(5000); @@ -76,6 +107,36 @@ public class HttpClientTest { HttpServletServer.factory.destroy(); HttpClient.factory.destroy(); + + if (savedValuesMap.containsKey(JettyJerseyServer.SYSTEM_KEYSTORE_PROPERTY_NAME)) { + System.setProperty(JettyJerseyServer.SYSTEM_KEYSTORE_PROPERTY_NAME, savedValuesMap.get(JettyJerseyServer.SYSTEM_KEYSTORE_PROPERTY_NAME)); + savedValuesMap.remove(JettyJerseyServer.SYSTEM_KEYSTORE_PROPERTY_NAME); + } else { + System.clearProperty(JettyJerseyServer.SYSTEM_KEYSTORE_PROPERTY_NAME); + } + + if (savedValuesMap.containsKey(JettyJerseyServer.SYSTEM_KEYSTORE_PASSWORD_PROPERTY_NAME)) { + System.setProperty(JettyJerseyServer.SYSTEM_KEYSTORE_PASSWORD_PROPERTY_NAME, savedValuesMap.get(JettyJerseyServer.SYSTEM_KEYSTORE_PASSWORD_PROPERTY_NAME)); + savedValuesMap.remove(JettyJerseyServer.SYSTEM_KEYSTORE_PASSWORD_PROPERTY_NAME); + } else { + System.clearProperty(JettyJerseyServer.SYSTEM_KEYSTORE_PASSWORD_PROPERTY_NAME); + } + + if (savedValuesMap.containsKey(JettyJerseyServer.SYSTEM_TRUSTSTORE_PROPERTY_NAME)) { + System.setProperty(JettyJerseyServer.SYSTEM_TRUSTSTORE_PROPERTY_NAME, savedValuesMap.get(JettyJerseyServer.SYSTEM_TRUSTSTORE_PROPERTY_NAME)); + savedValuesMap.remove(JettyJerseyServer.SYSTEM_TRUSTSTORE_PROPERTY_NAME); + } else { + System.clearProperty(JettyJerseyServer.SYSTEM_TRUSTSTORE_PROPERTY_NAME); + } + + if (savedValuesMap.containsKey(JettyJerseyServer.SYSTEM_TRUSTSTORE_PASSWORD_PROPERTY_NAME)) { + System.setProperty(JettyJerseyServer.SYSTEM_TRUSTSTORE_PASSWORD_PROPERTY_NAME, savedValuesMap.get(JettyJerseyServer.SYSTEM_TRUSTSTORE_PASSWORD_PROPERTY_NAME)); + savedValuesMap.remove(JettyJerseyServer.SYSTEM_TRUSTSTORE_PASSWORD_PROPERTY_NAME); + } else { + System.clearProperty(JettyJerseyServer.SYSTEM_TRUSTSTORE_PASSWORD_PROPERTY_NAME); + } + + } @Test @@ -95,7 +156,7 @@ public class HttpClientTest { public void testHttpAuthClient() throws Exception { logger.info("-- testHttpAuthClient() --"); - final HttpClient client = HttpClient.factory.build("testHttpAuthClient", false, false, "localhost", 6667, + final HttpClient client = HttpClient.factory.build("testHttpAuthClient", true, true,"localhost", 6667, "junit/echo", "x", "y", true); final Response response = client.get("hello"); final String body = HttpClient.getBody(response, String.class); @@ -108,7 +169,7 @@ public class HttpClientTest { public void testHttpAuthClient401() throws Exception { logger.info("-- testHttpAuthClient401() --"); - final HttpClient client = HttpClient.factory.build("testHttpAuthClient401", false, false, "localhost", 6667, + final HttpClient client = HttpClient.factory.build("testHttpAuthClient401", true, true, "localhost", 6667, "junit/echo", null, null, true); final Response response = client.get("hello"); assertTrue(response.getStatus() == 401); diff --git a/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/HttpServerTest.java b/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/HttpServerTest.java index b6f0c0e8..0db6cfe1 100644 --- a/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/HttpServerTest.java +++ b/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/HttpServerTest.java @@ -1,8 +1,8 @@ /*- * ============LICENSE_START======================================================= - * policy-endpoints + * ONAP * ================================================================================ - * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * Copyright (C) 2017-2018 AT&T Intellectual Property. All rights reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -77,7 +77,7 @@ public class HttpServerTest { public void testMultipleServers() throws Exception { logger.info("-- testMultipleServers() --"); - HttpServletServer server1 = HttpServletServer.factory.build("echo-1", "localhost", 5688, "/", true, true); + HttpServletServer server1 = HttpServletServer.factory.build("echo-1", false,"localhost", 5688, "/", true, true); server1.addServletPackage("/*", this.getClass().getPackage().getName()); server1.waitedStart(5000); diff --git a/policy-endpoints/src/test/resources/keystore-test b/policy-endpoints/src/test/resources/keystore-test new file mode 100644 index 0000000000000000000000000000000000000000..5820e0f0ce4be85553e170fa63d2669b757b44fa GIT binary patch literal 3895 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 TB{{+@Z#{s98>97~Dxv-bP#hX; literal 0 HcmV?d00001 -- 2.16.6