http://git-wip-us.apache.org/repos/asf/hadoop/blob/c2288ac4/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/ProxyCA.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/ProxyCA.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/ProxyCA.java new file mode 100644 index 0000000..26760d3 --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/ProxyCA.java @@ -0,0 +1,408 @@ +/* + * 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.hadoop.yarn.server.webproxy; + +import com.google.common.annotations.VisibleForTesting; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.classification.InterfaceStability; +import org.apache.hadoop.yarn.api.records.ApplicationId; +import org.apache.hadoop.yarn.exceptions.YarnRuntimeException; +import org.apache.http.conn.ssl.DefaultHostnameVerifier; +import org.apache.http.conn.util.PublicSuffixMatcherLoader; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils; +import org.bouncycastle.crypto.util.PrivateKeyFactory; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder; +import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509KeyManager; +import javax.net.ssl.X509TrustManager; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.net.Socket; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.Security; +import java.security.SignatureException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Random; +import java.util.UUID; + +/** + * Allows for the generation and acceptance of specialized HTTPS Certificates to + * be used for HTTPS communication between the AMs and the RM Proxy. + */ +@InterfaceAudience.Private +@InterfaceStability.Unstable +public class ProxyCA { + private static final Logger LOG = LoggerFactory.getLogger(ProxyCA.class); + + private X509Certificate caCert; + private KeyPair caKeyPair; + private KeyStore childTrustStore; + private final Random srand; + private X509TrustManager defaultTrustManager; + private X509KeyManager x509KeyManager; + private HostnameVerifier hostnameVerifier; + private static final AlgorithmIdentifier SIG_ALG_ID = + new DefaultSignatureAlgorithmIdentifierFinder().find("SHA512WITHRSA"); + + public ProxyCA() { + srand = new SecureRandom(); + + // This only has to be done once + Security.addProvider(new BouncyCastleProvider()); + } + + public void init() throws GeneralSecurityException, IOException { + createCACertAndKeyPair(); + + defaultTrustManager = null; + TrustManagerFactory factory = TrustManagerFactory.getInstance( + TrustManagerFactory.getDefaultAlgorithm()); + factory.init((KeyStore) null); + for (TrustManager manager : factory.getTrustManagers()) { + if (manager instanceof X509TrustManager) { + defaultTrustManager = (X509TrustManager) manager; + break; + } + } + if (defaultTrustManager == null) { + throw new YarnRuntimeException( + "Could not find default X509 Trust Manager"); + } + + this.x509KeyManager = createKeyManager(); + this.hostnameVerifier = createHostnameVerifier(); + this.childTrustStore = createTrustStore("client", caCert); + } + + private X509Certificate createCert(boolean isCa, String issuerStr, + String subjectStr, Date from, Date to, PublicKey publicKey, + PrivateKey privateKey) throws GeneralSecurityException, IOException { + X500Name issuer = new X500Name(issuerStr); + X500Name subject = new X500Name(subjectStr); + SubjectPublicKeyInfo subPubKeyInfo = + SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()); + X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder( + issuer, new BigInteger(64, srand), from, to, subject, subPubKeyInfo); + AlgorithmIdentifier digAlgId = + new DefaultDigestAlgorithmIdentifierFinder().find(SIG_ALG_ID); + ContentSigner contentSigner; + try { + contentSigner = new BcRSAContentSignerBuilder(SIG_ALG_ID, digAlgId) + .build(PrivateKeyFactory.createKey(privateKey.getEncoded())); + } catch (OperatorCreationException oce) { + throw new GeneralSecurityException(oce); + } + if (isCa) { + // BasicConstraints(0) indicates a CA and a path length of 0. This is + // important to indicate that child certificates can't issue additional + // grandchild certificates + certBuilder.addExtension(Extension.basicConstraints, true, + new BasicConstraints(0)); + } else { + // BasicConstraints(false) indicates this is not a CA + certBuilder.addExtension(Extension.basicConstraints, true, + new BasicConstraints(false)); + certBuilder.addExtension(Extension.authorityKeyIdentifier, false, + new JcaX509ExtensionUtils().createAuthorityKeyIdentifier(caCert)); + } + X509CertificateHolder certHolder = certBuilder.build(contentSigner); + X509Certificate cert = new JcaX509CertificateConverter().setProvider("BC") + .getCertificate(certHolder); + LOG.info("Created Certificate for {}", subject); + return cert; + } + + private void createCACertAndKeyPair() + throws GeneralSecurityException, IOException { + Date from = new Date(); + Date to = new GregorianCalendar(2037, Calendar.DECEMBER, 31).getTime(); + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + keyGen.initialize(2048); + caKeyPair = keyGen.genKeyPair(); + String subject = "OU=YARN-" + UUID.randomUUID(); + caCert = createCert(true, subject, subject, from, to, + caKeyPair.getPublic(), caKeyPair.getPrivate()); + if (LOG.isDebugEnabled()) { + LOG.debug("CA Certificate: \n{}", caCert); + } + } + + public byte[] createChildKeyStore(ApplicationId appId, String ksPassword) + throws Exception { + // We don't check the expiration date, and this will provide further reason + // for outside users to not accept these certificates + Date from = new Date(); + Date to = from; + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + keyGen.initialize(2048); + KeyPair keyPair = keyGen.genKeyPair(); + String issuer = caCert.getSubjectX500Principal().getName(); + String subject = "CN=" + appId; + X509Certificate cert = createCert(false, issuer, subject, from, to, + keyPair.getPublic(), caKeyPair.getPrivate()); + if (LOG.isTraceEnabled()) { + LOG.trace("Certificate for {}: \n{}", appId, cert); + } + + KeyStore keyStore = createChildKeyStore(ksPassword, "server", + keyPair.getPrivate(), cert); + return keyStoreToBytes(keyStore, ksPassword); + } + + public byte[] getChildTrustStore(String password) + throws GeneralSecurityException, IOException { + return keyStoreToBytes(childTrustStore, password); + } + + private KeyStore createEmptyKeyStore() + throws GeneralSecurityException, IOException { + KeyStore ks = KeyStore.getInstance("JKS"); + ks.load(null, null); // initialize + return ks; + } + + private KeyStore createChildKeyStore(String password, String alias, + Key privateKey, Certificate cert) + throws GeneralSecurityException, IOException { + KeyStore ks = createEmptyKeyStore(); + ks.setKeyEntry(alias, privateKey, password.toCharArray(), + new Certificate[]{cert, caCert}); + return ks; + } + + public String generateKeyStorePassword() { + return RandomStringUtils.random(16, 0, 0, true, true, null, srand); + } + + private byte[] keyStoreToBytes(KeyStore ks, String password) + throws GeneralSecurityException, IOException { + try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { + ks.store(out, password.toCharArray()); + return out.toByteArray(); + } + } + + private KeyStore createTrustStore(String alias, Certificate cert) + throws GeneralSecurityException, IOException { + KeyStore ks = createEmptyKeyStore(); + ks.setCertificateEntry(alias, cert); + return ks; + } + + public SSLContext createSSLContext(ApplicationId appId) + throws GeneralSecurityException { + // We need the normal TrustManager, plus our custom one. While the + // SSLContext accepts an array of TrustManagers, the docs indicate that only + // the first instance of any particular implementation type is used + // (e.g. X509KeyManager) - this means that simply putting both TrustManagers + // in won't work. We need to have ours do both. + TrustManager[] trustManagers = new TrustManager[] { + createTrustManager(appId)}; + KeyManager[] keyManagers = new KeyManager[]{x509KeyManager}; + + SSLContext sc = SSLContext.getInstance("SSL"); + sc.init(keyManagers, trustManagers, new SecureRandom()); + return sc; + } + + @VisibleForTesting + X509TrustManager createTrustManager(ApplicationId appId) { + return new X509TrustManager() { + @Override + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return defaultTrustManager.getAcceptedIssuers(); + } + + @Override + public void checkClientTrusted( + java.security.cert.X509Certificate[] certs, String authType) { + // not used + } + + @Override + public void checkServerTrusted( + java.security.cert.X509Certificate[] certs, String authType) + throws CertificateException { + // Our certs will always have 2 in the chain, with 0 being the app's + // cert and 1 being the RM's cert + boolean issuedByRM = false; + if (certs.length == 2) { + try { + // We can verify both certs using the CA cert's public key - the + // child cert's info is not needed + certs[0].verify(caKeyPair.getPublic()); + certs[1].verify(caKeyPair.getPublic()); + issuedByRM = true; + } catch (CertificateException | NoSuchAlgorithmException + | InvalidKeyException | NoSuchProviderException + | SignatureException e) { + // Fall back to the default trust manager + LOG.debug("Could not verify certificate with RM CA, falling " + + "back to default", e); + defaultTrustManager.checkServerTrusted(certs, authType); + } + } else { + LOG.debug("Certificate not issued by RM CA, falling back to " + + "default"); + defaultTrustManager.checkServerTrusted(certs, authType); + } + if (issuedByRM) { + // Check that it has the correct App ID + if (!certs[0].getSubjectX500Principal().getName() + .equals("CN=" + appId)) { + throw new CertificateException( + "Expected to find Subject X500 Principal with CN=" + + appId + " but found " + + certs[0].getSubjectX500Principal().getName()); + } + LOG.debug("Verified certificate signed by RM CA"); + } + } + }; + } + + @VisibleForTesting + X509KeyManager getX509KeyManager() { + return x509KeyManager; + } + + private X509KeyManager createKeyManager() { + return new X509KeyManager() { + @Override + public String[] getClientAliases(String s, Principal[] principals) { + return new String[]{"client"}; + } + + @Override + public String chooseClientAlias(String[] strings, + Principal[] principals, Socket socket) { + return "client"; + } + + @Override + public String[] getServerAliases(String s, Principal[] principals) { + return null; + } + + @Override + public String chooseServerAlias(String s, Principal[] principals, + Socket socket) { + return null; + } + + @Override + public X509Certificate[] getCertificateChain(String s) { + return new X509Certificate[]{caCert}; + } + + @Override + public PrivateKey getPrivateKey(String s) { + return caKeyPair.getPrivate(); + } + }; + } + + public HostnameVerifier getHostnameVerifier() { + return hostnameVerifier; + } + + private HostnameVerifier createHostnameVerifier() { + HostnameVerifier defaultHostnameVerifier = + new DefaultHostnameVerifier(PublicSuffixMatcherLoader.getDefault()); + return new HostnameVerifier() { + @Override + public boolean verify(String host, SSLSession sslSession) { + try { + Certificate[] certs = sslSession.getPeerCertificates(); + if (certs.length == 2) { + // Make sure this is one of our certs. More thorough checking would + // have already been done by the SSLContext + certs[0].verify(caKeyPair.getPublic()); + LOG.debug("Verified certificate signed by RM CA, " + + "skipping hostname verification"); + return true; + } + } catch (SSLPeerUnverifiedException e) { + // No certificate + return false; + } catch (CertificateException | NoSuchAlgorithmException + | InvalidKeyException | SignatureException + | NoSuchProviderException e) { + // fall back to normal verifier below + LOG.debug("Could not verify certificate with RM CA, " + + "falling back to default hostname verification", e); + } + return defaultHostnameVerifier.verify(host, sslSession); + } + }; + } + + @VisibleForTesting + void setDefaultTrustManager(X509TrustManager trustManager) { + this.defaultTrustManager = trustManager; + } + + @VisibleForTesting + public X509Certificate getCaCert() { + return caCert; + } + + @VisibleForTesting + public KeyPair getCaKeyPair() { + return caKeyPair; + } +}
http://git-wip-us.apache.org/repos/asf/hadoop/blob/c2288ac4/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/WebAppProxy.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/WebAppProxy.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/WebAppProxy.java index 71679cc..f205cf7 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/WebAppProxy.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/WebAppProxy.java @@ -39,6 +39,7 @@ public class WebAppProxy extends AbstractService { public static final String FETCHER_ATTRIBUTE= "AppUrlFetcher"; public static final String IS_SECURITY_ENABLED_ATTRIBUTE = "IsSecurityEnabled"; public static final String PROXY_HOST_ATTRIBUTE = "proxyHost"; + public static final String PROXY_CA = "ProxyCA"; private static final Logger LOG = LoggerFactory.getLogger( WebAppProxy.class); http://git-wip-us.apache.org/repos/asf/hadoop/blob/c2288ac4/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/WebAppProxyServlet.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/WebAppProxyServlet.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/WebAppProxyServlet.java index f21ff2c..2dc3a46 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/WebAppProxyServlet.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/WebAppProxyServlet.java @@ -45,6 +45,7 @@ import javax.servlet.http.HttpServletResponse; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriBuilderException; +import com.google.common.annotations.VisibleForTesting; import org.apache.hadoop.io.IOUtils; import org.apache.hadoop.net.NetUtils; import org.apache.hadoop.yarn.api.records.ApplicationId; @@ -63,15 +64,14 @@ import org.apache.hadoop.yarn.webapp.util.WebAppUtils; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; +import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.client.params.ClientPNames; -import org.apache.http.client.params.CookiePolicy; import org.apache.http.client.utils.URLEncodedUtils; -import org.apache.http.conn.params.ConnRoutePNames; import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -177,6 +177,39 @@ public class WebAppProxyServlet extends HttpServlet { __(). __(); } + + /** + * Show the user a page that says that HTTPS must be used but was not. + * @param resp the http response + * @param link the link to point to + * @return true if HTTPS must be used but was not, false otherwise + * @throws IOException on any error. + */ + @VisibleForTesting + static boolean checkHttpsStrictAndNotProvided( + HttpServletResponse resp, URI link, YarnConfiguration conf) + throws IOException { + String httpsPolicy = conf.get( + YarnConfiguration.RM_APPLICATION_HTTPS_POLICY, + YarnConfiguration.DEFAULT_RM_APPLICATION_HTTPS_POLICY); + boolean required = httpsPolicy.equals("STRICT"); + boolean provided = link.getScheme().equals("https"); + if (required && !provided) { + resp.setContentType(MimeType.HTML); + Page p = new Page(resp.getWriter()); + p.html(). + h1("HTTPS must be used"). + h3(). + __(YarnConfiguration.RM_APPLICATION_HTTPS_POLICY, + "is set to STRICT, which means that the tracking URL ", + "must be an HTTPS URL, but it is not."). + __("The tracking URL is: ", link). + __(). + __(); + return true; + } + return false; + } /** * Download link and have it be the response. @@ -186,17 +219,31 @@ public class WebAppProxyServlet extends HttpServlet { * @param c the cookie to set if any * @param proxyHost the proxy host * @param method the http method + * @param appId the ApplicationID * @throws IOException on any error. */ - private static void proxyLink(final HttpServletRequest req, + private void proxyLink(final HttpServletRequest req, final HttpServletResponse resp, final URI link, final Cookie c, - final String proxyHost, final HTTP method) throws IOException { - DefaultHttpClient client = new DefaultHttpClient(); - client - .getParams() - .setParameter(ClientPNames.COOKIE_POLICY, - CookiePolicy.BROWSER_COMPATIBILITY) - .setBooleanParameter(ClientPNames.ALLOW_CIRCULAR_REDIRECTS, true); + final String proxyHost, final HTTP method, final ApplicationId appId) + throws IOException { + HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); + + String httpsPolicy = conf.get(YarnConfiguration.RM_APPLICATION_HTTPS_POLICY, + YarnConfiguration.DEFAULT_RM_APPLICATION_HTTPS_POLICY); + if (httpsPolicy.equals("LENIENT") || httpsPolicy.equals("STRICT")) { + ProxyCA proxyCA = getProxyCA(); + // ProxyCA could be null when the Proxy is run outside the RM + if (proxyCA != null) { + try { + httpClientBuilder.setSSLContext(proxyCA.createSSLContext(appId)); + httpClientBuilder.setSSLHostnameVerifier( + proxyCA.getHostnameVerifier()); + } catch (Exception e) { + throw new IOException(e); + } + } + } + // Make sure we send the request from the proxy address in the config // since that is what the AM filter checks against. IP aliasing or // similar could cause issues otherwise. @@ -204,8 +251,11 @@ public class WebAppProxyServlet extends HttpServlet { if (LOG.isDebugEnabled()) { LOG.debug("local InetAddress for proxy host: {}", localAddress); } - client.getParams() - .setParameter(ConnRoutePNames.LOCAL_ADDRESS, localAddress); + httpClientBuilder.setDefaultRequestConfig( + RequestConfig.custom() + .setCircularRedirectsAllowed(true) + .setLocalAddress(localAddress) + .build()); HttpRequestBase base = null; if (method.equals(HTTP.GET)) { @@ -247,6 +297,7 @@ public class WebAppProxyServlet extends HttpServlet { PROXY_USER_COOKIE_NAME + "=" + URLEncoder.encode(user, "ASCII")); } OutputStream out = resp.getOutputStream(); + HttpClient client = httpClientBuilder.build(); try { HttpResponse httpResp = client.execute(base); resp.setStatus(httpResp.getStatusLine().getStatusCode()); @@ -287,6 +338,10 @@ public class WebAppProxyServlet extends HttpServlet { return ((AppReportFetcher) getServletContext() .getAttribute(WebAppProxy.FETCHER_ATTRIBUTE)).getApplicationReport(id); } + + private ProxyCA getProxyCA() { + return ((ProxyCA) getServletContext().getAttribute(WebAppProxy.PROXY_CA)); + } private String getProxyHost() throws IOException { return ((String) getServletContext() @@ -420,6 +475,10 @@ public class WebAppProxyServlet extends HttpServlet { return; } + if (checkHttpsStrictAndNotProvided(resp, trackingUri, conf)) { + return; + } + String runningUser = applicationReport.getUser(); if (checkUser && !runningUser.equals(remoteUser)) { @@ -453,7 +512,7 @@ public class WebAppProxyServlet extends HttpServlet { if (userWasWarned && userApproved) { c = makeCheckCookie(id, true); } - proxyLink(req, resp, toFetch, c, getProxyHost(), method); + proxyLink(req, resp, toFetch, c, getProxyHost(), method, id); } catch(URISyntaxException | YarnException e) { throw new IOException(e); http://git-wip-us.apache.org/repos/asf/hadoop/blob/c2288ac4/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/TestProxyCA.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/TestProxyCA.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/TestProxyCA.java new file mode 100644 index 0000000..67bcea2 --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/TestProxyCA.java @@ -0,0 +1,518 @@ +/** +* 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.hadoop.yarn.server.webproxy; + +import org.apache.hadoop.security.ssl.KeyStoreTestUtil; +import org.apache.hadoop.yarn.api.records.ApplicationId; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.X509KeyManager; +import javax.net.ssl.X509TrustManager; +import javax.security.auth.x500.X500Principal; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; + +public class TestProxyCA { + + @Test + public void testInit() throws Exception { + ProxyCA proxyCA = new ProxyCA(); + Assert.assertNull(proxyCA.getCaCert()); + Assert.assertNull(proxyCA.getCaKeyPair()); + Assert.assertNull(proxyCA.getX509KeyManager()); + Assert.assertNull(proxyCA.getHostnameVerifier()); + + proxyCA.init(); + Assert.assertNotNull(proxyCA.getCaCert()); + Assert.assertNotNull(proxyCA.getCaKeyPair()); + Assert.assertNotNull(proxyCA.getX509KeyManager()); + Assert.assertNotNull(proxyCA.getHostnameVerifier()); + } + + @Test + public void testCreateChildKeyStore() throws Exception { + ProxyCA proxyCA = new ProxyCA(); + proxyCA.init(); + ApplicationId appId = + ApplicationId.newInstance(System.currentTimeMillis(), 1); + byte[] keystoreBytes = proxyCA.createChildKeyStore(appId, + "password"); + KeyStore keyStore = KeyStoreTestUtil.bytesToKeyStore(keystoreBytes, + "password"); + Assert.assertEquals(1, keyStore.size()); + Certificate[] certChain = keyStore.getCertificateChain("server"); + Assert.assertEquals(2, certChain.length); + X509Certificate caCert = (X509Certificate) certChain[1]; + X509Certificate cert = (X509Certificate) certChain[0]; + + // check child cert + Assert.assertEquals(caCert.getSubjectX500Principal().toString(), + cert.getIssuerDN().toString()); + Assert.assertEquals(new X500Principal("CN=" + appId), + cert.getSubjectX500Principal()); + Assert.assertFalse("Found multiple fields in X500 Principal, when there " + + "should have only been one: " + cert.getSubjectX500Principal(), + cert.getSubjectX500Principal().toString().contains(",")); + Assert.assertEquals("SHA512withRSA", cert.getSigAlgName()); + Assert.assertEquals(cert.getNotBefore(), cert.getNotAfter()); + Assert.assertTrue("Expected certificate to be expired but was not: " + + cert.getNotAfter(), cert.getNotAfter().before(new Date())); + Assert.assertEquals(new X500Principal("CN=" + appId).toString(), + cert.getSubjectDN().toString()); + Key privateKey = keyStore.getKey("server", "password".toCharArray()); + Assert.assertEquals("RSA", privateKey.getAlgorithm()); + Assert.assertEquals(-1, cert.getBasicConstraints()); + + // verify signature on child cert + PublicKey caPublicKey = caCert.getPublicKey(); + cert.verify(caPublicKey); + + // check CA cert + checkCACert(caCert); + Assert.assertEquals(proxyCA.getCaCert(), caCert); + + // verify signature on CA cert + caCert.verify(caPublicKey); + + // verify CA public key matches private key + PrivateKey caPrivateKey = + proxyCA.getX509KeyManager().getPrivateKey(null); + checkPrivatePublicKeys(caPrivateKey, caPublicKey); + Assert.assertEquals(proxyCA.getCaKeyPair().getPublic(), caPublicKey); + Assert.assertEquals(proxyCA.getCaKeyPair().getPrivate(), caPrivateKey); + } + + @Test + public void testGetChildTrustStore() throws Exception { + ProxyCA proxyCA = new ProxyCA(); + proxyCA.init(); + byte[] truststoreBytes = proxyCA.getChildTrustStore("password"); + KeyStore truststore = KeyStoreTestUtil.bytesToKeyStore(truststoreBytes, + "password"); + Assert.assertEquals(1, truststore.size()); + X509Certificate caCert = + (X509Certificate) truststore.getCertificate("client"); + + // check CA cert + checkCACert(caCert); + Assert.assertEquals(proxyCA.getCaCert(), caCert); + + // verify signature on CA cert + PublicKey caPublicKey = caCert.getPublicKey(); + caCert.verify(caPublicKey); + + // verify CA public key matches private key + PrivateKey caPrivateKey = + proxyCA.getX509KeyManager().getPrivateKey(null); + checkPrivatePublicKeys(caPrivateKey, caPublicKey); + Assert.assertEquals(proxyCA.getCaKeyPair().getPublic(), caPublicKey); + Assert.assertEquals(proxyCA.getCaKeyPair().getPrivate(), caPrivateKey); + } + + @Test + public void testGenerateKeyStorePassword() throws Exception { + // We can't possibly test every possible string, but we can at least verify + // a few things about a few of the generated strings as a sanity check + ProxyCA proxyCA = new ProxyCA(); + proxyCA.init(); + Set<String> passwords = new HashSet<>(); + + for (int i = 0; i < 5; i++) { + String password = proxyCA.generateKeyStorePassword(); + Assert.assertEquals(16, password.length()); + for (char c : password.toCharArray()) { + Assert.assertFalse("Found character '" + c + "' in password '" + + password + "' which is outside of the expected range", c < ' '); + Assert.assertFalse("Found character '" + c + "' in password '" + + password + "' which is outside of the expected range", c > 'z'); + } + Assert.assertFalse("Password " + password + + " was generated twice, which is _extremely_ unlikely" + + " and shouldn't practically happen: " + passwords, + passwords.contains(password)); + passwords.add(password); + } + } + + @Test + public void testCreateTrustManagerDefaultTrustManager() throws Exception { + ProxyCA proxyCA = new ProxyCA(); + proxyCA.init(); + X509TrustManager defaultTrustManager = Mockito.mock(X509TrustManager.class); + proxyCA.setDefaultTrustManager(defaultTrustManager); + ApplicationId appId = + ApplicationId.newInstance(System.currentTimeMillis(), 1); + X509TrustManager trustManager = proxyCA.createTrustManager(appId); + Mockito.when(defaultTrustManager.getAcceptedIssuers()).thenReturn( + new X509Certificate[]{KeyStoreTestUtil.generateCertificate( + "CN=foo", KeyStoreTestUtil.generateKeyPair("RSA"), 30, + "SHA1withRSA")}); + + Assert.assertArrayEquals(defaultTrustManager.getAcceptedIssuers(), + trustManager.getAcceptedIssuers()); + trustManager.checkClientTrusted(null, null); + } + + @Test + public void testCreateTrustManagerYarnCert() throws Exception { + ProxyCA proxyCA = new ProxyCA(); + proxyCA.init(); + X509TrustManager defaultTrustManager = Mockito.mock(X509TrustManager.class); + proxyCA.setDefaultTrustManager(defaultTrustManager); + ApplicationId appId = + ApplicationId.newInstance(System.currentTimeMillis(), 1); + X509TrustManager trustManager = proxyCA.createTrustManager(appId); + + X509Certificate[] certChain = castCertificateArrayToX509CertificateArray( + KeyStoreTestUtil.bytesToKeyStore( + proxyCA.createChildKeyStore(appId, "password"), "password") + .getCertificateChain("server")); + trustManager.checkServerTrusted(certChain, "RSA"); + Mockito.verify(defaultTrustManager, Mockito.times(0)) + .checkServerTrusted(certChain, "RSA"); + } + + @Test + public void testCreateTrustManagerWrongApp() throws Exception { + ProxyCA proxyCA = new ProxyCA(); + proxyCA.init(); + X509TrustManager defaultTrustManager = Mockito.mock(X509TrustManager.class); + proxyCA.setDefaultTrustManager(defaultTrustManager); + ApplicationId appId = + ApplicationId.newInstance(System.currentTimeMillis(), 1); + ApplicationId appId2 = + ApplicationId.newInstance(System.currentTimeMillis(), 2); + X509TrustManager trustManager = proxyCA.createTrustManager(appId); + + X509Certificate[] certChain = castCertificateArrayToX509CertificateArray( + KeyStoreTestUtil.bytesToKeyStore( + proxyCA.createChildKeyStore(appId2, "password"), "password") + .getCertificateChain("server")); + try { + trustManager.checkServerTrusted(certChain, "RSA"); + Assert.fail("Should have thrown a CertificateException, but did not"); + } catch (CertificateException ce) { + Assert.assertEquals("Expected to find Subject X500 Principal with CN=" + + appId + " but found CN=" + appId2, ce.getMessage()); + } + } + + @Test + public void testCreateTrustManagerWrongRM() throws Exception { + ProxyCA proxyCA = new ProxyCA(); + proxyCA.init(); + X509TrustManager defaultTrustManager = Mockito.mock(X509TrustManager.class); + proxyCA.setDefaultTrustManager(defaultTrustManager); + ApplicationId appId = + ApplicationId.newInstance(System.currentTimeMillis(), 1); + X509TrustManager trustManager = proxyCA.createTrustManager(appId); + + ProxyCA proxyCA2 = new ProxyCA(); // Simulates another RM + proxyCA2.init(); + X509Certificate[] certChain = castCertificateArrayToX509CertificateArray( + KeyStoreTestUtil.bytesToKeyStore( + proxyCA2.createChildKeyStore(appId, "password"), "password") + .getCertificateChain("server")); + Mockito.verify(defaultTrustManager, Mockito.times(0)) + .checkServerTrusted(certChain, "RSA"); + trustManager.checkServerTrusted(certChain, "RSA"); + Mockito.verify(defaultTrustManager, Mockito.times(1)) + .checkServerTrusted(certChain, "RSA"); + } + + @Test + public void testCreateTrustManagerRealCert() throws Exception { + ProxyCA proxyCA = new ProxyCA(); + proxyCA.init(); + X509TrustManager defaultTrustManager = Mockito.mock(X509TrustManager.class); + proxyCA.setDefaultTrustManager(defaultTrustManager); + ApplicationId appId = + ApplicationId.newInstance(System.currentTimeMillis(), 1); + X509TrustManager trustManager = proxyCA.createTrustManager(appId); + + // "real" cert + X509Certificate[] + certChain = new X509Certificate[]{ + KeyStoreTestUtil.generateCertificate("CN=foo.com", + KeyStoreTestUtil.generateKeyPair("RSA"), 30, "SHA1withRSA")}; + Mockito.verify(defaultTrustManager, Mockito.times(0)) + .checkServerTrusted(certChain, "RSA"); + trustManager.checkServerTrusted(certChain, "RSA"); + Mockito.verify(defaultTrustManager, Mockito.times(1)) + .checkServerTrusted(certChain, "RSA"); + + // "real" cert x2 + certChain = new X509Certificate[]{ + KeyStoreTestUtil.generateCertificate("CN=foo.com", + KeyStoreTestUtil.generateKeyPair("RSA"), 30, "SHA1withRSA"), + KeyStoreTestUtil.generateCertificate("CN=foo.com", + KeyStoreTestUtil.generateKeyPair("RSA"), 30, "SHA1withRSA")}; + Mockito.verify(defaultTrustManager, Mockito.times(0)) + .checkServerTrusted(certChain, "RSA"); + trustManager.checkServerTrusted(certChain, "RSA"); + Mockito.verify(defaultTrustManager, Mockito.times(1)) + .checkServerTrusted(certChain, "RSA"); + } + + @Test + public void testCreateTrustManagerExceptions() throws Exception { + ProxyCA proxyCA = new ProxyCA(); + proxyCA.init(); + X509TrustManager defaultTrustManager = Mockito.mock(X509TrustManager.class); + proxyCA.setDefaultTrustManager(defaultTrustManager); + ApplicationId appId = + ApplicationId.newInstance(System.currentTimeMillis(), 1); + X509TrustManager trustManager = proxyCA.createTrustManager(appId); + + for (Exception e : new Exception[]{ + new CertificateException(), new NoSuchAlgorithmException(), + new InvalidKeyException(), new SignatureException(), + new NoSuchProviderException()}) { + X509Certificate[] certChain = castCertificateArrayToX509CertificateArray( + KeyStoreTestUtil.bytesToKeyStore( + proxyCA.createChildKeyStore(appId, "password"), "password") + .getCertificateChain("server")); + X509Certificate cert = Mockito.spy(certChain[0]); + certChain[0] = cert; + // Throw e to simulate problems with verifying + Mockito.doThrow(e).when(certChain[0]).verify(Mockito.any()); + Mockito.verify(defaultTrustManager, Mockito.times(0)) + .checkServerTrusted(certChain, "RSA"); + trustManager.checkServerTrusted(certChain, "RSA"); + Mockito.verify(defaultTrustManager, Mockito.times(1)) + .checkServerTrusted(certChain, "RSA"); + } + } + + @Test + public void testCreateKeyManager() throws Exception { + ProxyCA proxyCA = new ProxyCA(); + proxyCA.init(); + X509KeyManager keyManager = proxyCA.getX509KeyManager(); + + Assert.assertArrayEquals(new String[]{"client"}, + keyManager.getClientAliases(null, null)); + Assert.assertEquals("client", + keyManager.chooseClientAlias(null, null, null)); + Assert.assertNull(keyManager.getServerAliases(null, null)); + Assert.assertNull(keyManager.chooseServerAlias(null, null, null)); + + byte[] truststoreBytes = proxyCA.getChildTrustStore("password"); + KeyStore truststore = KeyStoreTestUtil.bytesToKeyStore(truststoreBytes, + "password"); + Assert.assertEquals(1, truststore.size()); + X509Certificate caCert = + (X509Certificate) truststore.getCertificate("client"); + Assert.assertArrayEquals(new X509Certificate[]{caCert}, + keyManager.getCertificateChain(null)); + Assert.assertEquals(proxyCA.getCaCert(), caCert); + + PrivateKey caPrivateKey = keyManager.getPrivateKey(null); + PublicKey caPublicKey = caCert.getPublicKey(); + checkPrivatePublicKeys(caPrivateKey, caPublicKey); + Assert.assertEquals(proxyCA.getCaKeyPair().getPublic(), caPublicKey); + Assert.assertEquals(proxyCA.getCaKeyPair().getPrivate(), caPrivateKey); + } + + @Test + public void testCreateHostnameVerifier() throws Exception { + ProxyCA proxyCA = new ProxyCA(); + proxyCA.init(); + HostnameVerifier verifier = proxyCA.getHostnameVerifier(); + + SSLSession sslSession = Mockito.mock(SSLSession.class); + Mockito.when(sslSession.getPeerCertificates()).thenReturn( + KeyStoreTestUtil.bytesToKeyStore( + proxyCA.createChildKeyStore( + ApplicationId.newInstance(System.currentTimeMillis(), 1), + "password"), "password").getCertificateChain("server")); + Assert.assertTrue(verifier.verify("foo", sslSession)); + } + + @Test + public void testCreateHostnameVerifierSSLPeerUnverifiedException() + throws Exception { + ProxyCA proxyCA = new ProxyCA(); + proxyCA.init(); + HostnameVerifier verifier = proxyCA.getHostnameVerifier(); + + SSLSession sslSession = Mockito.mock(SSLSession.class); + Mockito.when(sslSession.getPeerCertificates()).thenThrow( + new SSLPeerUnverifiedException("")); + Assert.assertFalse(verifier.verify("foo", sslSession)); + } + + @Test + public void testCreateHostnameVerifierWrongRM() throws Exception { + ProxyCA proxyCA = new ProxyCA(); + proxyCA.init(); + HostnameVerifier verifier = proxyCA.getHostnameVerifier(); + + SSLSession sslSession = Mockito.mock(SSLSession.class); + ProxyCA proxyCA2 = new ProxyCA(); // Simulate another RM + proxyCA2.init(); + Mockito.when(sslSession.getPeerCertificates()).thenReturn( + KeyStoreTestUtil.bytesToKeyStore( + proxyCA2.createChildKeyStore( + ApplicationId.newInstance(System.currentTimeMillis(), 1), + "password"), "password").getCertificateChain("server")); + Assert.assertFalse(verifier.verify("foo", sslSession)); + } + + @Test + public void testCreateHostnameVerifierExceptions() throws Exception { + ProxyCA proxyCA = new ProxyCA(); + proxyCA.init(); + HostnameVerifier verifier = proxyCA.getHostnameVerifier(); + + for (Exception e : new Exception[]{ + new CertificateException(), new NoSuchAlgorithmException(), + new InvalidKeyException(), new SignatureException(), + new NoSuchProviderException()}) { + SSLSession sslSession = Mockito.mock(SSLSession.class); + Mockito.when(sslSession.getPeerCertificates()).thenAnswer( + new Answer<Certificate[]>() { + @Override + public Certificate[] answer(InvocationOnMock invocation) + throws Throwable { + Certificate[] certChain = KeyStoreTestUtil.bytesToKeyStore( + proxyCA.createChildKeyStore( + ApplicationId.newInstance(System.currentTimeMillis(), 1), + "password"), "password").getCertificateChain("server"); + Certificate cert = Mockito.spy(certChain[0]); + certChain[0] = cert; + // Throw e to simulate problems with verifying + Mockito.doThrow(e).when(cert).verify(Mockito.any()); + return certChain; + } + }); + Assert.assertFalse(verifier.verify("foo", sslSession)); + } + } + + @Test + public void testCreateHostnameVerifierRealCert() throws Exception { + ProxyCA proxyCA = new ProxyCA(); + proxyCA.init(); + HostnameVerifier verifier = proxyCA.getHostnameVerifier(); + + SSLSession sslSession = Mockito.mock(SSLSession.class); + Mockito.when(sslSession.getPeerCertificates()).thenAnswer( + new Answer<Certificate[]>() { + @Override + public Certificate[] answer(InvocationOnMock invocation) + throws Throwable { + // "real" cert + Certificate[] certChain = new Certificate[]{ + KeyStoreTestUtil.generateCertificate("CN=foo.com", + KeyStoreTestUtil.generateKeyPair("RSA"), 30, "SHA1withRSA") + }; + return certChain; + } + }); + Assert.assertTrue(verifier.verify("foo.com", sslSession)); + } + + @Test + public void testCreateHostnameVerifierRealCertBad() throws Exception { + ProxyCA proxyCA = new ProxyCA(); + proxyCA.init(); + HostnameVerifier verifier = proxyCA.getHostnameVerifier(); + + SSLSession sslSession = Mockito.mock(SSLSession.class); + Mockito.when(sslSession.getPeerCertificates()).thenAnswer( + new Answer<Certificate[]>() { + @Override + public Certificate[] answer(InvocationOnMock invocation) + throws Throwable { + // "real" cert + Certificate[] certChain = new Certificate[]{ + KeyStoreTestUtil.generateCertificate("CN=foo.com", + KeyStoreTestUtil.generateKeyPair("RSA"), 30, "SHA1withRSA") + }; + return certChain; + } + }); + Assert.assertFalse(verifier.verify("bar.com", sslSession)); + } + + private void checkCACert(X509Certificate caCert) { + Assert.assertEquals(caCert.getSubjectX500Principal().toString(), + caCert.getIssuerDN().toString()); + Assert.assertEquals(caCert.getSubjectX500Principal().toString(), + caCert.getSubjectDN().toString()); + Assert.assertTrue("Expected CA certificate X500 Principal to start with" + + " 'OU=YARN-', but did not: " + caCert.getSubjectX500Principal(), + caCert.getSubjectX500Principal().toString().startsWith("OU=YARN-")); + Assert.assertFalse("Found multiple fields in X500 Principal, when there " + + "should have only been one: " + caCert.getSubjectX500Principal(), + caCert.getSubjectX500Principal().toString().contains(",")); + Assert.assertEquals("SHA512withRSA", caCert.getSigAlgName()); + Assert.assertEquals( + new GregorianCalendar(2037, Calendar.DECEMBER, 31).getTime(), + caCert.getNotAfter()); + Assert.assertTrue("Expected certificate to have started but was not: " + + caCert.getNotBefore(), caCert.getNotBefore().before(new Date())); + Assert.assertEquals(0, caCert.getBasicConstraints()); + } + + private void checkPrivatePublicKeys(PrivateKey privateKey, + PublicKey publicKey) throws NoSuchAlgorithmException, InvalidKeyException, + SignatureException { + byte[] data = new byte[2000]; + new Random().nextBytes(data); + Signature signer = Signature.getInstance("SHA512withRSA"); + signer.initSign(privateKey); + signer.update(data); + byte[] sig = signer.sign(); + signer = Signature.getInstance("SHA512withRSA"); + signer.initVerify(publicKey); + signer.update(data); + Assert.assertTrue(signer.verify(sig)); + } + + private X509Certificate[] castCertificateArrayToX509CertificateArray( + Certificate[] certs) { + return Arrays.copyOf(certs, certs.length, X509Certificate[].class); + } +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/c2288ac4/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/TestWebAppProxyServlet.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/TestWebAppProxyServlet.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/TestWebAppProxyServlet.java index fc97387..f04bc9a 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/TestWebAppProxyServlet.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/TestWebAppProxyServlet.java @@ -27,6 +27,8 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.StringWriter; import java.net.ConnectException; import java.net.HttpCookie; import java.net.HttpURLConnection; @@ -54,6 +56,7 @@ import org.apache.hadoop.yarn.api.records.impl.pb.ApplicationReportPBImpl; import org.apache.hadoop.yarn.conf.YarnConfiguration; import org.apache.hadoop.yarn.exceptions.ApplicationNotFoundException; import org.apache.hadoop.yarn.exceptions.YarnException; +import org.apache.hadoop.yarn.webapp.MimeType; import org.apache.hadoop.yarn.webapp.util.WebAppUtils; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.servlet.ServletContextHandler; @@ -63,6 +66,7 @@ import org.junit.BeforeClass; import org.junit.Test; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletHolder; +import org.mockito.Mockito; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -333,12 +337,13 @@ public class TestWebAppProxyServlet { assertEquals(proxyConn.getRequestProperties().size(), 4); proxyConn.connect(); assertEquals(HttpURLConnection.HTTP_OK, proxyConn.getResponseCode()); - // Verify if number of headers received by end server is 8. - // Eight headers include Accept, Host, Connection, User-Agent, Cookie, - // Origin, Access-Control-Request-Method and + // Verify if number of headers received by end server is 9. + // This should match WebAppProxyServlet#PASS_THROUGH_HEADERS. + // Nine headers include Accept, Host, Connection, User-Agent, Cookie, + // Origin, Access-Control-Request-Method, Accept-Encoding, and // Access-Control-Request-Headers. Pls note that Unknown-Header is dropped // by proxy as it is not in the list of allowed headers. - assertEquals(numberOfHeaders, 8); + assertEquals(numberOfHeaders, 9); assertFalse(hasUnknownHeader); } finally { proxy.close(); @@ -383,6 +388,51 @@ public class TestWebAppProxyServlet { } } + @Test(timeout=5000) + public void testCheckHttpsStrictAndNotProvided() throws Exception { + HttpServletResponse resp = Mockito.mock(HttpServletResponse.class); + StringWriter sw = new StringWriter(); + Mockito.when(resp.getWriter()).thenReturn(new PrintWriter(sw)); + YarnConfiguration conf = new YarnConfiguration(); + final URI httpLink = new URI("http://foo.com"); + final URI httpsLink = new URI("https://foo.com"); + + // NONE policy + conf.set(YarnConfiguration.RM_APPLICATION_HTTPS_POLICY, "NONE"); + assertFalse(WebAppProxyServlet.checkHttpsStrictAndNotProvided( + resp, httpsLink, conf)); + assertEquals("", sw.toString()); + Mockito.verify(resp, Mockito.times(0)).setContentType(Mockito.any()); + assertFalse(WebAppProxyServlet.checkHttpsStrictAndNotProvided( + resp, httpLink, conf)); + assertEquals("", sw.toString()); + Mockito.verify(resp, Mockito.times(0)).setContentType(Mockito.any()); + + // LENIENT policy + conf.set(YarnConfiguration.RM_APPLICATION_HTTPS_POLICY, "LENIENT"); + assertFalse(WebAppProxyServlet.checkHttpsStrictAndNotProvided( + resp, httpsLink, conf)); + assertEquals("", sw.toString()); + Mockito.verify(resp, Mockito.times(0)).setContentType(Mockito.any()); + assertFalse(WebAppProxyServlet.checkHttpsStrictAndNotProvided( + resp, httpLink, conf)); + assertEquals("", sw.toString()); + Mockito.verify(resp, Mockito.times(0)).setContentType(Mockito.any()); + + // STRICT policy + conf.set(YarnConfiguration.RM_APPLICATION_HTTPS_POLICY, "STRICT"); + assertFalse(WebAppProxyServlet.checkHttpsStrictAndNotProvided( + resp, httpsLink, conf)); + assertEquals("", sw.toString()); + Mockito.verify(resp, Mockito.times(0)).setContentType(Mockito.any()); + assertTrue(WebAppProxyServlet.checkHttpsStrictAndNotProvided( + resp, httpLink, conf)); + String s = sw.toString(); + assertTrue("Was expecting an HTML page explaining that an HTTPS tracking" + + " url must be used but found " + s, s.contains("HTTPS must be used")); + Mockito.verify(resp, Mockito.times(1)).setContentType(MimeType.HTML); + } + private String readInputStream(InputStream input) throws Exception { ByteArrayOutputStream data = new ByteArrayOutputStream(); byte[] buffer = new byte[512]; --------------------------------------------------------------------- To unsubscribe, e-mail: common-commits-unsubscr...@hadoop.apache.org For additional commands, e-mail: common-commits-h...@hadoop.apache.org