package org.infinispan.server.test.security.rest; import static org.junit.Assert.assertEquals; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.SocketException; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.TrustManager; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.conn.HttpClientConnectionManager; import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.socket.PlainConnectionSocketFactory; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.infinispan.arquillian.core.InfinispanResource; import org.infinispan.arquillian.core.RemoteInfinispanServer; import org.infinispan.arquillian.core.RunningServer; import org.infinispan.arquillian.core.WithRunningServer; import org.jboss.arquillian.junit.Arquillian; import org.jboss.security.JBossJSSESecurityDomain; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; /** * Tests CLIENT-CERT security for REST endpoint as is configured via "auth-method" attribute on "rest-connector" element * in datagrid subsystem. * <p/> * In order to configure CLIENT-CERT security, we add a new security-domain in the security subsystem * and a new https connector in the web subsystem. This is done via XSL transformations. * <p/> * Client authenticates himself with client.keystore file. Server contains ca.jks file in security subsystem as a * truststore and keystore_server.jks file in the REST connector as a certificate file. How to create and inspect those files * is described e.g. at http://docs.oracle.com/javase/6/docs/technotes/guides/security/jsse/JSSERefGuide.html * <p/> * Password for all the files is the same: "secret" The user is allowed to connect to the secured REST endpoint with * "client1" alias cos the server has this alias registered in its truststore. There's also another alias "test2" which is * not signed by the CA, and therefore won't be accepted. * <p/> * The REST endpoint requires users to be in "REST" role which is defined in roles.properties. * * @author <a href="mailto:mgencur@redhat.com">Martin Gencur</a> */ @RunWith(Arquillian.class) public class RESTCertSecurityIT { private static final String CONTAINER = "rest-security-cert"; private static final String KEY_A = "a"; private static final String KEY_B = "b"; private static final String KEY_C = "c"; private static final String KEY_D = "d"; private static final String client1Alias = "client1"; private static final String client2Alias = "client2"; @InfinispanResource("rest-security-cert") RemoteInfinispanServer server; static CloseableHttpClient client1; static CloseableHttpClient client2; @BeforeClass public static void setup() throws Exception { client1 = securedClient(client1Alias); client2 = securedClient(client2Alias); } @AfterClass public static void tearDown() { try { client1.close(); } catch (IOException e) { } try { client2.close(); } catch (IOException e) { } } @Ignore public void testSecuredReadWriteOperations() throws Exception { //correct alias for the certificate put(client1, keyAddress(KEY_A), HttpStatus.SC_OK); //test wrong authorization, 1. wrong alias for the certificate put(client2, keyAddress(KEY_B), HttpStatus.SC_FORBIDDEN); //2. access over 8080 put(client1, keyAddressUnsecured(KEY_B), HttpStatus.SC_UNAUTHORIZED); post(client1, keyAddress(KEY_C), HttpStatus.SC_OK); post(client2, keyAddress(KEY_D), HttpStatus.SC_FORBIDDEN); //get is secured too HttpResponse resp = get(client1, keyAddress(KEY_A), HttpStatus.SC_OK); String content = new BufferedReader(new InputStreamReader(resp.getEntity().getContent())).readLine(); assertEquals("data", content); //test wrong authorization, 1. wrong alias for the certificate get(client2, keyAddress(KEY_A), HttpStatus.SC_FORBIDDEN); //2. access over 8080 get(client1, keyAddressUnsecured(KEY_A), HttpStatus.SC_UNAUTHORIZED); head(client2, keyAddress(KEY_A), HttpStatus.SC_FORBIDDEN); //access over 8080 head(client1, keyAddressUnsecured(KEY_A), HttpStatus.SC_UNAUTHORIZED); head(client1, keyAddress(KEY_A), HttpStatus.SC_OK); delete(client2, keyAddress(KEY_A), HttpStatus.SC_FORBIDDEN); delete(client1, keyAddress(KEY_A), HttpStatus.SC_OK); delete(client1, keyAddress(KEY_C), HttpStatus.SC_OK); } @Test @WithRunningServer({@RunningServer(name = CONTAINER, config = "testsuite/rest-sec-cert.xml")}) public void testValidCertificateAccess() throws Exception { put(client1, keyAddress(KEY_A), HttpStatus.SC_OK); } @Test @WithRunningServer({@RunningServer(name = CONTAINER, config = "testsuite/rest-sec-cert.xml")}) public void testInvalidCertificateAccess() throws Exception { put(client2, keyAddress(KEY_A), HttpStatus.SC_FORBIDDEN); } private String keyAddress(String key) { return "https://" + server.getRESTEndpoint().getInetAddress().getHostName() + ":8443" + server.getRESTEndpoint().getContextPath() + "/default/" + key; } private String keyAddressUnsecured(String key) { return "http://" + server.getRESTEndpoint().getInetAddress().getHostName() + ":8080" + server.getRESTEndpoint().getContextPath() + "/default/" + key; } private HttpResponse handleIOException(IOException e, int expectedCode) throws IOException { if ((expectedCode == HttpStatus.SC_FORBIDDEN) && ((e instanceof SSLHandshakeException) || (e instanceof SocketException))) return null; else throw e; } private HttpResponse put(CloseableHttpClient httpClient, String uri, int expectedCode) throws Exception { HttpResponse response; HttpPut put = new HttpPut(uri); put.setEntity(new StringEntity("data", "UTF-8")); try { response = httpClient.execute(put); assertEquals(expectedCode, response.getStatusLine().getStatusCode()); return response; } catch (IOException e) { return handleIOException(e, expectedCode); } } private HttpResponse post(CloseableHttpClient httpClient, String uri, int expectedCode) throws Exception { HttpResponse response; HttpPost post = new HttpPost(uri); post.setEntity(new StringEntity("data", "UTF-8")); response = httpClient.execute(post); assertEquals(expectedCode, response.getStatusLine().getStatusCode()); return response; } private HttpResponse get(CloseableHttpClient httpClient, String uri, int expectedCode) throws Exception { HttpResponse response; HttpGet get = new HttpGet(uri); response = httpClient.execute(get); assertEquals(expectedCode, response.getStatusLine().getStatusCode()); return response; } private HttpResponse delete(CloseableHttpClient httpClient, String uri, int expectedCode) throws Exception { HttpResponse response; HttpDelete delete = new HttpDelete(uri); response = httpClient.execute(delete); assertEquals(expectedCode, response.getStatusLine().getStatusCode()); return response; } private HttpResponse head(CloseableHttpClient httpClient, String uri, int expectedCode) throws Exception { HttpResponse response; HttpHead head = new HttpHead(uri); response = httpClient.execute(head); assertEquals(expectedCode, response.getStatusLine().getStatusCode()); return response; } public static CloseableHttpClient securedClient(String alias) throws Exception { ClassLoader tccl = Thread.currentThread().getContextClassLoader(); SSLContext ctx = SSLContext.getInstance("TLS"); JBossJSSESecurityDomain jsseSecurityDomain = new JBossJSSESecurityDomain("client_cert_auth"); jsseSecurityDomain.setKeyStoreURL(tccl.getResource("keystore_client.jks").getPath()); jsseSecurityDomain.setKeyStorePassword("secret"); jsseSecurityDomain.setClientAlias(alias); jsseSecurityDomain.setTrustStoreURL(tccl.getResource("ca.jks").getPath()); jsseSecurityDomain.setTrustStorePassword("secret"); jsseSecurityDomain.reloadKeyAndTrustStore(); KeyManager[] keyManagers = jsseSecurityDomain.getKeyManagers(); TrustManager[] trustManagers = jsseSecurityDomain.getTrustManagers(); ctx.init(keyManagers, trustManagers, null); HostnameVerifier verifier = (hostname, sslSession) -> true; ConnectionSocketFactory sslssf = new SSLConnectionSocketFactory(ctx, verifier); ConnectionSocketFactory plainsf = new PlainConnectionSocketFactory(); Registry<ConnectionSocketFactory> sr = RegistryBuilder.<ConnectionSocketFactory>create() .register("http", plainsf) .register("https", sslssf) .build(); HttpClientConnectionManager pcm = new PoolingHttpClientConnectionManager(sr); CloseableHttpClient httpClient = HttpClients.custom() .setConnectionManager(pcm) .build(); return httpClient; } }