001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with this 004 * work for additional information regarding copyright ownership. The ASF 005 * licenses this file to you under the Apache License, Version 2.0 (the 006 * "License"); you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 013 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 014 * License for the specific language governing permissions and limitations under 015 * the License. 016 */ 017 package org.apache.hadoop.hdfs.server.datanode; 018 019 import java.io.IOException; 020 import java.net.InetSocketAddress; 021 import java.net.ServerSocket; 022 import java.nio.channels.ServerSocketChannel; 023 import java.security.GeneralSecurityException; 024 025 import org.apache.commons.daemon.Daemon; 026 import org.apache.commons.daemon.DaemonContext; 027 import org.apache.hadoop.conf.Configuration; 028 029 import org.apache.hadoop.hdfs.DFSConfigKeys; 030 import org.apache.hadoop.hdfs.server.common.HdfsServerConstants; 031 import org.apache.hadoop.http.HttpConfig; 032 import org.apache.hadoop.http.HttpServer; 033 import org.apache.hadoop.security.UserGroupInformation; 034 import org.apache.hadoop.security.ssl.SSLFactory; 035 import org.mortbay.jetty.Connector; 036 import org.mortbay.jetty.nio.SelectChannelConnector; 037 import org.mortbay.jetty.security.SslSocketConnector; 038 039 import javax.net.ssl.SSLServerSocketFactory; 040 041 import com.google.common.annotations.VisibleForTesting; 042 043 /** 044 * Utility class to start a datanode in a secure cluster, first obtaining 045 * privileged resources before main startup and handing them to the datanode. 046 */ 047 public class SecureDataNodeStarter implements Daemon { 048 /** 049 * Stash necessary resources needed for datanode operation in a secure env. 050 */ 051 public static class SecureResources { 052 private final ServerSocket streamingSocket; 053 private final Connector listener; 054 public SecureResources(ServerSocket streamingSocket, 055 Connector listener) { 056 057 this.streamingSocket = streamingSocket; 058 this.listener = listener; 059 } 060 061 public ServerSocket getStreamingSocket() { return streamingSocket; } 062 063 public Connector getListener() { return listener; } 064 } 065 066 private String [] args; 067 private SecureResources resources; 068 private SSLFactory sslFactory; 069 070 @Override 071 public void init(DaemonContext context) throws Exception { 072 System.err.println("Initializing secure datanode resources"); 073 Configuration conf = new Configuration(); 074 075 // Stash command-line arguments for regular datanode 076 args = context.getArguments(); 077 078 sslFactory = new SSLFactory(SSLFactory.Mode.SERVER, conf); 079 resources = getSecureResources(sslFactory, conf); 080 } 081 082 @Override 083 public void start() throws Exception { 084 System.err.println("Starting regular datanode initialization"); 085 DataNode.secureMain(args, resources); 086 } 087 088 @Override public void destroy() { 089 sslFactory.destroy(); 090 } 091 092 @Override public void stop() throws Exception { /* Nothing to do */ } 093 094 @VisibleForTesting 095 public static SecureResources getSecureResources(final SSLFactory sslFactory, 096 Configuration conf) throws Exception { 097 // Obtain secure port for data streaming to datanode 098 InetSocketAddress streamingAddr = DataNode.getStreamingAddr(conf); 099 int socketWriteTimeout = conf.getInt(DFSConfigKeys.DFS_DATANODE_SOCKET_WRITE_TIMEOUT_KEY, 100 HdfsServerConstants.WRITE_TIMEOUT); 101 102 ServerSocket ss = (socketWriteTimeout > 0) ? 103 ServerSocketChannel.open().socket() : new ServerSocket(); 104 ss.bind(streamingAddr, 0); 105 106 // Check that we got the port we need 107 if (ss.getLocalPort() != streamingAddr.getPort()) { 108 throw new RuntimeException("Unable to bind on specified streaming port in secure " + 109 "context. Needed " + streamingAddr.getPort() + ", got " + ss.getLocalPort()); 110 } 111 112 // Obtain secure listener for web server 113 Connector listener; 114 if (HttpConfig.isSecure()) { 115 try { 116 sslFactory.init(); 117 } catch (GeneralSecurityException ex) { 118 throw new IOException(ex); 119 } 120 SslSocketConnector sslListener = new SslSocketConnector() { 121 @Override 122 protected SSLServerSocketFactory createFactory() throws Exception { 123 return sslFactory.createSSLServerSocketFactory(); 124 } 125 }; 126 listener = sslListener; 127 } else { 128 listener = HttpServer.createDefaultChannelConnector(); 129 } 130 131 InetSocketAddress infoSocAddr = DataNode.getInfoAddr(conf); 132 listener.setHost(infoSocAddr.getHostName()); 133 listener.setPort(infoSocAddr.getPort()); 134 // Open listener here in order to bind to port as root 135 listener.open(); 136 if (listener.getPort() != infoSocAddr.getPort()) { 137 throw new RuntimeException("Unable to bind on specified info port in secure " + 138 "context. Needed " + streamingAddr.getPort() + ", got " + ss.getLocalPort()); 139 } 140 System.err.println("Successfully obtained privileged resources (streaming port = " 141 + ss + " ) (http listener port = " + listener.getConnection() +")"); 142 143 if ((ss.getLocalPort() > 1023 || listener.getPort() > 1023) && 144 UserGroupInformation.isSecurityEnabled()) { 145 throw new RuntimeException("Cannot start secure datanode with unprivileged ports"); 146 } 147 System.err.println("Opened streaming server at " + streamingAddr); 148 System.err.println("Opened info server at " + infoSocAddr); 149 return new SecureResources(ss, listener); 150 } 151 152 }