| /* |
| * 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.brooklyn.launcher; |
| |
| import static org.testng.Assert.assertEquals; |
| import static org.testng.Assert.assertTrue; |
| import static org.testng.Assert.fail; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.net.SocketException; |
| import java.net.URISyntaxException; |
| import java.net.URL; |
| import java.security.KeyStore; |
| import java.security.SecureRandom; |
| import java.util.List; |
| import java.util.Map; |
| |
| import javax.net.ssl.SSLException; |
| import javax.net.ssl.SSLHandshakeException; |
| |
| import org.apache.brooklyn.core.entity.Entities; |
| import org.apache.brooklyn.core.internal.BrooklynProperties; |
| import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; |
| import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; |
| import org.apache.brooklyn.rest.BrooklynWebConfig; |
| import org.apache.brooklyn.util.collections.MutableMap; |
| import org.apache.brooklyn.util.exceptions.Exceptions; |
| import org.apache.brooklyn.util.http.HttpTool; |
| import org.apache.brooklyn.util.http.HttpToolResponse; |
| import org.apache.http.auth.AuthScope; |
| import org.apache.http.auth.UsernamePasswordCredentials; |
| import org.apache.http.client.CredentialsProvider; |
| import org.apache.http.client.HttpClient; |
| import org.apache.http.client.methods.HttpGet; |
| import org.apache.http.conn.ssl.SSLSocketFactory; |
| import org.apache.http.impl.client.BasicCredentialsProvider; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| import org.testng.annotations.AfterMethod; |
| import org.testng.annotations.BeforeMethod; |
| import org.testng.annotations.DataProvider; |
| import org.testng.annotations.Test; |
| |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.Lists; |
| |
| public class BrooklynWebServerTest { |
| |
| public static final Logger log = LoggerFactory.getLogger(BrooklynWebServer.class); |
| |
| private BrooklynProperties brooklynProperties; |
| private BrooklynWebServer webServer; |
| private List<LocalManagementContext> managementContexts = Lists.newCopyOnWriteArrayList(); |
| |
| @BeforeMethod(alwaysRun=true) |
| public void setUp(){ |
| brooklynProperties = BrooklynProperties.Factory.newEmpty(); |
| } |
| |
| @AfterMethod(alwaysRun=true) |
| public void tearDown() throws Exception { |
| for (LocalManagementContext managementContext : managementContexts) { |
| Entities.destroyAll(managementContext); |
| } |
| managementContexts.clear(); |
| if (webServer != null) webServer.stop(); |
| } |
| |
| private LocalManagementContext newManagementContext(BrooklynProperties brooklynProperties) { |
| LocalManagementContext result = new LocalManagementContextForTests(brooklynProperties); |
| managementContexts.add(result); |
| return result; |
| } |
| |
| @Test |
| public void verifyHttp() throws Exception { |
| webServer = new BrooklynWebServer(newManagementContext(brooklynProperties)); |
| webServer.skipSecurity(); |
| try { |
| webServer.start(); |
| |
| HttpToolResponse response = HttpTool.execAndConsume(HttpTool.httpClientBuilder().build(), new HttpGet(webServer.getRootUrl())); |
| assertEquals(response.getResponseCode(), 200); |
| } finally { |
| webServer.stop(); |
| } |
| } |
| |
| @Test |
| public void verifySecurityInitialized() throws Exception { |
| webServer = new BrooklynWebServer(newManagementContext(brooklynProperties)); |
| webServer.start(); |
| try { |
| HttpToolResponse response = HttpTool.execAndConsume(HttpTool.httpClientBuilder().build(), new HttpGet(webServer.getRootUrl())); |
| assertEquals(response.getResponseCode(), 401); |
| } finally { |
| webServer.stop(); |
| } |
| } |
| |
| @Test |
| public void verifySecurityInitializedExplicitUser() throws Exception { |
| webServer = new BrooklynWebServer(newManagementContext(brooklynProperties)); |
| webServer.start(); |
| |
| CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); |
| credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("myuser", "somepass")); |
| HttpClient client = HttpTool.httpClientBuilder() |
| .credentials(new UsernamePasswordCredentials("myuser", "somepass")) |
| .uri(webServer.getRootUrl()) |
| .build(); |
| |
| try { |
| HttpToolResponse response = HttpTool.execAndConsume(client, new HttpGet(webServer.getRootUrl())); |
| assertEquals(response.getResponseCode(), 401); |
| } finally { |
| webServer.stop(); |
| } |
| } |
| |
| @DataProvider(name="keystorePaths") |
| public Object[][] getKeystorePaths() { |
| return new Object[][] { |
| {getFile("server.ks")}, |
| {new File(getFile("server.ks")).toURI().toString()}, |
| {"classpath://server.ks"}}; |
| } |
| |
| @Test(dataProvider="keystorePaths") |
| public void verifyHttps(String keystoreUrl) throws Exception { |
| Map<String,?> flags = ImmutableMap.<String,Object>builder() |
| .put("httpsEnabled", true) |
| .put("keystoreUrl", keystoreUrl) |
| .put("keystorePassword", "password") |
| .build(); |
| webServer = new BrooklynWebServer(flags, newManagementContext(brooklynProperties)); |
| webServer.skipSecurity().start(); |
| |
| try { |
| KeyStore keyStore = load("client.ks", "password"); |
| KeyStore trustStore = load("client.ts", "password"); |
| SSLSocketFactory socketFactory = new SSLSocketFactory(SSLSocketFactory.TLS, keyStore, "password", trustStore, (SecureRandom)null, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); |
| |
| HttpToolResponse response = HttpTool.execAndConsume( |
| HttpTool.httpClientBuilder() |
| .port(webServer.getActualPort()) |
| .https(true) |
| .socketFactory(socketFactory) |
| .build(), |
| new HttpGet(webServer.getRootUrl())); |
| assertEquals(response.getResponseCode(), 200); |
| } finally { |
| webServer.stop(); |
| } |
| } |
| |
| @Test |
| public void verifyHttpsFromConfig() throws Exception { |
| brooklynProperties.put(BrooklynWebConfig.HTTPS_REQUIRED, true); |
| brooklynProperties.put(BrooklynWebConfig.KEYSTORE_URL, getFile("server.ks")); |
| brooklynProperties.put(BrooklynWebConfig.KEYSTORE_PASSWORD, "password"); |
| verifyHttpsFromConfig(brooklynProperties); |
| } |
| |
| @Test |
| public void verifyHttpsCiphers() throws Exception { |
| brooklynProperties.put(BrooklynWebConfig.HTTPS_REQUIRED, true); |
| brooklynProperties.put(BrooklynWebConfig.TRANSPORT_PROTOCOLS, "IGNORED"); |
| brooklynProperties.put(BrooklynWebConfig.TRANSPORT_CIPHERS, "IGNORED"); |
| try { |
| verifyHttpsFromConfig(brooklynProperties); |
| fail("Expected to fail due to unsupported ciphers during connection negotiation"); |
| } catch (Exception e) { |
| // if the server manages to process the error in the protocol ciphers above |
| // before the client enters the SSL handshake, then the server closes the socket |
| // abruptly and the client throws java.net.SocketException |
| // (see org.eclipse.jetty.io.ssl.SslConnection.onOpen) |
| // |
| // however, if the client manages to enter SSL negotiations, then the same behavior above |
| // causes the client to reports javax.net.ssl.SSLHandshakeException |
| // (see org.apache.http.conn.ssl.SSLSocketFactory.connectSocket) |
| // |
| // this race happens because org.apache.http.conn.ssl.SSLSockerFactory.connectSocket(...) |
| // calls java.net.Socket.connect(), and next javax.net.ssl.SSLSocket.startHandshake(), |
| // which provides the opportunity for the server to interweave between those and close the |
| // socket before the client calls startHandshake(), or maybe miss it |
| assertTrue((Exceptions.getFirstThrowableOfType(e, SocketException.class) != null) |
| || (Exceptions.getFirstThrowableOfType(e, SSLException.class) != null) |
| || (Exceptions.getFirstThrowableOfType(e, SSLHandshakeException.class) != null), |
| "Expected to fail due to inability to negotiate"); |
| } |
| } |
| |
| private void verifyHttpsFromConfig(BrooklynProperties brooklynProperties) throws Exception { |
| webServer = new BrooklynWebServer(MutableMap.of(), newManagementContext(brooklynProperties)); |
| webServer.skipSecurity(); |
| webServer.start(); |
| |
| try { |
| KeyStore keyStore = load("client.ks", "password"); |
| KeyStore trustStore = load("client.ts", "password"); |
| SSLSocketFactory socketFactory = new SSLSocketFactory(SSLSocketFactory.TLS, keyStore, "password", trustStore, (SecureRandom)null, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); |
| |
| HttpToolResponse response = HttpTool.execAndConsume( |
| HttpTool.httpClientBuilder() |
| .port(webServer.getActualPort()) |
| .https(true) |
| .socketFactory(socketFactory) |
| .build(), |
| new HttpGet(webServer.getRootUrl())); |
| assertEquals(response.getResponseCode(), 200); |
| } finally { |
| webServer.stop(); |
| } |
| } |
| |
| private KeyStore load(String name, String password) throws Exception { |
| KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); |
| FileInputStream instream = new FileInputStream(new File(getFile(name))); |
| keystore.load(instream, password.toCharArray()); |
| return keystore; |
| } |
| |
| @Test |
| public void testGetFileFromUrl() throws Exception { |
| // On Windows will treat as relative paths |
| String url = "file:///tmp/special%40file%20with%20spaces"; |
| String file = "/tmp/special@file with spaces"; |
| assertEquals(getFile(new URL(url)), new File(file).getAbsolutePath()); |
| } |
| |
| private String getFile(String classpathResource) { |
| // this works because both IDE and Maven run tests with classes/resources on the file system |
| return getFile(getClass().getResource("/" + classpathResource)); |
| } |
| |
| private String getFile(URL url) { |
| try { |
| return new File(url.toURI()).getAbsolutePath(); |
| } catch (URISyntaxException e) { |
| throw Exceptions.propagate(e); |
| } |
| } |
| } |