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 */ 017package org.apache.hadoop.hdfs.server.datanode; 018 019import java.net.InetSocketAddress; 020import java.net.ServerSocket; 021import java.nio.channels.ServerSocketChannel; 022 023import org.apache.commons.daemon.Daemon; 024import org.apache.commons.daemon.DaemonContext; 025import org.apache.hadoop.conf.Configuration; 026import org.apache.hadoop.fs.CommonConfigurationKeysPublic; 027import org.apache.hadoop.hdfs.DFSConfigKeys; 028import org.apache.hadoop.hdfs.DFSUtil; 029import org.apache.hadoop.hdfs.HdfsConfiguration; 030import org.apache.hadoop.hdfs.server.common.HdfsServerConstants; 031import org.apache.hadoop.http.HttpConfig; 032import org.apache.hadoop.http.HttpServer2; 033import org.apache.hadoop.security.UserGroupInformation; 034import org.mortbay.jetty.Connector; 035 036import com.google.common.annotations.VisibleForTesting; 037 038/** 039 * Utility class to start a datanode in a secure cluster, first obtaining 040 * privileged resources before main startup and handing them to the datanode. 041 */ 042public class SecureDataNodeStarter implements Daemon { 043 /** 044 * Stash necessary resources needed for datanode operation in a secure env. 045 */ 046 public static class SecureResources { 047 private final ServerSocket streamingSocket; 048 private final Connector listener; 049 public SecureResources(ServerSocket streamingSocket, 050 Connector listener) { 051 052 this.streamingSocket = streamingSocket; 053 this.listener = listener; 054 } 055 056 public ServerSocket getStreamingSocket() { return streamingSocket; } 057 058 public Connector getListener() { return listener; } 059 } 060 061 private String [] args; 062 private SecureResources resources; 063 064 @Override 065 public void init(DaemonContext context) throws Exception { 066 System.err.println("Initializing secure datanode resources"); 067 // Create a new HdfsConfiguration object to ensure that the configuration in 068 // hdfs-site.xml is picked up. 069 Configuration conf = new HdfsConfiguration(); 070 071 // Stash command-line arguments for regular datanode 072 args = context.getArguments(); 073 resources = getSecureResources(conf); 074 } 075 076 @Override 077 public void start() throws Exception { 078 System.err.println("Starting regular datanode initialization"); 079 DataNode.secureMain(args, resources); 080 } 081 082 @Override public void destroy() {} 083 @Override public void stop() throws Exception { /* Nothing to do */ } 084 085 /** 086 * Acquire privileged resources (i.e., the privileged ports) for the data 087 * node. The privileged resources consist of the port of the RPC server and 088 * the port of HTTP (not HTTPS) server. 089 */ 090 @VisibleForTesting 091 public static SecureResources getSecureResources(Configuration conf) 092 throws Exception { 093 HttpConfig.Policy policy = DFSUtil.getHttpPolicy(conf); 094 boolean isSecure = UserGroupInformation.isSecurityEnabled(); 095 096 // Obtain secure port for data streaming to datanode 097 InetSocketAddress streamingAddr = DataNode.getStreamingAddr(conf); 098 int socketWriteTimeout = conf.getInt( 099 DFSConfigKeys.DFS_DATANODE_SOCKET_WRITE_TIMEOUT_KEY, 100 HdfsServerConstants.WRITE_TIMEOUT); 101 int backlogLength = conf.getInt( 102 CommonConfigurationKeysPublic.IPC_SERVER_LISTEN_QUEUE_SIZE_KEY, 103 CommonConfigurationKeysPublic.IPC_SERVER_LISTEN_QUEUE_SIZE_DEFAULT); 104 105 ServerSocket ss = (socketWriteTimeout > 0) ? 106 ServerSocketChannel.open().socket() : new ServerSocket(); 107 ss.bind(streamingAddr, backlogLength); 108 109 // Check that we got the port we need 110 if (ss.getLocalPort() != streamingAddr.getPort()) { 111 throw new RuntimeException( 112 "Unable to bind on specified streaming port in secure " 113 + "context. Needed " + streamingAddr.getPort() + ", got " 114 + ss.getLocalPort()); 115 } 116 117 if (ss.getLocalPort() > 1023 && isSecure) { 118 throw new RuntimeException( 119 "Cannot start secure datanode with unprivileged RPC ports"); 120 } 121 122 System.err.println("Opened streaming server at " + streamingAddr); 123 124 // Bind a port for the web server. The code intends to bind HTTP server to 125 // privileged port only, as the client can authenticate the server using 126 // certificates if they are communicating through SSL. 127 Connector listener = null; 128 if (policy.isHttpEnabled()) { 129 listener = HttpServer2.createDefaultChannelConnector(); 130 InetSocketAddress infoSocAddr = DataNode.getInfoAddr(conf); 131 listener.setHost(infoSocAddr.getHostName()); 132 listener.setPort(infoSocAddr.getPort()); 133 // Open listener here in order to bind to port as root 134 listener.open(); 135 if (listener.getPort() != infoSocAddr.getPort()) { 136 throw new RuntimeException("Unable to bind on specified info port in secure " + 137 "context. Needed " + streamingAddr.getPort() + ", got " + ss.getLocalPort()); 138 } 139 System.err.println("Successfully obtained privileged resources (streaming port = " 140 + ss + " ) (http listener port = " + listener.getConnection() +")"); 141 142 if (listener.getPort() > 1023 && isSecure) { 143 throw new RuntimeException( 144 "Cannot start secure datanode with unprivileged HTTP ports"); 145 } 146 System.err.println("Opened info server at " + infoSocAddr); 147 } 148 149 return new SecureResources(ss, listener); 150 } 151 152}