PageRenderTime 261ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/src/java/org/apache/hadoop/security/SecurityUtil.java

https://github.com/RS1999ent/hadoop-common
Java | 294 lines | 152 code | 23 blank | 119 comment | 41 complexity | b48f7d122e8e99edb0f24a0f45de8307 MD5 | raw file
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with this
  4. * work for additional information regarding copyright ownership. The ASF
  5. * licenses this file to you under the Apache License, Version 2.0 (the
  6. * "License"); you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  13. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  14. * License for the specific language governing permissions and limitations under
  15. * the License.
  16. */
  17. package org.apache.hadoop.security;
  18. import java.io.IOException;
  19. import java.net.InetAddress;
  20. import java.net.URI;
  21. import java.net.URL;
  22. import java.net.UnknownHostException;
  23. import java.security.AccessController;
  24. import java.util.Set;
  25. import javax.security.auth.Subject;
  26. import javax.security.auth.kerberos.KerberosPrincipal;
  27. import javax.security.auth.kerberos.KerberosTicket;
  28. import org.apache.commons.logging.Log;
  29. import org.apache.commons.logging.LogFactory;
  30. import org.apache.hadoop.classification.InterfaceAudience;
  31. import org.apache.hadoop.classification.InterfaceStability;
  32. import org.apache.hadoop.conf.Configuration;
  33. import org.apache.hadoop.security.UserGroupInformation;
  34. import org.apache.hadoop.net.NetUtils;
  35. import sun.security.jgss.krb5.Krb5Util;
  36. import sun.security.krb5.Credentials;
  37. import sun.security.krb5.PrincipalName;
  38. @InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"})
  39. @InterfaceStability.Evolving
  40. public class SecurityUtil {
  41. public static final Log LOG = LogFactory.getLog(SecurityUtil.class);
  42. public static final String HOSTNAME_PATTERN = "_HOST";
  43. /**
  44. * Find the original TGT within the current subject's credentials. Cross-realm
  45. * TGT's of the form "krbtgt/TWO.COM@ONE.COM" may be present.
  46. *
  47. * @return The TGT from the current subject
  48. * @throws IOException
  49. * if TGT can't be found
  50. */
  51. private static KerberosTicket getTgtFromSubject() throws IOException {
  52. Subject current = Subject.getSubject(AccessController.getContext());
  53. if (current == null) {
  54. throw new IOException(
  55. "Can't get TGT from current Subject, because it is null");
  56. }
  57. Set<KerberosTicket> tickets = current
  58. .getPrivateCredentials(KerberosTicket.class);
  59. for (KerberosTicket t : tickets) {
  60. if (isOriginalTGT(t))
  61. return t;
  62. }
  63. throw new IOException("Failed to find TGT from current Subject");
  64. }
  65. /**
  66. * TGS must have the server principal of the form "krbtgt/FOO@FOO".
  67. * @param principal
  68. * @return true or false
  69. */
  70. static boolean
  71. isTGSPrincipal(KerberosPrincipal principal) {
  72. if (principal == null)
  73. return false;
  74. if (principal.getName().equals("krbtgt/" + principal.getRealm() +
  75. "@" + principal.getRealm())) {
  76. return true;
  77. }
  78. return false;
  79. }
  80. /**
  81. * Check whether the server principal is the TGS's principal
  82. * @param ticket the original TGT (the ticket that is obtained when a
  83. * kinit is done)
  84. * @return true or false
  85. */
  86. protected static boolean isOriginalTGT(KerberosTicket ticket) {
  87. return isTGSPrincipal(ticket.getServer());
  88. }
  89. /**
  90. * Explicitly pull the service ticket for the specified host. This solves a
  91. * problem with Java's Kerberos SSL problem where the client cannot
  92. * authenticate against a cross-realm service. It is necessary for clients
  93. * making kerberized https requests to call this method on the target URL
  94. * to ensure that in a cross-realm environment the remote host will be
  95. * successfully authenticated.
  96. *
  97. * This method is internal to Hadoop and should not be used by other
  98. * applications. This method should not be considered stable or open:
  99. * it will be removed when the Java behavior is changed.
  100. *
  101. * @param remoteHost Target URL the krb-https client will access
  102. * @throws IOException
  103. */
  104. public static void fetchServiceTicket(URL remoteHost) throws IOException {
  105. if(!UserGroupInformation.isSecurityEnabled())
  106. return;
  107. String serviceName = "host/" + remoteHost.getHost();
  108. if (LOG.isDebugEnabled())
  109. LOG.debug("Fetching service ticket for host at: " + serviceName);
  110. Credentials serviceCred = null;
  111. try {
  112. PrincipalName principal = new PrincipalName(serviceName,
  113. PrincipalName.KRB_NT_SRV_HST);
  114. serviceCred = Credentials.acquireServiceCreds(principal
  115. .toString(), Krb5Util.ticketToCreds(getTgtFromSubject()));
  116. } catch (Exception e) {
  117. throw new IOException("Can't get service ticket for: "
  118. + serviceName, e);
  119. }
  120. if (serviceCred == null) {
  121. throw new IOException("Can't get service ticket for " + serviceName);
  122. }
  123. Subject.getSubject(AccessController.getContext()).getPrivateCredentials()
  124. .add(Krb5Util.credsToTicket(serviceCred));
  125. }
  126. /**
  127. * Convert Kerberos principal name pattern to valid Kerberos principal
  128. * names. It replaces hostname pattern with hostname, which should be
  129. * fully-qualified domain name. If hostname is null or "0.0.0.0", it uses
  130. * dynamically looked-up fqdn of the current host instead.
  131. *
  132. * @param principalConfig
  133. * the Kerberos principal name conf value to convert
  134. * @param hostname
  135. * the fully-qualified domain name used for substitution
  136. * @return converted Kerberos principal name
  137. * @throws IOException
  138. */
  139. public static String getServerPrincipal(String principalConfig,
  140. String hostname) throws IOException {
  141. String[] components = getComponents(principalConfig);
  142. if (components == null || components.length != 3
  143. || !components[1].equals(HOSTNAME_PATTERN)) {
  144. return principalConfig;
  145. } else {
  146. return replacePattern(components, hostname);
  147. }
  148. }
  149. /**
  150. * Convert Kerberos principal name pattern to valid Kerberos principal names.
  151. * This method is similar to {@link #getServerPrincipal(String, String)},
  152. * except 1) the reverse DNS lookup from addr to hostname is done only when
  153. * necessary, 2) param addr can't be null (no default behavior of using local
  154. * hostname when addr is null).
  155. *
  156. * @param principalConfig
  157. * Kerberos principal name pattern to convert
  158. * @param addr
  159. * InetAddress of the host used for substitution
  160. * @return converted Kerberos principal name
  161. * @throws IOException
  162. */
  163. public static String getServerPrincipal(String principalConfig,
  164. InetAddress addr) throws IOException {
  165. String[] components = getComponents(principalConfig);
  166. if (components == null || components.length != 3
  167. || !components[1].equals(HOSTNAME_PATTERN)) {
  168. return principalConfig;
  169. } else {
  170. if (addr == null) {
  171. throw new IOException("Can't replace " + HOSTNAME_PATTERN
  172. + " pattern since client address is null");
  173. }
  174. return replacePattern(components, addr.getCanonicalHostName());
  175. }
  176. }
  177. private static String[] getComponents(String principalConfig) {
  178. if (principalConfig == null)
  179. return null;
  180. return principalConfig.split("[/@]");
  181. }
  182. private static String replacePattern(String[] components, String hostname)
  183. throws IOException {
  184. String fqdn = hostname;
  185. if (fqdn == null || fqdn.equals("") || fqdn.equals("0.0.0.0")) {
  186. fqdn = getLocalHostName();
  187. }
  188. return components[0] + "/" + fqdn + "@" + components[2];
  189. }
  190. static String getLocalHostName() throws UnknownHostException {
  191. return InetAddress.getLocalHost().getCanonicalHostName();
  192. }
  193. /**
  194. * Login as a principal specified in config. Substitute $host in
  195. * user's Kerberos principal name with a dynamically looked-up fully-qualified
  196. * domain name of the current host.
  197. *
  198. * @param conf
  199. * conf to use
  200. * @param keytabFileKey
  201. * the key to look for keytab file in conf
  202. * @param userNameKey
  203. * the key to look for user's Kerberos principal name in conf
  204. * @throws IOException
  205. */
  206. public static void login(final Configuration conf,
  207. final String keytabFileKey, final String userNameKey) throws IOException {
  208. login(conf, keytabFileKey, userNameKey, getLocalHostName());
  209. }
  210. /**
  211. * Login as a principal specified in config. Substitute $host in user's Kerberos principal
  212. * name with hostname. If non-secure mode - return. If no keytab available -
  213. * bail out with an exception
  214. *
  215. * @param conf
  216. * conf to use
  217. * @param keytabFileKey
  218. * the key to look for keytab file in conf
  219. * @param userNameKey
  220. * the key to look for user's Kerberos principal name in conf
  221. * @param hostname
  222. * hostname to use for substitution
  223. * @throws IOException
  224. */
  225. public static void login(final Configuration conf,
  226. final String keytabFileKey, final String userNameKey, String hostname)
  227. throws IOException {
  228. if(! UserGroupInformation.isSecurityEnabled())
  229. return;
  230. String keytabFilename = conf.get(keytabFileKey);
  231. if (keytabFilename == null || keytabFilename.length() == 0) {
  232. throw new IOException("Running in secure mode, but config doesn't have a keytab");
  233. }
  234. String principalConfig = conf.get(userNameKey, System
  235. .getProperty("user.name"));
  236. String principalName = SecurityUtil.getServerPrincipal(principalConfig,
  237. hostname);
  238. UserGroupInformation.loginUserFromKeytab(principalName, keytabFilename);
  239. }
  240. /**
  241. * create service name for Delegation token ip:port
  242. * @param uri
  243. * @param defPort
  244. * @return "ip:port"
  245. */
  246. public static String buildDTServiceName(URI uri, int defPort) {
  247. int port = uri.getPort();
  248. if(port == -1)
  249. port = defPort;
  250. // build the service name string "/ip:port"
  251. // for whatever reason using NetUtils.createSocketAddr(target).toString()
  252. // returns "localhost/ip:port"
  253. StringBuffer sb = new StringBuffer();
  254. String host = uri.getHost();
  255. if (host != null) {
  256. host = NetUtils.normalizeHostName(host);
  257. } else {
  258. host = "";
  259. }
  260. sb.append(host).append(":").append(port);
  261. return sb.toString();
  262. }
  263. /**
  264. * Get the host name from the principal name of format <service>/host@realm.
  265. * @param principalName principal name of format as described above
  266. * @return host name if the the string conforms to the above format, else null
  267. */
  268. public static String getHostFromPrincipal(String principalName) {
  269. return new KerberosName(principalName).getHostName();
  270. }
  271. }