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