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
019 package org.apache.hadoop.hdfs.security.token.delegation;
020
021 import com.google.common.base.Preconditions;
022 import com.google.common.collect.Lists;
023 import com.google.protobuf.ByteString;
024 import org.apache.commons.logging.Log;
025 import org.apache.commons.logging.LogFactory;
026 import org.apache.hadoop.classification.InterfaceAudience;
027 import org.apache.hadoop.hdfs.server.namenode.FSNamesystem;
028 import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SecretManagerSection;
029 import org.apache.hadoop.hdfs.server.namenode.NameNode;
030 import org.apache.hadoop.hdfs.server.namenode.NameNode.OperationCategory;
031 import org.apache.hadoop.hdfs.server.namenode.startupprogress.Phase;
032 import org.apache.hadoop.hdfs.server.namenode.startupprogress.StartupProgress;
033 import org.apache.hadoop.hdfs.server.namenode.startupprogress.StartupProgress.Counter;
034 import org.apache.hadoop.hdfs.server.namenode.startupprogress.Step;
035 import org.apache.hadoop.hdfs.server.namenode.startupprogress.StepType;
036 import org.apache.hadoop.io.Text;
037 import org.apache.hadoop.ipc.RetriableException;
038 import org.apache.hadoop.ipc.StandbyException;
039 import org.apache.hadoop.security.Credentials;
040 import org.apache.hadoop.security.SecurityUtil;
041 import org.apache.hadoop.security.UserGroupInformation;
042 import org.apache.hadoop.security.token.Token;
043 import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenSecretManager;
044 import org.apache.hadoop.security.token.delegation.DelegationKey;
045
046 import java.io.DataInput;
047 import java.io.IOException;
048 import java.io.InterruptedIOException;
049 import java.net.InetSocketAddress;
050 import java.util.ArrayList;
051 import java.util.List;
052 import java.util.Map.Entry;
053
054 /**
055 * A HDFS specific delegation token secret manager.
056 * The secret manager is responsible for generating and accepting the password
057 * for each token.
058 */
059 @InterfaceAudience.Private
060 public class DelegationTokenSecretManager
061 extends AbstractDelegationTokenSecretManager<DelegationTokenIdentifier> {
062
063 private static final Log LOG = LogFactory
064 .getLog(DelegationTokenSecretManager.class);
065
066 private final FSNamesystem namesystem;
067 private final SerializerCompat serializerCompat = new SerializerCompat();
068
069 public DelegationTokenSecretManager(long delegationKeyUpdateInterval,
070 long delegationTokenMaxLifetime, long delegationTokenRenewInterval,
071 long delegationTokenRemoverScanInterval, FSNamesystem namesystem) {
072 this(delegationKeyUpdateInterval, delegationTokenMaxLifetime,
073 delegationTokenRenewInterval, delegationTokenRemoverScanInterval, false,
074 namesystem);
075 }
076
077 /**
078 * Create a secret manager
079 * @param delegationKeyUpdateInterval the number of seconds for rolling new
080 * secret keys.
081 * @param delegationTokenMaxLifetime the maximum lifetime of the delegation
082 * tokens
083 * @param delegationTokenRenewInterval how often the tokens must be renewed
084 * @param delegationTokenRemoverScanInterval how often the tokens are scanned
085 * for expired tokens
086 * @param storeTokenTrackingId whether to store the token's tracking id
087 */
088 public DelegationTokenSecretManager(long delegationKeyUpdateInterval,
089 long delegationTokenMaxLifetime, long delegationTokenRenewInterval,
090 long delegationTokenRemoverScanInterval, boolean storeTokenTrackingId,
091 FSNamesystem namesystem) {
092 super(delegationKeyUpdateInterval, delegationTokenMaxLifetime,
093 delegationTokenRenewInterval, delegationTokenRemoverScanInterval);
094 this.namesystem = namesystem;
095 this.storeTokenTrackingId = storeTokenTrackingId;
096 }
097
098 @Override //SecretManager
099 public DelegationTokenIdentifier createIdentifier() {
100 return new DelegationTokenIdentifier();
101 }
102
103 @Override
104 public byte[] retrievePassword(
105 DelegationTokenIdentifier identifier) throws InvalidToken {
106 try {
107 // this check introduces inconsistency in the authentication to a
108 // HA standby NN. non-token auths are allowed into the namespace which
109 // decides whether to throw a StandbyException. tokens are a bit
110 // different in that a standby may be behind and thus not yet know
111 // of all tokens issued by the active NN. the following check does
112 // not allow ANY token auth, however it should allow known tokens in
113 namesystem.checkOperation(OperationCategory.READ);
114 } catch (StandbyException se) {
115 // FIXME: this is a hack to get around changing method signatures by
116 // tunneling a non-InvalidToken exception as the cause which the
117 // RPC server will unwrap before returning to the client
118 InvalidToken wrappedStandby = new InvalidToken("StandbyException");
119 wrappedStandby.initCause(se);
120 throw wrappedStandby;
121 }
122 return super.retrievePassword(identifier);
123 }
124
125 @Override
126 public byte[] retriableRetrievePassword(DelegationTokenIdentifier identifier)
127 throws InvalidToken, StandbyException, RetriableException, IOException {
128 namesystem.checkOperation(OperationCategory.READ);
129 try {
130 return super.retrievePassword(identifier);
131 } catch (InvalidToken it) {
132 if (namesystem.inTransitionToActive()) {
133 // if the namesystem is currently in the middle of transition to
134 // active state, let client retry since the corresponding editlog may
135 // have not been applied yet
136 throw new RetriableException(it);
137 } else {
138 throw it;
139 }
140 }
141 }
142
143 /**
144 * Returns expiry time of a token given its identifier.
145 *
146 * @param dtId DelegationTokenIdentifier of a token
147 * @return Expiry time of the token
148 * @throws IOException
149 */
150 public synchronized long getTokenExpiryTime(
151 DelegationTokenIdentifier dtId) throws IOException {
152 DelegationTokenInformation info = currentTokens.get(dtId);
153 if (info != null) {
154 return info.getRenewDate();
155 } else {
156 throw new IOException("No delegation token found for this identifier");
157 }
158 }
159
160 /**
161 * Load SecretManager state from fsimage.
162 *
163 * @param in input stream to read fsimage
164 * @throws IOException
165 */
166 public synchronized void loadSecretManagerStateCompat(DataInput in)
167 throws IOException {
168 if (running) {
169 // a safety check
170 throw new IOException(
171 "Can't load state from image in a running SecretManager.");
172 }
173 serializerCompat.load(in);
174 }
175
176 public static class SecretManagerState {
177 public final SecretManagerSection section;
178 public final List<SecretManagerSection.DelegationKey> keys;
179 public final List<SecretManagerSection.PersistToken> tokens;
180
181 public SecretManagerState(
182 SecretManagerSection s,
183 List<SecretManagerSection.DelegationKey> keys,
184 List<SecretManagerSection.PersistToken> tokens) {
185 this.section = s;
186 this.keys = keys;
187 this.tokens = tokens;
188 }
189 }
190
191 public synchronized void loadSecretManagerState(SecretManagerState state)
192 throws IOException {
193 Preconditions.checkState(!running,
194 "Can't load state from image in a running SecretManager.");
195
196 currentId = state.section.getCurrentId();
197 delegationTokenSequenceNumber = state.section.getTokenSequenceNumber();
198 for (SecretManagerSection.DelegationKey k : state.keys) {
199 addKey(new DelegationKey(k.getId(), k.getExpiryDate(), k.hasKey() ? k
200 .getKey().toByteArray() : null));
201 }
202
203 for (SecretManagerSection.PersistToken t : state.tokens) {
204 DelegationTokenIdentifier id = new DelegationTokenIdentifier(new Text(
205 t.getOwner()), new Text(t.getRenewer()), new Text(t.getRealUser()));
206 id.setIssueDate(t.getIssueDate());
207 id.setMaxDate(t.getMaxDate());
208 id.setSequenceNumber(t.getSequenceNumber());
209 id.setMasterKeyId(t.getMasterKeyId());
210 addPersistedDelegationToken(id, t.getExpiryDate());
211 }
212 }
213
214 public synchronized SecretManagerState saveSecretManagerState() {
215 SecretManagerSection s = SecretManagerSection.newBuilder()
216 .setCurrentId(currentId)
217 .setTokenSequenceNumber(delegationTokenSequenceNumber)
218 .setNumKeys(allKeys.size()).setNumTokens(currentTokens.size()).build();
219 ArrayList<SecretManagerSection.DelegationKey> keys = Lists
220 .newArrayListWithCapacity(allKeys.size());
221 ArrayList<SecretManagerSection.PersistToken> tokens = Lists
222 .newArrayListWithCapacity(currentTokens.size());
223
224 for (DelegationKey v : allKeys.values()) {
225 SecretManagerSection.DelegationKey.Builder b = SecretManagerSection.DelegationKey
226 .newBuilder().setId(v.getKeyId()).setExpiryDate(v.getExpiryDate());
227 if (v.getEncodedKey() != null) {
228 b.setKey(ByteString.copyFrom(v.getEncodedKey()));
229 }
230 keys.add(b.build());
231 }
232
233 for (Entry<DelegationTokenIdentifier, DelegationTokenInformation> e : currentTokens
234 .entrySet()) {
235 DelegationTokenIdentifier id = e.getKey();
236 SecretManagerSection.PersistToken.Builder b = SecretManagerSection.PersistToken
237 .newBuilder().setOwner(id.getOwner().toString())
238 .setRenewer(id.getRenewer().toString())
239 .setRealUser(id.getRealUser().toString())
240 .setIssueDate(id.getIssueDate()).setMaxDate(id.getMaxDate())
241 .setSequenceNumber(id.getSequenceNumber())
242 .setMasterKeyId(id.getMasterKeyId())
243 .setExpiryDate(e.getValue().getRenewDate());
244 tokens.add(b.build());
245 }
246
247 return new SecretManagerState(s, keys, tokens);
248 }
249
250 /**
251 * This method is intended to be used only while reading edit logs.
252 *
253 * @param identifier DelegationTokenIdentifier read from the edit logs or
254 * fsimage
255 *
256 * @param expiryTime token expiry time
257 * @throws IOException
258 */
259 public synchronized void addPersistedDelegationToken(
260 DelegationTokenIdentifier identifier, long expiryTime) throws IOException {
261 if (running) {
262 // a safety check
263 throw new IOException(
264 "Can't add persisted delegation token to a running SecretManager.");
265 }
266 int keyId = identifier.getMasterKeyId();
267 DelegationKey dKey = allKeys.get(keyId);
268 if (dKey == null) {
269 LOG
270 .warn("No KEY found for persisted identifier "
271 + identifier.toString());
272 return;
273 }
274 byte[] password = createPassword(identifier.getBytes(), dKey.getKey());
275 if (identifier.getSequenceNumber() > this.delegationTokenSequenceNumber) {
276 this.delegationTokenSequenceNumber = identifier.getSequenceNumber();
277 }
278 if (currentTokens.get(identifier) == null) {
279 currentTokens.put(identifier, new DelegationTokenInformation(expiryTime,
280 password, getTrackingIdIfEnabled(identifier)));
281 } else {
282 throw new IOException(
283 "Same delegation token being added twice; invalid entry in fsimage or editlogs");
284 }
285 }
286
287 /**
288 * Add a MasterKey to the list of keys.
289 *
290 * @param key DelegationKey
291 * @throws IOException
292 */
293 public synchronized void updatePersistedMasterKey(DelegationKey key)
294 throws IOException {
295 addKey(key);
296 }
297
298 /**
299 * Update the token cache with renewal record in edit logs.
300 *
301 * @param identifier DelegationTokenIdentifier of the renewed token
302 * @param expiryTime
303 * @throws IOException
304 */
305 public synchronized void updatePersistedTokenRenewal(
306 DelegationTokenIdentifier identifier, long expiryTime) throws IOException {
307 if (running) {
308 // a safety check
309 throw new IOException(
310 "Can't update persisted delegation token renewal to a running SecretManager.");
311 }
312 DelegationTokenInformation info = null;
313 info = currentTokens.get(identifier);
314 if (info != null) {
315 int keyId = identifier.getMasterKeyId();
316 byte[] password = createPassword(identifier.getBytes(), allKeys
317 .get(keyId).getKey());
318 currentTokens.put(identifier, new DelegationTokenInformation(expiryTime,
319 password, getTrackingIdIfEnabled(identifier)));
320 }
321 }
322
323 /**
324 * Update the token cache with the cancel record in edit logs
325 *
326 * @param identifier DelegationTokenIdentifier of the canceled token
327 * @throws IOException
328 */
329 public synchronized void updatePersistedTokenCancellation(
330 DelegationTokenIdentifier identifier) throws IOException {
331 if (running) {
332 // a safety check
333 throw new IOException(
334 "Can't update persisted delegation token renewal to a running SecretManager.");
335 }
336 currentTokens.remove(identifier);
337 }
338
339 /**
340 * Returns the number of delegation keys currently stored.
341 * @return number of delegation keys
342 */
343 public synchronized int getNumberOfKeys() {
344 return allKeys.size();
345 }
346
347 /**
348 * Call namesystem to update editlogs for new master key.
349 */
350 @Override //AbstractDelegationTokenManager
351 protected void logUpdateMasterKey(DelegationKey key)
352 throws IOException {
353 synchronized (noInterruptsLock) {
354 // The edit logging code will fail catastrophically if it
355 // is interrupted during a logSync, since the interrupt
356 // closes the edit log files. Doing this inside the
357 // above lock and then checking interruption status
358 // prevents this bug.
359 if (Thread.interrupted()) {
360 throw new InterruptedIOException(
361 "Interrupted before updating master key");
362 }
363 namesystem.logUpdateMasterKey(key);
364 }
365 }
366
367 @Override //AbstractDelegationTokenManager
368 protected void logExpireToken(final DelegationTokenIdentifier dtId)
369 throws IOException {
370 synchronized (noInterruptsLock) {
371 // The edit logging code will fail catastrophically if it
372 // is interrupted during a logSync, since the interrupt
373 // closes the edit log files. Doing this inside the
374 // above lock and then checking interruption status
375 // prevents this bug.
376 if (Thread.interrupted()) {
377 throw new InterruptedIOException(
378 "Interrupted before expiring delegation token");
379 }
380 namesystem.logExpireDelegationToken(dtId);
381 }
382 }
383
384 /** A utility method for creating credentials. */
385 public static Credentials createCredentials(final NameNode namenode,
386 final UserGroupInformation ugi, final String renewer) throws IOException {
387 final Token<DelegationTokenIdentifier> token = namenode.getRpcServer(
388 ).getDelegationToken(new Text(renewer));
389 if (token == null) {
390 throw new IOException("Failed to get the token for " + renewer
391 + ", user=" + ugi.getShortUserName());
392 }
393
394 final InetSocketAddress addr = namenode.getNameNodeAddress();
395 SecurityUtil.setTokenService(token, addr);
396 final Credentials c = new Credentials();
397 c.addToken(new Text(ugi.getShortUserName()), token);
398 return c;
399 }
400
401 private final class SerializerCompat {
402 private void load(DataInput in) throws IOException {
403 currentId = in.readInt();
404 loadAllKeys(in);
405 delegationTokenSequenceNumber = in.readInt();
406 loadCurrentTokens(in);
407 }
408
409 /**
410 * Private helper methods to load Delegation tokens from fsimage
411 */
412 private synchronized void loadCurrentTokens(DataInput in)
413 throws IOException {
414 StartupProgress prog = NameNode.getStartupProgress();
415 Step step = new Step(StepType.DELEGATION_TOKENS);
416 prog.beginStep(Phase.LOADING_FSIMAGE, step);
417 int numberOfTokens = in.readInt();
418 prog.setTotal(Phase.LOADING_FSIMAGE, step, numberOfTokens);
419 Counter counter = prog.getCounter(Phase.LOADING_FSIMAGE, step);
420 for (int i = 0; i < numberOfTokens; i++) {
421 DelegationTokenIdentifier id = new DelegationTokenIdentifier();
422 id.readFields(in);
423 long expiryTime = in.readLong();
424 addPersistedDelegationToken(id, expiryTime);
425 counter.increment();
426 }
427 prog.endStep(Phase.LOADING_FSIMAGE, step);
428 }
429
430 /**
431 * Private helper method to load delegation keys from fsimage.
432 * @param in
433 * @throws IOException
434 */
435 private synchronized void loadAllKeys(DataInput in) throws IOException {
436 StartupProgress prog = NameNode.getStartupProgress();
437 Step step = new Step(StepType.DELEGATION_KEYS);
438 prog.beginStep(Phase.LOADING_FSIMAGE, step);
439 int numberOfKeys = in.readInt();
440 prog.setTotal(Phase.LOADING_FSIMAGE, step, numberOfKeys);
441 Counter counter = prog.getCounter(Phase.LOADING_FSIMAGE, step);
442 for (int i = 0; i < numberOfKeys; i++) {
443 DelegationKey value = new DelegationKey();
444 value.readFields(in);
445 addKey(value);
446 counter.increment();
447 }
448 prog.endStep(Phase.LOADING_FSIMAGE, step);
449 }
450 }
451
452 }