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 }