001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.security.token.delegation.web; 019 020import org.apache.hadoop.classification.InterfaceAudience; 021import org.apache.hadoop.classification.InterfaceStability; 022import org.apache.hadoop.security.SecurityUtil; 023import org.apache.hadoop.security.UserGroupInformation; 024import org.apache.hadoop.security.authentication.client.AuthenticatedURL; 025import org.apache.hadoop.security.authentication.client.AuthenticationException; 026import org.apache.hadoop.security.authentication.client.Authenticator; 027import org.apache.hadoop.security.authentication.client.ConnectionConfigurator; 028import org.apache.hadoop.security.token.Token; 029import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenIdentifier; 030import org.apache.hadoop.util.HttpExceptionUtils; 031import org.apache.hadoop.util.StringUtils; 032import org.codehaus.jackson.map.ObjectMapper; 033import org.slf4j.Logger; 034import org.slf4j.LoggerFactory; 035 036import java.io.IOException; 037import java.net.HttpURLConnection; 038import java.net.InetSocketAddress; 039import java.net.URL; 040import java.net.URLEncoder; 041import java.util.HashMap; 042import java.util.Map; 043 044/** 045 * {@link Authenticator} wrapper that enhances an {@link Authenticator} with 046 * Delegation Token support. 047 */ 048@InterfaceAudience.Public 049@InterfaceStability.Evolving 050public abstract class DelegationTokenAuthenticator implements Authenticator { 051 private static Logger LOG = 052 LoggerFactory.getLogger(DelegationTokenAuthenticator.class); 053 054 private static final String CONTENT_TYPE = "Content-Type"; 055 private static final String APPLICATION_JSON_MIME = "application/json"; 056 057 private static final String HTTP_GET = "GET"; 058 private static final String HTTP_PUT = "PUT"; 059 060 public static final String OP_PARAM = "op"; 061 062 public static final String DELEGATION_TOKEN_HEADER = 063 "X-Hadoop-Delegation-Token"; 064 065 public static final String DELEGATION_PARAM = "delegation"; 066 public static final String TOKEN_PARAM = "token"; 067 public static final String RENEWER_PARAM = "renewer"; 068 public static final String DELEGATION_TOKEN_JSON = "Token"; 069 public static final String DELEGATION_TOKEN_URL_STRING_JSON = "urlString"; 070 public static final String RENEW_DELEGATION_TOKEN_JSON = "long"; 071 072 /** 073 * DelegationToken operations. 074 */ 075 @InterfaceAudience.Private 076 public static enum DelegationTokenOperation { 077 GETDELEGATIONTOKEN(HTTP_GET, true), 078 RENEWDELEGATIONTOKEN(HTTP_PUT, true), 079 CANCELDELEGATIONTOKEN(HTTP_PUT, false); 080 081 private String httpMethod; 082 private boolean requiresKerberosCredentials; 083 084 private DelegationTokenOperation(String httpMethod, 085 boolean requiresKerberosCredentials) { 086 this.httpMethod = httpMethod; 087 this.requiresKerberosCredentials = requiresKerberosCredentials; 088 } 089 090 public String getHttpMethod() { 091 return httpMethod; 092 } 093 094 public boolean requiresKerberosCredentials() { 095 return requiresKerberosCredentials; 096 } 097 } 098 099 private Authenticator authenticator; 100 private ConnectionConfigurator connConfigurator; 101 102 public DelegationTokenAuthenticator(Authenticator authenticator) { 103 this.authenticator = authenticator; 104 } 105 106 @Override 107 public void setConnectionConfigurator(ConnectionConfigurator configurator) { 108 authenticator.setConnectionConfigurator(configurator); 109 connConfigurator = configurator; 110 } 111 112 private boolean hasDelegationToken(URL url, AuthenticatedURL.Token token) { 113 boolean hasDt = false; 114 if (token instanceof DelegationTokenAuthenticatedURL.Token) { 115 hasDt = ((DelegationTokenAuthenticatedURL.Token) token). 116 getDelegationToken() != null; 117 } 118 if (!hasDt) { 119 String queryStr = url.getQuery(); 120 hasDt = (queryStr != null) && queryStr.contains(DELEGATION_PARAM + "="); 121 } 122 return hasDt; 123 } 124 125 @Override 126 public void authenticate(URL url, AuthenticatedURL.Token token) 127 throws IOException, AuthenticationException { 128 if (!hasDelegationToken(url, token)) { 129 // check and renew TGT to handle potential expiration 130 UserGroupInformation.getCurrentUser().checkTGTAndReloginFromKeytab(); 131 authenticator.authenticate(url, token); 132 } 133 } 134 135 /** 136 * Requests a delegation token using the configured <code>Authenticator</code> 137 * for authentication. 138 * 139 * @param url the URL to get the delegation token from. Only HTTP/S URLs are 140 * supported. 141 * @param token the authentication token being used for the user where the 142 * Delegation token will be stored. 143 * @param renewer the renewer user. 144 * @throws IOException if an IO error occurred. 145 * @throws AuthenticationException if an authentication exception occurred. 146 */ 147 public Token<AbstractDelegationTokenIdentifier> getDelegationToken(URL url, 148 AuthenticatedURL.Token token, String renewer) 149 throws IOException, AuthenticationException { 150 return getDelegationToken(url, token, renewer, null); 151 } 152 153 /** 154 * Requests a delegation token using the configured <code>Authenticator</code> 155 * for authentication. 156 * 157 * @param url the URL to get the delegation token from. Only HTTP/S URLs are 158 * supported. 159 * @param token the authentication token being used for the user where the 160 * Delegation token will be stored. 161 * @param renewer the renewer user. 162 * @param doAsUser the user to do as, which will be the token owner. 163 * @throws IOException if an IO error occurred. 164 * @throws AuthenticationException if an authentication exception occurred. 165 */ 166 public Token<AbstractDelegationTokenIdentifier> getDelegationToken(URL url, 167 AuthenticatedURL.Token token, String renewer, String doAsUser) 168 throws IOException, AuthenticationException { 169 Map json = doDelegationTokenOperation(url, token, 170 DelegationTokenOperation.GETDELEGATIONTOKEN, renewer, null, true, 171 doAsUser); 172 json = (Map) json.get(DELEGATION_TOKEN_JSON); 173 String tokenStr = (String) json.get(DELEGATION_TOKEN_URL_STRING_JSON); 174 Token<AbstractDelegationTokenIdentifier> dToken = 175 new Token<AbstractDelegationTokenIdentifier>(); 176 dToken.decodeFromUrlString(tokenStr); 177 InetSocketAddress service = new InetSocketAddress(url.getHost(), 178 url.getPort()); 179 SecurityUtil.setTokenService(dToken, service); 180 return dToken; 181 } 182 183 /** 184 * Renews a delegation token from the server end-point using the 185 * configured <code>Authenticator</code> for authentication. 186 * 187 * @param url the URL to renew the delegation token from. Only HTTP/S URLs are 188 * supported. 189 * @param token the authentication token with the Delegation Token to renew. 190 * @throws IOException if an IO error occurred. 191 * @throws AuthenticationException if an authentication exception occurred. 192 */ 193 public long renewDelegationToken(URL url, 194 AuthenticatedURL.Token token, 195 Token<AbstractDelegationTokenIdentifier> dToken) 196 throws IOException, AuthenticationException { 197 return renewDelegationToken(url, token, dToken, null); 198 } 199 200 /** 201 * Renews a delegation token from the server end-point using the 202 * configured <code>Authenticator</code> for authentication. 203 * 204 * @param url the URL to renew the delegation token from. Only HTTP/S URLs are 205 * supported. 206 * @param token the authentication token with the Delegation Token to renew. 207 * @param doAsUser the user to do as, which will be the token owner. 208 * @throws IOException if an IO error occurred. 209 * @throws AuthenticationException if an authentication exception occurred. 210 */ 211 public long renewDelegationToken(URL url, 212 AuthenticatedURL.Token token, 213 Token<AbstractDelegationTokenIdentifier> dToken, String doAsUser) 214 throws IOException, AuthenticationException { 215 Map json = doDelegationTokenOperation(url, token, 216 DelegationTokenOperation.RENEWDELEGATIONTOKEN, null, dToken, true, 217 doAsUser); 218 return (Long) json.get(RENEW_DELEGATION_TOKEN_JSON); 219 } 220 221 /** 222 * Cancels a delegation token from the server end-point. It does not require 223 * being authenticated by the configured <code>Authenticator</code>. 224 * 225 * @param url the URL to cancel the delegation token from. Only HTTP/S URLs 226 * are supported. 227 * @param token the authentication token with the Delegation Token to cancel. 228 * @throws IOException if an IO error occurred. 229 */ 230 public void cancelDelegationToken(URL url, 231 AuthenticatedURL.Token token, 232 Token<AbstractDelegationTokenIdentifier> dToken) 233 throws IOException { 234 cancelDelegationToken(url, token, dToken, null); 235 } 236 237 /** 238 * Cancels a delegation token from the server end-point. It does not require 239 * being authenticated by the configured <code>Authenticator</code>. 240 * 241 * @param url the URL to cancel the delegation token from. Only HTTP/S URLs 242 * are supported. 243 * @param token the authentication token with the Delegation Token to cancel. 244 * @param doAsUser the user to do as, which will be the token owner. 245 * @throws IOException if an IO error occurred. 246 */ 247 public void cancelDelegationToken(URL url, 248 AuthenticatedURL.Token token, 249 Token<AbstractDelegationTokenIdentifier> dToken, String doAsUser) 250 throws IOException { 251 try { 252 doDelegationTokenOperation(url, token, 253 DelegationTokenOperation.CANCELDELEGATIONTOKEN, null, dToken, false, 254 doAsUser); 255 } catch (AuthenticationException ex) { 256 throw new IOException("This should not happen: " + ex.getMessage(), ex); 257 } 258 } 259 260 private Map doDelegationTokenOperation(URL url, 261 AuthenticatedURL.Token token, DelegationTokenOperation operation, 262 String renewer, Token<?> dToken, boolean hasResponse, String doAsUser) 263 throws IOException, AuthenticationException { 264 Map ret = null; 265 Map<String, String> params = new HashMap<String, String>(); 266 params.put(OP_PARAM, operation.toString()); 267 if (renewer != null) { 268 params.put(RENEWER_PARAM, renewer); 269 } 270 if (dToken != null) { 271 params.put(TOKEN_PARAM, dToken.encodeToUrlString()); 272 } 273 // proxyuser 274 if (doAsUser != null) { 275 params.put(DelegationTokenAuthenticatedURL.DO_AS, 276 URLEncoder.encode(doAsUser, "UTF-8")); 277 } 278 String urlStr = url.toExternalForm(); 279 StringBuilder sb = new StringBuilder(urlStr); 280 String separator = (urlStr.contains("?")) ? "&" : "?"; 281 for (Map.Entry<String, String> entry : params.entrySet()) { 282 sb.append(separator).append(entry.getKey()).append("="). 283 append(URLEncoder.encode(entry.getValue(), "UTF8")); 284 separator = "&"; 285 } 286 url = new URL(sb.toString()); 287 AuthenticatedURL aUrl = new AuthenticatedURL(this, connConfigurator); 288 HttpURLConnection conn = aUrl.openConnection(url, token); 289 conn.setRequestMethod(operation.getHttpMethod()); 290 HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK); 291 if (hasResponse) { 292 String contentType = conn.getHeaderField(CONTENT_TYPE); 293 contentType = (contentType != null) ? StringUtils.toLowerCase(contentType) 294 : null; 295 if (contentType != null && 296 contentType.contains(APPLICATION_JSON_MIME)) { 297 try { 298 ObjectMapper mapper = new ObjectMapper(); 299 ret = mapper.readValue(conn.getInputStream(), Map.class); 300 } catch (Exception ex) { 301 throw new AuthenticationException(String.format( 302 "'%s' did not handle the '%s' delegation token operation: %s", 303 url.getAuthority(), operation, ex.getMessage()), ex); 304 } 305 } else { 306 throw new AuthenticationException(String.format("'%s' did not " + 307 "respond with JSON to the '%s' delegation token operation", 308 url.getAuthority(), operation)); 309 } 310 } 311 return ret; 312 } 313 314}