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 019package org.apache.hadoop.security.token; 020 021import com.google.common.collect.Maps; 022import com.google.common.primitives.Bytes; 023 024import org.apache.commons.codec.binary.Base64; 025import org.apache.commons.logging.Log; 026import org.apache.commons.logging.LogFactory; 027import org.apache.hadoop.classification.InterfaceAudience; 028import org.apache.hadoop.classification.InterfaceStability; 029import org.apache.hadoop.conf.Configuration; 030import org.apache.hadoop.io.*; 031import org.apache.hadoop.util.ReflectionUtils; 032 033import java.io.*; 034import java.util.Arrays; 035import java.util.Map; 036import java.util.ServiceLoader; 037import java.util.UUID; 038 039/** 040 * The client-side form of the token. 041 */ 042@InterfaceAudience.Public 043@InterfaceStability.Evolving 044public class Token<T extends TokenIdentifier> implements Writable { 045 public static final Log LOG = LogFactory.getLog(Token.class); 046 047 private static Map<Text, Class<? extends TokenIdentifier>> tokenKindMap; 048 049 private byte[] identifier; 050 private byte[] password; 051 private Text kind; 052 private Text service; 053 private TokenRenewer renewer; 054 055 /** 056 * Construct a token given a token identifier and a secret manager for the 057 * type of the token identifier. 058 * @param id the token identifier 059 * @param mgr the secret manager 060 */ 061 public Token(T id, SecretManager<T> mgr) { 062 password = mgr.createPassword(id); 063 identifier = id.getBytes(); 064 kind = id.getKind(); 065 service = new Text(); 066 } 067 068 /** 069 * Construct a token from the components. 070 * @param identifier the token identifier 071 * @param password the token's password 072 * @param kind the kind of token 073 * @param service the service for this token 074 */ 075 public Token(byte[] identifier, byte[] password, Text kind, Text service) { 076 this.identifier = (identifier == null)? new byte[0] : identifier; 077 this.password = (password == null)? new byte[0] : password; 078 this.kind = (kind == null)? new Text() : kind; 079 this.service = (service == null)? new Text() : service; 080 } 081 082 /** 083 * Default constructor 084 */ 085 public Token() { 086 identifier = new byte[0]; 087 password = new byte[0]; 088 kind = new Text(); 089 service = new Text(); 090 } 091 092 /** 093 * Clone a token. 094 * @param other the token to clone 095 */ 096 public Token(Token<T> other) { 097 this.identifier = other.identifier; 098 this.password = other.password; 099 this.kind = other.kind; 100 this.service = other.service; 101 } 102 103 /** 104 * Get the token identifier's byte representation 105 * @return the token identifier's byte representation 106 */ 107 public byte[] getIdentifier() { 108 return identifier; 109 } 110 111 private static Class<? extends TokenIdentifier> 112 getClassForIdentifier(Text kind) { 113 Class<? extends TokenIdentifier> cls = null; 114 synchronized (Token.class) { 115 if (tokenKindMap == null) { 116 tokenKindMap = Maps.newHashMap(); 117 for (TokenIdentifier id : ServiceLoader.load(TokenIdentifier.class)) { 118 tokenKindMap.put(id.getKind(), id.getClass()); 119 } 120 } 121 cls = tokenKindMap.get(kind); 122 } 123 if (cls == null) { 124 LOG.debug("Cannot find class for token kind " + kind); 125 return null; 126 } 127 return cls; 128 } 129 130 /** 131 * Get the token identifier object, or null if it could not be constructed 132 * (because the class could not be loaded, for example). 133 * @return the token identifier, or null 134 * @throws IOException 135 */ 136 @SuppressWarnings("unchecked") 137 public T decodeIdentifier() throws IOException { 138 Class<? extends TokenIdentifier> cls = getClassForIdentifier(getKind()); 139 if (cls == null) { 140 return null; 141 } 142 TokenIdentifier tokenIdentifier = ReflectionUtils.newInstance(cls, null); 143 ByteArrayInputStream buf = new ByteArrayInputStream(identifier); 144 DataInputStream in = new DataInputStream(buf); 145 tokenIdentifier.readFields(in); 146 in.close(); 147 return (T) tokenIdentifier; 148 } 149 150 /** 151 * Get the token password/secret 152 * @return the token password/secret 153 */ 154 public byte[] getPassword() { 155 return password; 156 } 157 158 /** 159 * Get the token kind 160 * @return the kind of the token 161 */ 162 public synchronized Text getKind() { 163 return kind; 164 } 165 166 /** 167 * Set the token kind. This is only intended to be used by services that 168 * wrap another service's token, such as HFTP wrapping HDFS. 169 * @param newKind 170 */ 171 @InterfaceAudience.Private 172 public synchronized void setKind(Text newKind) { 173 kind = newKind; 174 renewer = null; 175 } 176 177 /** 178 * Get the service on which the token is supposed to be used 179 * @return the service name 180 */ 181 public Text getService() { 182 return service; 183 } 184 185 /** 186 * Set the service on which the token is supposed to be used 187 * @param newService the service name 188 */ 189 public void setService(Text newService) { 190 service = newService; 191 } 192 193 /** 194 * Indicates whether the token is a clone. Used by HA failover proxy 195 * to indicate a token should not be visible to the user via 196 * UGI.getCredentials() 197 */ 198 @InterfaceAudience.Private 199 @InterfaceStability.Unstable 200 public static class PrivateToken<T extends TokenIdentifier> extends Token<T> { 201 public PrivateToken(Token<T> token) { 202 super(token); 203 } 204 } 205 206 @Override 207 public void readFields(DataInput in) throws IOException { 208 int len = WritableUtils.readVInt(in); 209 if (identifier == null || identifier.length != len) { 210 identifier = new byte[len]; 211 } 212 in.readFully(identifier); 213 len = WritableUtils.readVInt(in); 214 if (password == null || password.length != len) { 215 password = new byte[len]; 216 } 217 in.readFully(password); 218 kind.readFields(in); 219 service.readFields(in); 220 } 221 222 @Override 223 public void write(DataOutput out) throws IOException { 224 WritableUtils.writeVInt(out, identifier.length); 225 out.write(identifier); 226 WritableUtils.writeVInt(out, password.length); 227 out.write(password); 228 kind.write(out); 229 service.write(out); 230 } 231 232 /** 233 * Generate a string with the url-quoted base64 encoded serialized form 234 * of the Writable. 235 * @param obj the object to serialize 236 * @return the encoded string 237 * @throws IOException 238 */ 239 private static String encodeWritable(Writable obj) throws IOException { 240 DataOutputBuffer buf = new DataOutputBuffer(); 241 obj.write(buf); 242 Base64 encoder = new Base64(0, null, true); 243 byte[] raw = new byte[buf.getLength()]; 244 System.arraycopy(buf.getData(), 0, raw, 0, buf.getLength()); 245 return encoder.encodeToString(raw); 246 } 247 248 /** 249 * Modify the writable to the value from the newValue 250 * @param obj the object to read into 251 * @param newValue the string with the url-safe base64 encoded bytes 252 * @throws IOException 253 */ 254 private static void decodeWritable(Writable obj, 255 String newValue) throws IOException { 256 Base64 decoder = new Base64(0, null, true); 257 DataInputBuffer buf = new DataInputBuffer(); 258 byte[] decoded = decoder.decode(newValue); 259 buf.reset(decoded, decoded.length); 260 obj.readFields(buf); 261 } 262 263 /** 264 * Encode this token as a url safe string 265 * @return the encoded string 266 * @throws IOException 267 */ 268 public String encodeToUrlString() throws IOException { 269 return encodeWritable(this); 270 } 271 272 /** 273 * Decode the given url safe string into this token. 274 * @param newValue the encoded string 275 * @throws IOException 276 */ 277 public void decodeFromUrlString(String newValue) throws IOException { 278 decodeWritable(this, newValue); 279 } 280 281 @SuppressWarnings("unchecked") 282 @Override 283 public boolean equals(Object right) { 284 if (this == right) { 285 return true; 286 } else if (right == null || getClass() != right.getClass()) { 287 return false; 288 } else { 289 Token<T> r = (Token<T>) right; 290 return Arrays.equals(identifier, r.identifier) && 291 Arrays.equals(password, r.password) && 292 kind.equals(r.kind) && 293 service.equals(r.service); 294 } 295 } 296 297 @Override 298 public int hashCode() { 299 return WritableComparator.hashBytes(identifier, identifier.length); 300 } 301 302 private static void addBinaryBuffer(StringBuilder buffer, byte[] bytes) { 303 for (int idx = 0; idx < bytes.length; idx++) { 304 // if not the first, put a blank separator in 305 if (idx != 0) { 306 buffer.append(' '); 307 } 308 String num = Integer.toHexString(0xff & bytes[idx]); 309 // if it is only one digit, add a leading 0. 310 if (num.length() < 2) { 311 buffer.append('0'); 312 } 313 buffer.append(num); 314 } 315 } 316 317 private void identifierToString(StringBuilder buffer) { 318 T id = null; 319 try { 320 id = decodeIdentifier(); 321 } catch (IOException e) { 322 // handle in the finally block 323 } finally { 324 if (id != null) { 325 buffer.append("(").append(id).append(")"); 326 } else { 327 addBinaryBuffer(buffer, identifier); 328 } 329 } 330 } 331 332 @Override 333 public String toString() { 334 StringBuilder buffer = new StringBuilder(); 335 buffer.append("Kind: "); 336 buffer.append(kind.toString()); 337 buffer.append(", Service: "); 338 buffer.append(service.toString()); 339 buffer.append(", Ident: "); 340 identifierToString(buffer); 341 return buffer.toString(); 342 } 343 344 public String buildCacheKey() { 345 return UUID.nameUUIDFromBytes( 346 Bytes.concat(kind.getBytes(), identifier, password)).toString(); 347 } 348 349 private static ServiceLoader<TokenRenewer> renewers = 350 ServiceLoader.load(TokenRenewer.class); 351 352 private synchronized TokenRenewer getRenewer() throws IOException { 353 if (renewer != null) { 354 return renewer; 355 } 356 renewer = TRIVIAL_RENEWER; 357 synchronized (renewers) { 358 for (TokenRenewer canidate : renewers) { 359 if (canidate.handleKind(this.kind)) { 360 renewer = canidate; 361 return renewer; 362 } 363 } 364 } 365 LOG.warn("No TokenRenewer defined for token kind " + this.kind); 366 return renewer; 367 } 368 369 /** 370 * Is this token managed so that it can be renewed or cancelled? 371 * @return true, if it can be renewed and cancelled. 372 */ 373 public boolean isManaged() throws IOException { 374 return getRenewer().isManaged(this); 375 } 376 377 /** 378 * Renew this delegation token 379 * @return the new expiration time 380 * @throws IOException 381 * @throws InterruptedException 382 */ 383 public long renew(Configuration conf 384 ) throws IOException, InterruptedException { 385 return getRenewer().renew(this, conf); 386 } 387 388 /** 389 * Cancel this delegation token 390 * @throws IOException 391 * @throws InterruptedException 392 */ 393 public void cancel(Configuration conf 394 ) throws IOException, InterruptedException { 395 getRenewer().cancel(this, conf); 396 } 397 398 /** 399 * A trivial renewer for token kinds that aren't managed. Sub-classes need 400 * to implement getKind for their token kind. 401 */ 402 @InterfaceAudience.Public 403 @InterfaceStability.Evolving 404 public static class TrivialRenewer extends TokenRenewer { 405 406 // define the kind for this renewer 407 protected Text getKind() { 408 return null; 409 } 410 411 @Override 412 public boolean handleKind(Text kind) { 413 return kind.equals(getKind()); 414 } 415 416 @Override 417 public boolean isManaged(Token<?> token) { 418 return false; 419 } 420 421 @Override 422 public long renew(Token<?> token, Configuration conf) { 423 throw new UnsupportedOperationException("Token renewal is not supported "+ 424 " for " + token.kind + " tokens"); 425 } 426 427 @Override 428 public void cancel(Token<?> token, Configuration conf) throws IOException, 429 InterruptedException { 430 throw new UnsupportedOperationException("Token cancel is not supported " + 431 " for " + token.kind + " tokens"); 432 } 433 434 } 435 private static final TokenRenewer TRIVIAL_RENEWER = new TrivialRenewer(); 436}