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