| /** |
| * 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.tools; |
| |
| import java.io.BufferedReader; |
| import java.io.DataInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.PrintStream; |
| import java.net.HttpURLConnection; |
| import java.net.InetSocketAddress; |
| import java.net.URL; |
| import java.net.URLConnection; |
| import java.security.PrivilegedExceptionAction; |
| import java.util.Collection; |
| |
| import org.apache.commons.cli.CommandLine; |
| import org.apache.commons.cli.Options; |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| import org.apache.hadoop.conf.Configuration; |
| import org.apache.hadoop.fs.FileSystem; |
| import org.apache.hadoop.fs.Path; |
| import org.apache.hadoop.hdfs.HftpFileSystem; |
| import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier; |
| import org.apache.hadoop.hdfs.server.namenode.CancelDelegationTokenServlet; |
| import org.apache.hadoop.hdfs.server.namenode.GetDelegationTokenServlet; |
| import org.apache.hadoop.hdfs.server.namenode.NameNode; |
| import org.apache.hadoop.hdfs.server.namenode.RenewDelegationTokenServlet; |
| import org.apache.hadoop.io.IOUtils; |
| import org.apache.hadoop.net.NetUtils; |
| import org.apache.hadoop.security.Credentials; |
| import org.apache.hadoop.security.SecurityUtil; |
| import org.apache.hadoop.security.UserGroupInformation; |
| import org.apache.hadoop.security.token.Token; |
| import org.apache.hadoop.util.GenericOptionsParser; |
| |
| /** |
| * Fetch a DelegationToken from the current Namenode and store it in the |
| * specified file. |
| */ |
| public class DelegationTokenFetcher { |
| |
| static{ |
| Configuration.addDefaultResource("hdfs-default.xml"); |
| Configuration.addDefaultResource("hdfs-site.xml"); |
| } |
| |
| private static final Log LOG = |
| LogFactory.getLog(DelegationTokenFetcher.class); |
| private static final String WEBSERVICE = "webservice"; |
| private static final String CANCEL = "cancel"; |
| private static final String RENEW = "renew"; |
| |
| static { |
| // Enable Kerberos sockets |
| System.setProperty("https.cipherSuites", "TLS_KRB5_WITH_3DES_EDE_CBC_SHA"); |
| } |
| |
| private static void printUsage(PrintStream err) throws IOException { |
| err.println("fetchdt retrieves delegation tokens from the NameNode"); |
| err.println(); |
| err.println("fetchdt <opts> <token file>"); |
| err.println("Options:"); |
| err.println(" --webservice <url> Url to contact NN on"); |
| err.println(" --cancel Cancel the delegation token"); |
| err.println(" --renew Renew the delegation token"); |
| err.println(); |
| GenericOptionsParser.printGenericCommandUsage(err); |
| System.exit(1); |
| } |
| |
| private static Collection<Token<?>> |
| readTokens(Path file, Configuration conf) throws IOException{ |
| Credentials creds = Credentials.readTokenStorageFile(file, conf); |
| return creds.getAllTokens(); |
| } |
| |
| /** |
| * Command-line interface |
| */ |
| public static void main(final String [] args) throws Exception { |
| final Configuration conf = new Configuration(); |
| Options fetcherOptions = new Options(); |
| fetcherOptions.addOption(WEBSERVICE, true, |
| "HTTP/S url to reach the NameNode at"); |
| fetcherOptions.addOption(CANCEL, false, "cancel the token"); |
| fetcherOptions.addOption(RENEW, false, "renew the token"); |
| GenericOptionsParser parser = |
| new GenericOptionsParser(conf, fetcherOptions, args); |
| CommandLine cmd = parser.getCommandLine(); |
| |
| // get options |
| final String webUrl = |
| cmd.hasOption(WEBSERVICE) ? cmd.getOptionValue(WEBSERVICE) : null; |
| final boolean cancel = cmd.hasOption(CANCEL); |
| final boolean renew = cmd.hasOption(RENEW); |
| String[] remaining = parser.getRemainingArgs(); |
| |
| // check option validity |
| if (cancel && renew) { |
| System.err.println("ERROR: Only specify cancel or renew."); |
| printUsage(System.err); |
| } |
| if (remaining.length != 1 || remaining[0].charAt(0) == '-') { |
| System.err.println("ERROR: Must specify exactly one token file"); |
| printUsage(System.err); |
| } |
| // default to using the local file system |
| FileSystem local = FileSystem.getLocal(conf); |
| final Path tokenFile = new Path(local.getWorkingDirectory(), remaining[0]); |
| |
| // Login the current user |
| final UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); |
| ugi.doAs(new PrivilegedExceptionAction<Object>() { |
| @Override |
| public Object run() throws Exception { |
| |
| if (cancel) { |
| for(Token<?> token: readTokens(tokenFile, conf)) { |
| if (token.isManaged()) { |
| token.cancel(conf); |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("Cancelled token for " + token.getService()); |
| } |
| } |
| } |
| } else if (renew) { |
| for(Token<?> token: readTokens(tokenFile, conf)) { |
| if (token.isManaged()) { |
| token.renew(conf); |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("Renewed token for " + token.getService()); |
| } |
| } |
| } |
| } else { |
| if (webUrl != null) { |
| getDTfromRemote(webUrl, null). |
| writeTokenStorageFile(tokenFile, conf); |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("Fetched token via http for " + webUrl); |
| } |
| } else { |
| FileSystem fs = FileSystem.get(conf); |
| Token<?> token = fs.getDelegationToken(ugi.getShortUserName()); |
| Credentials cred = new Credentials(); |
| cred.addToken(token.getService(), token); |
| cred.writeTokenStorageFile(tokenFile, conf); |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("Fetched token for " + fs.getUri() + " into " + |
| tokenFile); |
| } |
| } |
| } |
| return null; |
| } |
| }); |
| |
| } |
| |
| /** |
| * Utility method to obtain a delegation token over http |
| * @param nnAddr Namenode http addr, such as http://namenode:50070 |
| * @param renewer User that is renewing the ticket in such a request |
| */ |
| static public Credentials getDTfromRemote(String nnAddr, |
| String renewer) throws IOException { |
| DataInputStream dis = null; |
| InetSocketAddress serviceAddr = NetUtils.createSocketAddr(nnAddr); |
| |
| try { |
| StringBuffer url = new StringBuffer(); |
| if (renewer != null) { |
| url.append(nnAddr).append(GetDelegationTokenServlet.PATH_SPEC).append("?"). |
| append(GetDelegationTokenServlet.RENEWER).append("=").append(renewer); |
| } else { |
| url.append(nnAddr).append(GetDelegationTokenServlet.PATH_SPEC); |
| } |
| if(LOG.isDebugEnabled()) { |
| LOG.debug("Retrieving token from: " + url); |
| } |
| URL remoteURL = new URL(url.toString()); |
| URLConnection connection = SecurityUtil.openSecureHttpConnection(remoteURL); |
| |
| InputStream in = connection.getInputStream(); |
| Credentials ts = new Credentials(); |
| dis = new DataInputStream(in); |
| ts.readFields(dis); |
| for(Token<?> token: ts.getAllTokens()) { |
| token.setKind(HftpFileSystem.TOKEN_KIND); |
| SecurityUtil.setTokenService(token, serviceAddr); |
| } |
| return ts; |
| } catch (Exception e) { |
| throw new IOException("Unable to obtain remote token", e); |
| } finally { |
| if(dis != null) dis.close(); |
| } |
| } |
| |
| /** |
| * Renew a Delegation Token. |
| * @param nnAddr the NameNode's address |
| * @param tok the token to renew |
| * @return the Date that the token will expire next. |
| * @throws IOException |
| */ |
| static public long renewDelegationToken(String nnAddr, |
| Token<DelegationTokenIdentifier> tok) throws IOException { |
| StringBuilder buf = new StringBuilder(); |
| buf.append(nnAddr); |
| buf.append(RenewDelegationTokenServlet.PATH_SPEC); |
| buf.append("?"); |
| buf.append(RenewDelegationTokenServlet.TOKEN); |
| buf.append("="); |
| buf.append(tok.encodeToUrlString()); |
| BufferedReader in = null; |
| HttpURLConnection connection = null; |
| try { |
| URL url = new URL(buf.toString()); |
| connection = (HttpURLConnection) SecurityUtil.openSecureHttpConnection(url); |
| in = new BufferedReader(new InputStreamReader |
| (connection.getInputStream())); |
| long result = Long.parseLong(in.readLine()); |
| in.close(); |
| return result; |
| } catch (IOException ie) { |
| LOG.info("error in renew over HTTP", ie); |
| IOException e = null; |
| if(connection != null) { |
| String resp = connection.getResponseMessage(); |
| e = getExceptionFromResponse(resp); |
| } |
| |
| IOUtils.cleanup(LOG, in); |
| if(e!=null) { |
| LOG.info("rethrowing exception from HTTP request: " + e.getLocalizedMessage()); |
| throw e; |
| } |
| throw ie; |
| } |
| } |
| |
| static private IOException getExceptionFromResponse(String resp) { |
| String exceptionClass = "", exceptionMsg = ""; |
| if(resp != null && !resp.isEmpty()) { |
| String[] rs = resp.split(";"); |
| exceptionClass = rs[0]; |
| exceptionMsg = rs[1]; |
| } |
| LOG.info("Error response from HTTP request=" + resp + |
| ";ec=" + exceptionClass + ";em="+exceptionMsg); |
| IOException e = null; |
| if(exceptionClass != null && !exceptionClass.isEmpty()) { |
| if(exceptionClass.contains("InvalidToken")) { |
| e = new org.apache.hadoop.security.token.SecretManager.InvalidToken(exceptionMsg); |
| e.setStackTrace(new StackTraceElement[0]); // stack is not relevant |
| } else if(exceptionClass.contains("AccessControlException")) { |
| e = new org.apache.hadoop.security.AccessControlException(exceptionMsg); |
| e.setStackTrace(new StackTraceElement[0]); // stack is not relevant |
| } |
| } |
| LOG.info("Exception from HTTP response=" + e.getLocalizedMessage()); |
| return e; |
| } |
| |
| /** |
| * Cancel a Delegation Token. |
| * @param nnAddr the NameNode's address |
| * @param tok the token to cancel |
| * @throws IOException |
| */ |
| static public void cancelDelegationToken(String nnAddr, |
| Token<DelegationTokenIdentifier> tok) throws IOException { |
| StringBuilder buf = new StringBuilder(); |
| buf.append(nnAddr); |
| buf.append(CancelDelegationTokenServlet.PATH_SPEC); |
| buf.append("?"); |
| buf.append(CancelDelegationTokenServlet.TOKEN); |
| buf.append("="); |
| buf.append(tok.encodeToUrlString()); |
| BufferedReader in = null; |
| try { |
| URL url = new URL(buf.toString()); |
| HttpURLConnection connection = |
| (HttpURLConnection)SecurityUtil.openSecureHttpConnection(url); |
| if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { |
| throw new IOException("Error cancelling token:" + |
| connection.getResponseMessage()); |
| } |
| } catch (IOException ie) { |
| IOUtils.cleanup(LOG, in); |
| throw ie; |
| } |
| } |
| } |