|  | /** | 
|  | * 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.hdfs; | 
|  |  | 
|  | import java.io.FileInputStream; | 
|  | import java.io.IOException; | 
|  | import java.net.HttpURLConnection; | 
|  | import java.net.URI; | 
|  | import java.net.URISyntaxException; | 
|  | import java.net.URL; | 
|  | import java.security.KeyStore; | 
|  | import java.security.cert.X509Certificate; | 
|  |  | 
|  | import javax.net.ssl.HostnameVerifier; | 
|  | import javax.net.ssl.HttpsURLConnection; | 
|  | import javax.net.ssl.KeyManager; | 
|  | import javax.net.ssl.KeyManagerFactory; | 
|  | import javax.net.ssl.SSLContext; | 
|  | import javax.net.ssl.SSLSession; | 
|  | import javax.net.ssl.TrustManager; | 
|  | import javax.net.ssl.TrustManagerFactory; | 
|  | import javax.net.ssl.X509TrustManager; | 
|  |  | 
|  | import org.apache.hadoop.classification.InterfaceAudience; | 
|  | import org.apache.hadoop.classification.InterfaceStability; | 
|  | import org.apache.hadoop.conf.Configuration; | 
|  |  | 
|  | /** | 
|  | * An implementation of a protocol for accessing filesystems over HTTPS. The | 
|  | * following implementation provides a limited, read-only interface to a | 
|  | * filesystem over HTTPS. | 
|  | * | 
|  | * @see org.apache.hadoop.hdfs.server.namenode.ListPathsServlet | 
|  | * @see org.apache.hadoop.hdfs.server.namenode.FileDataServlet | 
|  | */ | 
|  | @InterfaceAudience.Private | 
|  | @InterfaceStability.Evolving | 
|  | public class HsftpFileSystem extends HftpFileSystem { | 
|  |  | 
|  | private static final long MM_SECONDS_PER_DAY = 1000 * 60 * 60 * 24; | 
|  | private volatile int ExpWarnDays = 0; | 
|  |  | 
|  | @Override | 
|  | public void initialize(URI name, Configuration conf) throws IOException { | 
|  | super.initialize(name, conf); | 
|  | setupSsl(conf); | 
|  | ExpWarnDays = conf.getInt("ssl.expiration.warn.days", 30); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Set up SSL resources | 
|  | * | 
|  | * @throws IOException | 
|  | */ | 
|  | private static void setupSsl(Configuration conf) throws IOException { | 
|  | Configuration sslConf = new HdfsConfiguration(false); | 
|  | sslConf.addResource(conf.get(DFSConfigKeys.DFS_CLIENT_HTTPS_KEYSTORE_RESOURCE_KEY, | 
|  | DFSConfigKeys.DFS_CLIENT_HTTPS_KEYSTORE_RESOURCE_DEFAULT)); | 
|  | FileInputStream fis = null; | 
|  | try { | 
|  | SSLContext sc = SSLContext.getInstance("SSL"); | 
|  | KeyManager[] kms = null; | 
|  | TrustManager[] tms = null; | 
|  | if (sslConf.get("ssl.client.keystore.location") != null) { | 
|  | // initialize default key manager with keystore file and pass | 
|  | KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); | 
|  | KeyStore ks = KeyStore.getInstance(sslConf.get( | 
|  | "ssl.client.keystore.type", "JKS")); | 
|  | char[] ksPass = sslConf.get("ssl.client.keystore.password", "changeit") | 
|  | .toCharArray(); | 
|  | fis = new FileInputStream(sslConf.get("ssl.client.keystore.location", | 
|  | "keystore.jks")); | 
|  | ks.load(fis, ksPass); | 
|  | kmf.init(ks, sslConf.get("ssl.client.keystore.keypassword", "changeit") | 
|  | .toCharArray()); | 
|  | kms = kmf.getKeyManagers(); | 
|  | fis.close(); | 
|  | fis = null; | 
|  | } | 
|  | // initialize default trust manager with truststore file and pass | 
|  | if (sslConf.getBoolean("ssl.client.do.not.authenticate.server", false)) { | 
|  | // by pass trustmanager validation | 
|  | tms = new DummyTrustManager[] { new DummyTrustManager() }; | 
|  | } else { | 
|  | TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX"); | 
|  | KeyStore ts = KeyStore.getInstance(sslConf.get( | 
|  | "ssl.client.truststore.type", "JKS")); | 
|  | char[] tsPass = sslConf.get("ssl.client.truststore.password", | 
|  | "changeit").toCharArray(); | 
|  | fis = new FileInputStream(sslConf.get("ssl.client.truststore.location", | 
|  | "truststore.jks")); | 
|  | ts.load(fis, tsPass); | 
|  | tmf.init(ts); | 
|  | tms = tmf.getTrustManagers(); | 
|  | } | 
|  | sc.init(kms, tms, new java.security.SecureRandom()); | 
|  | HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); | 
|  | } catch (Exception e) { | 
|  | throw new IOException("Could not initialize SSLContext", e); | 
|  | } finally { | 
|  | if (fis != null) { | 
|  | fis.close(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected HttpURLConnection openConnection(String path, String query) | 
|  | throws IOException { | 
|  | try { | 
|  | query = updateQuery(query); | 
|  | final URL url = new URI("https", null, nnAddr.getHostName(), nnAddr | 
|  | .getPort(), path, query, null).toURL(); | 
|  | HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); | 
|  | // bypass hostname verification | 
|  | conn.setHostnameVerifier(new DummyHostnameVerifier()); | 
|  | conn.setRequestMethod("GET"); | 
|  | conn.connect(); | 
|  |  | 
|  | // check cert expiration date | 
|  | final int warnDays = ExpWarnDays; | 
|  | if (warnDays > 0) { // make sure only check once | 
|  | ExpWarnDays = 0; | 
|  | long expTimeThreshold = warnDays * MM_SECONDS_PER_DAY | 
|  | + System.currentTimeMillis(); | 
|  | X509Certificate[] clientCerts = (X509Certificate[]) conn | 
|  | .getLocalCertificates(); | 
|  | if (clientCerts != null) { | 
|  | for (X509Certificate cert : clientCerts) { | 
|  | long expTime = cert.getNotAfter().getTime(); | 
|  | if (expTime < expTimeThreshold) { | 
|  | StringBuilder sb = new StringBuilder(); | 
|  | sb.append("\n Client certificate " | 
|  | + cert.getSubjectX500Principal().getName()); | 
|  | int dayOffSet = (int) ((expTime - System.currentTimeMillis()) / MM_SECONDS_PER_DAY); | 
|  | sb.append(" have " + dayOffSet + " days to expire"); | 
|  | LOG.warn(sb.toString()); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | return (HttpURLConnection) conn; | 
|  | } catch (URISyntaxException e) { | 
|  | throw (IOException) new IOException().initCause(e); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public URI getUri() { | 
|  | try { | 
|  | return new URI("hsftp", null, nnAddr.getHostName(), nnAddr.getPort(), | 
|  | null, null, null); | 
|  | } catch (URISyntaxException e) { | 
|  | return null; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Dummy hostname verifier that is used to bypass hostname checking | 
|  | */ | 
|  | protected static class DummyHostnameVerifier implements HostnameVerifier { | 
|  | public boolean verify(String hostname, SSLSession session) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Dummy trustmanager that is used to trust all server certificates | 
|  | */ | 
|  | protected static class DummyTrustManager implements X509TrustManager { | 
|  | public void checkClientTrusted(X509Certificate[] chain, String authType) { | 
|  | } | 
|  |  | 
|  | public void checkServerTrusted(X509Certificate[] chain, String authType) { | 
|  | } | 
|  |  | 
|  | public X509Certificate[] getAcceptedIssuers() { | 
|  | return null; | 
|  | } | 
|  | } | 
|  |  | 
|  | } |