GEODE-1570: add a test to verify rest security with SSL.
Project: http://git-wip-us.apache.org/repos/asf/incubator-geode/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-geode/commit/a515ee06 Tree: http://git-wip-us.apache.org/repos/asf/incubator-geode/tree/a515ee06 Diff: http://git-wip-us.apache.org/repos/asf/incubator-geode/diff/a515ee06 Branch: refs/heads/feature/GEODE-1930 Commit: a515ee06deeca3adf0ba053883a488787267ebcb Parents: 08bbbe2 Author: Jinmei Liao <jil...@pivotal.io> Authored: Mon Nov 7 18:10:52 2016 -0800 Committer: Jinmei Liao <jil...@pivotal.io> Committed: Wed Nov 9 08:11:07 2016 -0800 ---------------------------------------------------------------------- .../rest/internal/web/GeodeRestClient.java | 154 +++++++++++-------- .../internal/web/RestSecurityWithSSLTest.java | 85 ++++++++++ .../src/test/resources/ssl/trusted.keystore | Bin 0 -> 2241 bytes .../SecurityClusterConfigDUnitTest.java | 32 ++-- .../test/dunit/rules/LocatorStarterRule.java | 14 ++ 5 files changed, 205 insertions(+), 80 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/a515ee06/geode-assembly/src/test/java/org/apache/geode/rest/internal/web/GeodeRestClient.java ---------------------------------------------------------------------- diff --git a/geode-assembly/src/test/java/org/apache/geode/rest/internal/web/GeodeRestClient.java b/geode-assembly/src/test/java/org/apache/geode/rest/internal/web/GeodeRestClient.java index 071b95c..4510e20 100644 --- a/geode-assembly/src/test/java/org/apache/geode/rest/internal/web/GeodeRestClient.java +++ b/geode-assembly/src/test/java/org/apache/geode/rest/internal/web/GeodeRestClient.java @@ -15,13 +15,10 @@ package org.apache.geode.rest.internal.web; -import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.AuthCache; -import org.apache.http.client.ClientProtocolException; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; @@ -30,46 +27,85 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.entity.StringEntity; -import org.apache.http.impl.auth.BasicScheme; -import org.apache.http.impl.client.BasicAuthCache; import org.apache.http.impl.client.BasicCredentialsProvider; -import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; -import org.junit.Assert; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStream; import java.io.InputStreamReader; -import java.net.MalformedURLException; import java.nio.charset.StandardCharsets; +import java.security.KeyStore; +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; public class GeodeRestClient { - - public final static String PROTOCOL = "http"; public final static String CONTEXT = "/geode/v1"; private int restPort = 0; private String bindAddress = null; + private String protocol = "http"; + private boolean useHttps = false; + private KeyStore keyStore; public GeodeRestClient(String bindAddress, int restPort) { this.bindAddress = bindAddress; this.restPort = restPort; } - public HttpResponse doHEAD(String query, String username, String password) - throws MalformedURLException { + public GeodeRestClient(String bindAddress, int restPort, boolean useHttps) { + if (useHttps) { + this.protocol = "https"; + this.useHttps = true; + } else { + this.protocol = "http"; + this.useHttps = false; + } + this.bindAddress = bindAddress; + this.restPort = restPort; + } + + public static String getContentType(HttpResponse response) { + return response.getEntity().getContentType().getValue(); + } + + /** + * Retrieve the status code of the HttpResponse + * + * @param response The HttpResponse message received from the server + * @return a numeric value + */ + public static int getCode(HttpResponse response) { + return response.getStatusLine().getStatusCode(); + } + + public static JSONObject getJsonObject(HttpResponse response) throws IOException, JSONException { + JSONTokener tokener = new JSONTokener(new InputStreamReader(response.getEntity().getContent())); + return new JSONObject(tokener); + } + + public static JSONArray getJsonArray(HttpResponse response) throws IOException, JSONException { + JSONTokener tokener = new JSONTokener(new InputStreamReader(response.getEntity().getContent())); + return new JSONArray(tokener); + } + + public HttpResponse doHEAD(String query, String username, String password) throws Exception { HttpHead httpHead = new HttpHead(CONTEXT + query); return doRequest(httpHead, username, password); } public HttpResponse doPost(String query, String username, String password, String body) - throws MalformedURLException { + throws Exception { HttpPost httpPost = new HttpPost(CONTEXT + query); httpPost.addHeader("content-type", "application/json"); httpPost.setEntity(new StringEntity(body, StandardCharsets.UTF_8)); @@ -77,83 +113,69 @@ public class GeodeRestClient { } public HttpResponse doPut(String query, String username, String password, String body) - throws MalformedURLException { + throws Exception { HttpPut httpPut = new HttpPut(CONTEXT + query); httpPut.addHeader("content-type", "application/json"); httpPut.setEntity(new StringEntity(body, StandardCharsets.UTF_8)); return doRequest(httpPut, username, password); } - public HttpResponse doGet(String uri, String username, String password) - throws MalformedURLException { + public HttpResponse doGet(String uri, String username, String password) throws Exception { HttpGet getRequest = new HttpGet(CONTEXT + uri); return doRequest(getRequest, username, password); } - public HttpResponse doGetRequest(String url) throws MalformedURLException { + public HttpResponse doGetRequest(String url) throws Exception { HttpGet getRequest = new HttpGet(url); return doRequest(getRequest, null, null); } - - public HttpResponse doDelete(String uri, String username, String password) - throws MalformedURLException { + public HttpResponse doDelete(String uri, String username, String password) throws Exception { HttpDelete httpDelete = new HttpDelete(CONTEXT + uri); return doRequest(httpDelete, username, password); } - public static String getContentType(HttpResponse response) { - return response.getEntity().getContentType().getValue(); - } - - /** - * Retrieve the status code of the HttpResponse - * - * @param response The HttpResponse message received from the server - * - * @return a numeric value - */ - public static int getCode(HttpResponse response) { - return response.getStatusLine().getStatusCode(); - } - - public static JSONObject getJsonObject(HttpResponse response) throws IOException, JSONException { - JSONTokener tokener = new JSONTokener(new InputStreamReader(response.getEntity().getContent())); - return new JSONObject(tokener); - } - - public static JSONArray getJsonArray(HttpResponse response) throws IOException, JSONException { - JSONTokener tokener = new JSONTokener(new InputStreamReader(response.getEntity().getContent())); - return new JSONArray(tokener); - } - public HttpResponse doRequest(HttpRequestBase request, String username, String password) - throws MalformedURLException { - HttpHost targetHost = new HttpHost(bindAddress, restPort, PROTOCOL); - CloseableHttpClient httpclient = HttpClients.custom().build(); + throws Exception { + HttpHost targetHost = new HttpHost(bindAddress, restPort, protocol); + + HttpClientBuilder clientBuilder = HttpClients.custom(); HttpClientContext clientContext = HttpClientContext.create(); - // if username is null, do not put in authentication + + // configures the clientBuilder and clientContext if (username != null) { CredentialsProvider credsProvider = new BasicCredentialsProvider(); credsProvider.setCredentials(new AuthScope(targetHost.getHostName(), targetHost.getPort()), new UsernamePasswordCredentials(username, password)); - httpclient = HttpClients.custom().setDefaultCredentialsProvider(credsProvider).build(); - AuthCache authCache = new BasicAuthCache(); - BasicScheme basicAuth = new BasicScheme(); - authCache.put(targetHost, basicAuth); - clientContext.setCredentialsProvider(credsProvider); - clientContext.setAuthCache(authCache); + clientBuilder.setDefaultCredentialsProvider(credsProvider); + } + + if (useHttps) { + SSLContext ctx = SSLContext.getInstance("TLS"); + ctx.init(new KeyManager[0], new TrustManager[]{new DefaultTrustManager()}, + new SecureRandom()); + clientBuilder.setSSLContext(ctx); + clientBuilder.setSSLHostnameVerifier(new NoopHostnameVerifier()); + } + + return clientBuilder.build().execute(targetHost, request, clientContext); + } + + private static class DefaultTrustManager implements X509TrustManager { + + @Override + public void checkClientTrusted(X509Certificate[] arg0, String arg1) + throws CertificateException { + } + + @Override + public void checkServerTrusted(X509Certificate[] arg0, String arg1) + throws CertificateException { } - try { - return httpclient.execute(targetHost, request, clientContext); - } catch (ClientProtocolException e) { - e.printStackTrace(); - Assert.fail("Rest GET should not have thrown ClientProtocolException!"); - } catch (IOException e) { - e.printStackTrace(); - Assert.fail("Rest GET Request should not have thrown IOException!"); + @Override + public X509Certificate[] getAcceptedIssuers() { + return null; } - return null; } } http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/a515ee06/geode-assembly/src/test/java/org/apache/geode/rest/internal/web/RestSecurityWithSSLTest.java ---------------------------------------------------------------------- diff --git a/geode-assembly/src/test/java/org/apache/geode/rest/internal/web/RestSecurityWithSSLTest.java b/geode-assembly/src/test/java/org/apache/geode/rest/internal/web/RestSecurityWithSSLTest.java new file mode 100644 index 0000000..f1df632 --- /dev/null +++ b/geode-assembly/src/test/java/org/apache/geode/rest/internal/web/RestSecurityWithSSLTest.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package org.apache.geode.rest.internal.web; + +import static org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_BIND_ADDRESS; +import static org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_PORT; +import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_MANAGER; +import static org.apache.geode.distributed.ConfigurationProperties.SSL_ENABLED_COMPONENTS; +import static org.apache.geode.distributed.ConfigurationProperties.SSL_KEYSTORE; +import static org.apache.geode.distributed.ConfigurationProperties.SSL_KEYSTORE_PASSWORD; +import static org.apache.geode.distributed.ConfigurationProperties.SSL_KEYSTORE_TYPE; +import static org.apache.geode.distributed.ConfigurationProperties.SSL_PROTOCOLS; +import static org.apache.geode.distributed.ConfigurationProperties.SSL_TRUSTSTORE; +import static org.apache.geode.distributed.ConfigurationProperties.SSL_TRUSTSTORE_PASSWORD; +import static org.apache.geode.distributed.ConfigurationProperties.START_DEV_REST_API; +import static org.junit.Assert.assertEquals; + +import org.apache.geode.internal.AvailablePortHelper; +import org.apache.geode.internal.security.SecurableCommunicationChannel; +import org.apache.geode.security.templates.SimpleSecurityManager; +import org.apache.geode.test.dunit.rules.ServerStarterRule; +import org.apache.geode.test.junit.categories.IntegrationTest; +import org.apache.geode.test.junit.categories.SecurityTest; +import org.apache.http.HttpResponse; +import org.junit.After; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import java.net.URL; +import java.util.Properties; + +@Category({IntegrationTest.class, SecurityTest.class}) +public class RestSecurityWithSSLTest { + + private static int restPort = AvailablePortHelper.getRandomAvailableTCPPort(); + ServerStarterRule serverStarter = null; + + @Test + public void testRestSecurityWithSSL() throws Exception { + URL keystoreUrl = + RestSecurityWithSSLTest.class.getClassLoader().getResource("ssl/trusted.keystore"); + + Properties properties = new Properties(); + properties.setProperty(SECURITY_MANAGER, SimpleSecurityManager.class.getName()); + properties.setProperty(START_DEV_REST_API, "true"); + properties.setProperty(HTTP_SERVICE_BIND_ADDRESS, "localhost"); + properties.setProperty(HTTP_SERVICE_PORT, restPort + ""); + properties.setProperty(SSL_ENABLED_COMPONENTS, SecurableCommunicationChannel.WEB.getConstant()); + properties.setProperty(SSL_KEYSTORE, keystoreUrl.getPath()); + properties.setProperty(SSL_KEYSTORE_PASSWORD, "password"); + properties.setProperty(SSL_KEYSTORE_TYPE, "JKS"); + properties.setProperty(SSL_TRUSTSTORE, keystoreUrl.getPath()); + properties.setProperty(SSL_TRUSTSTORE_PASSWORD, "password"); + properties.setProperty(SSL_PROTOCOLS, "TLSv1.2,TLSv1.1"); + + serverStarter = new ServerStarterRule(properties); + serverStarter.startServer(); + + GeodeRestClient restClient = new GeodeRestClient("localhost", restPort, true); + HttpResponse response = restClient.doGet("/servers", "cluster", "cluster"); + + assertEquals(200, GeodeRestClient.getCode(response)); + } + + @After + public void after() { + if (serverStarter != null) { + serverStarter.after(); + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/a515ee06/geode-assembly/src/test/resources/ssl/trusted.keystore ---------------------------------------------------------------------- diff --git a/geode-assembly/src/test/resources/ssl/trusted.keystore b/geode-assembly/src/test/resources/ssl/trusted.keystore new file mode 100644 index 0000000..bd75039 Binary files /dev/null and b/geode-assembly/src/test/resources/ssl/trusted.keystore differ http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/a515ee06/geode-core/src/test/java/org/apache/geode/security/SecurityClusterConfigDUnitTest.java ---------------------------------------------------------------------- diff --git a/geode-core/src/test/java/org/apache/geode/security/SecurityClusterConfigDUnitTest.java b/geode-core/src/test/java/org/apache/geode/security/SecurityClusterConfigDUnitTest.java index cce4fdb..5d843d8 100644 --- a/geode-core/src/test/java/org/apache/geode/security/SecurityClusterConfigDUnitTest.java +++ b/geode-core/src/test/java/org/apache/geode/security/SecurityClusterConfigDUnitTest.java @@ -15,17 +15,14 @@ package org.apache.geode.security; -import static org.apache.geode.distributed.ConfigurationProperties.*; -import static org.assertj.core.api.Java6Assertions.*; -import static org.junit.Assert.*; - -import java.util.Properties; - -import org.apache.geode.test.dunit.rules.ServerStarterRule; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.experimental.categories.Category; +import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER; +import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_PORT; +import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_START; +import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_MANAGER; +import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_POST_PROCESSOR; +import static org.assertj.core.api.Java6Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import org.apache.geode.GemFireConfigException; import org.apache.geode.distributed.DistributedSystem; @@ -34,8 +31,15 @@ import org.apache.geode.security.templates.SimpleSecurityManager; import org.apache.geode.test.dunit.IgnoredException; import org.apache.geode.test.dunit.internal.JUnit4DistributedTestCase; import org.apache.geode.test.dunit.rules.LocatorServerStartupRule; +import org.apache.geode.test.dunit.rules.ServerStarterRule; import org.apache.geode.test.junit.categories.DistributedTest; import org.apache.geode.test.junit.categories.SecurityTest; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import java.util.Properties; @Category({DistributedTest.class, SecurityTest.class}) public class SecurityClusterConfigDUnitTest extends JUnit4DistributedTestCase { @@ -51,9 +55,9 @@ public class SecurityClusterConfigDUnitTest extends JUnit4DistributedTestCase { .addIgnoredException(LocalizedStrings.GEMFIRE_CACHE_SECURITY_MISCONFIGURATION_2.toString()); Properties props = new Properties(); props.setProperty(SECURITY_MANAGER, SimpleSecurityManager.class.getName()); - props.put(JMX_MANAGER, "false"); - props.put(JMX_MANAGER_START, "false"); - props.put(JMX_MANAGER_PORT, 0); + props.setProperty(JMX_MANAGER, "false"); + props.setProperty(JMX_MANAGER_START, "false"); + props.setProperty(JMX_MANAGER_PORT, 0 + ""); props.setProperty(SECURITY_POST_PROCESSOR, PDXPostProcessor.class.getName()); lsRule.getLocatorVM(0, props); } http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/a515ee06/geode-core/src/test/java/org/apache/geode/test/dunit/rules/LocatorStarterRule.java ---------------------------------------------------------------------- diff --git a/geode-core/src/test/java/org/apache/geode/test/dunit/rules/LocatorStarterRule.java b/geode-core/src/test/java/org/apache/geode/test/dunit/rules/LocatorStarterRule.java index 72627a0..3329a40 100644 --- a/geode-core/src/test/java/org/apache/geode/test/dunit/rules/LocatorStarterRule.java +++ b/geode-core/src/test/java/org/apache/geode/test/dunit/rules/LocatorStarterRule.java @@ -15,6 +15,9 @@ package org.apache.geode.test.dunit.rules; +import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER; +import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_PORT; +import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_START; import static org.apache.geode.distributed.ConfigurationProperties.MCAST_PORT; import static org.junit.Assert.assertTrue; @@ -51,6 +54,17 @@ public class LocatorStarterRule extends ExternalResource implements Serializable if (!properties.containsKey(MCAST_PORT)) { properties.setProperty(MCAST_PORT, "0"); } + if (properties.containsKey(JMX_MANAGER_PORT)) { + int jmxPort = Integer.parseInt(properties.getProperty(JMX_MANAGER_PORT)); + if (jmxPort > 0) { + if (!properties.containsKey(JMX_MANAGER)) { + properties.put(JMX_MANAGER, "true"); + } + if (!properties.containsKey(JMX_MANAGER_START)) { + properties.put(JMX_MANAGER_START, "true"); + } + } + } locator = (InternalLocator) Locator.startLocatorAndDS(0, null, properties); int locatorPort = locator.getPort(); locator.resetInternalLocatorFileNamesWithCorrectPortNumber(locatorPort);