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.fs.http.server; 019 020 import org.apache.hadoop.classification.InterfaceAudience; 021 import org.apache.hadoop.fs.http.client.HttpFSFileSystem; 022 import org.apache.hadoop.fs.http.client.HttpFSKerberosAuthenticator; 023 import org.apache.hadoop.fs.http.client.HttpFSKerberosAuthenticator.DelegationTokenOperation; 024 import org.apache.hadoop.lib.service.DelegationTokenIdentifier; 025 import org.apache.hadoop.lib.service.DelegationTokenManager; 026 import org.apache.hadoop.lib.service.DelegationTokenManagerException; 027 import org.apache.hadoop.security.UserGroupInformation; 028 import org.apache.hadoop.security.authentication.client.AuthenticationException; 029 import org.apache.hadoop.security.authentication.server.AuthenticationToken; 030 import org.apache.hadoop.security.authentication.server.KerberosAuthenticationHandler; 031 import org.apache.hadoop.security.token.Token; 032 import org.json.simple.JSONObject; 033 034 import javax.servlet.http.HttpServletRequest; 035 import javax.servlet.http.HttpServletResponse; 036 import javax.ws.rs.core.MediaType; 037 import java.io.IOException; 038 import java.io.Writer; 039 import java.text.MessageFormat; 040 import java.util.ArrayList; 041 import java.util.Arrays; 042 import java.util.HashMap; 043 import java.util.HashSet; 044 import java.util.LinkedHashMap; 045 import java.util.List; 046 import java.util.Map; 047 import java.util.Set; 048 049 /** 050 * Server side <code>AuthenticationHandler</code> that authenticates requests 051 * using the incoming delegation token as a 'delegation' query string parameter. 052 * <p/> 053 * If not delegation token is present in the request it delegates to the 054 * {@link KerberosAuthenticationHandler} 055 */ 056 @InterfaceAudience.Private 057 public class HttpFSKerberosAuthenticationHandler 058 extends KerberosAuthenticationHandler { 059 060 static final Set<String> DELEGATION_TOKEN_OPS = 061 new HashSet<String>(); 062 063 static { 064 DELEGATION_TOKEN_OPS.add( 065 DelegationTokenOperation.GETDELEGATIONTOKEN.toString()); 066 DELEGATION_TOKEN_OPS.add( 067 DelegationTokenOperation.GETDELEGATIONTOKENS.toString()); 068 DELEGATION_TOKEN_OPS.add( 069 DelegationTokenOperation.RENEWDELEGATIONTOKEN.toString()); 070 DELEGATION_TOKEN_OPS.add( 071 DelegationTokenOperation.CANCELDELEGATIONTOKEN.toString()); 072 } 073 074 public static final String TYPE = "kerberos-dt"; 075 076 /** 077 * Returns authentication type of the handler. 078 * 079 * @return <code>delegationtoken-kerberos</code> 080 */ 081 @Override 082 public String getType() { 083 return TYPE; 084 } 085 086 private static final String ENTER = System.getProperty("line.separator"); 087 088 @Override 089 @SuppressWarnings("unchecked") 090 public boolean managementOperation(AuthenticationToken token, 091 HttpServletRequest request, HttpServletResponse response) 092 throws IOException, AuthenticationException { 093 boolean requestContinues = true; 094 String op = request.getParameter(HttpFSFileSystem.OP_PARAM); 095 op = (op != null) ? op.toUpperCase() : null; 096 if (DELEGATION_TOKEN_OPS.contains(op) && 097 !request.getMethod().equals("OPTIONS")) { 098 DelegationTokenOperation dtOp = 099 DelegationTokenOperation.valueOf(op); 100 if (dtOp.getHttpMethod().equals(request.getMethod())) { 101 if (dtOp.requiresKerberosCredentials() && token == null) { 102 response.sendError(HttpServletResponse.SC_UNAUTHORIZED, 103 MessageFormat.format( 104 "Operation [{0}] requires SPNEGO authentication established", 105 dtOp)); 106 requestContinues = false; 107 } else { 108 DelegationTokenManager tokenManager = 109 HttpFSServerWebApp.get().get(DelegationTokenManager.class); 110 try { 111 Map map = null; 112 switch (dtOp) { 113 case GETDELEGATIONTOKEN: 114 case GETDELEGATIONTOKENS: 115 String renewerParam = 116 request.getParameter(HttpFSKerberosAuthenticator.RENEWER_PARAM); 117 if (renewerParam == null) { 118 renewerParam = token.getUserName(); 119 } 120 Token<?> dToken = tokenManager.createToken( 121 UserGroupInformation.getCurrentUser(), renewerParam); 122 if (dtOp == DelegationTokenOperation.GETDELEGATIONTOKEN) { 123 map = delegationTokenToJSON(dToken); 124 } else { 125 map = delegationTokensToJSON(Arrays.asList((Token)dToken)); 126 } 127 break; 128 case RENEWDELEGATIONTOKEN: 129 case CANCELDELEGATIONTOKEN: 130 String tokenParam = 131 request.getParameter(HttpFSKerberosAuthenticator.TOKEN_PARAM); 132 if (tokenParam == null) { 133 response.sendError(HttpServletResponse.SC_BAD_REQUEST, 134 MessageFormat.format( 135 "Operation [{0}] requires the parameter [{1}]", 136 dtOp, HttpFSKerberosAuthenticator.TOKEN_PARAM)); 137 requestContinues = false; 138 } else { 139 if (dtOp == DelegationTokenOperation.CANCELDELEGATIONTOKEN) { 140 Token<DelegationTokenIdentifier> dt = 141 new Token<DelegationTokenIdentifier>(); 142 dt.decodeFromUrlString(tokenParam); 143 tokenManager.cancelToken(dt, 144 UserGroupInformation.getCurrentUser().getUserName()); 145 } else { 146 Token<DelegationTokenIdentifier> dt = 147 new Token<DelegationTokenIdentifier>(); 148 dt.decodeFromUrlString(tokenParam); 149 long expirationTime = 150 tokenManager.renewToken(dt, token.getUserName()); 151 map = new HashMap(); 152 map.put("long", expirationTime); 153 } 154 } 155 break; 156 } 157 if (requestContinues) { 158 response.setStatus(HttpServletResponse.SC_OK); 159 if (map != null) { 160 response.setContentType(MediaType.APPLICATION_JSON); 161 Writer writer = response.getWriter(); 162 JSONObject.writeJSONString(map, writer); 163 writer.write(ENTER); 164 writer.flush(); 165 166 } 167 requestContinues = false; 168 } 169 } catch (DelegationTokenManagerException ex) { 170 throw new AuthenticationException(ex.toString(), ex); 171 } 172 } 173 } else { 174 response.sendError(HttpServletResponse.SC_BAD_REQUEST, 175 MessageFormat.format( 176 "Wrong HTTP method [{0}] for operation [{1}], it should be [{2}]", 177 request.getMethod(), dtOp, dtOp.getHttpMethod())); 178 requestContinues = false; 179 } 180 } 181 return requestContinues; 182 } 183 184 @SuppressWarnings("unchecked") 185 private static Map delegationTokenToJSON(Token token) throws IOException { 186 Map json = new LinkedHashMap(); 187 json.put(HttpFSKerberosAuthenticator.DELEGATION_TOKEN_URL_STRING_JSON, 188 token.encodeToUrlString()); 189 Map response = new LinkedHashMap(); 190 response.put(HttpFSKerberosAuthenticator.DELEGATION_TOKEN_JSON, json); 191 return response; 192 } 193 194 @SuppressWarnings("unchecked") 195 private static Map delegationTokensToJSON(List<Token> tokens) 196 throws IOException { 197 List list = new ArrayList(); 198 for (Token token : tokens) { 199 Map map = new HashMap(); 200 map.put(HttpFSKerberosAuthenticator.DELEGATION_TOKEN_URL_STRING_JSON, 201 token.encodeToUrlString()); 202 list.add(map); 203 } 204 Map map = new HashMap(); 205 map.put(HttpFSKerberosAuthenticator.DELEGATION_TOKEN_JSON, list); 206 Map response = new LinkedHashMap(); 207 response.put(HttpFSKerberosAuthenticator.DELEGATION_TOKENS_JSON, map); 208 return response; 209 } 210 211 /** 212 * Authenticates a request looking for the <code>delegation</code> 213 * query-string parameter and verifying it is a valid token. If there is not 214 * <code>delegation</code> query-string parameter, it delegates the 215 * authentication to the {@link KerberosAuthenticationHandler} unless it is 216 * disabled. 217 * 218 * @param request the HTTP client request. 219 * @param response the HTTP client response. 220 * 221 * @return the authentication token for the authenticated request. 222 * @throws IOException thrown if an IO error occurred. 223 * @throws AuthenticationException thrown if the authentication failed. 224 */ 225 @Override 226 public AuthenticationToken authenticate(HttpServletRequest request, 227 HttpServletResponse response) 228 throws IOException, AuthenticationException { 229 AuthenticationToken token; 230 String delegationParam = 231 request.getParameter(HttpFSKerberosAuthenticator.DELEGATION_PARAM); 232 if (delegationParam != null) { 233 try { 234 Token<DelegationTokenIdentifier> dt = 235 new Token<DelegationTokenIdentifier>(); 236 dt.decodeFromUrlString(delegationParam); 237 DelegationTokenManager tokenManager = 238 HttpFSServerWebApp.get().get(DelegationTokenManager.class); 239 UserGroupInformation ugi = tokenManager.verifyToken(dt); 240 final String shortName = ugi.getShortUserName(); 241 242 // creating a ephemeral token 243 token = new AuthenticationToken(shortName, ugi.getUserName(), 244 getType()); 245 token.setExpires(0); 246 } catch (Throwable ex) { 247 throw new AuthenticationException("Could not verify DelegationToken, " + 248 ex.toString(), ex); 249 } 250 } else { 251 token = super.authenticate(request, response); 252 } 253 return token; 254 } 255 256 257 }